trust-dns-client-0.22.0/.cargo_vcs_info.json0000644000000001530000000000100143330ustar { "git": { "sha1": "19b4dc40c046b8d49991bd7b5969333771774f1b" }, "path_in_vcs": "crates/client" }trust-dns-client-0.22.0/Cargo.toml0000644000000077730000000000100123500ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "trust-dns-client" version = "0.22.0" authors = ["Benjamin Fry "] description = """ Trust-DNS is a safe and secure DNS library. This is the Client library with DNSec support. DNSSec with NSEC validation for negative records, is complete. The client supports dynamic DNS with SIG0 authenticated requests, implementing easy to use high level funtions. Trust-DNS is based on the Tokio and Futures libraries, which means it should be easily integrated into other software that also use those libraries. """ homepage = "http://www.trust-dns.org/index.html" documentation = "https://docs.rs/trust-dns" readme = "README.md" keywords = [ "DNS", "BIND", "dig", "named", "dnssec", ] categories = ["network-programming"] license = "MIT/Apache-2.0" repository = "https://github.com/bluejekyll/trust-dns" [package.metadata.docs.rs] all-features = true default-target = "x86_64-unknown-linux-gnu" targets = [ "x86_64-apple-darwin", "x86_64-pc-windows-msvc", ] rustdoc-args = [ "--cfg", "docsrs", ] [lib] name = "trust_dns_client" path = "src/lib.rs" [dependencies.cfg-if] version = "1" [dependencies.data-encoding] version = "2.2.0" [dependencies.futures-channel] version = "0.3.5" features = ["std"] default-features = false [dependencies.futures-util] version = "0.3.5" features = ["std"] default-features = false [dependencies.lazy_static] version = "1.2.0" [dependencies.openssl] version = "0.10" features = [ "v102", "v110", ] optional = true [dependencies.radix_trie] version = "0.2.0" [dependencies.rand] version = "0.8" [dependencies.ring] version = "0.16" features = ["std"] optional = true [dependencies.rustls] version = "0.20.0" optional = true [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dependencies.thiserror] version = "1.0.20" [dependencies.time] version = "0.3" [dependencies.tokio] version = "1.0" features = ["rt"] [dependencies.tracing] version = "0.1.30" [dependencies.trust-dns-proto] version = "0.22.0" [dependencies.webpki] version = "0.22.0" optional = true [dev-dependencies.futures] version = "0.3.5" features = [ "std", "executor", ] default-features = false [dev-dependencies.openssl] version = "0.10" features = [ "v102", "v110", ] optional = false [dev-dependencies.tokio] version = "1.0" features = [ "rt", "macros", ] [dev-dependencies.tracing-subscriber] version = "0.3" features = [ "std", "fmt", "env-filter", ] [features] backtrace = ["trust-dns-proto/backtrace"] dns-over-https = ["trust-dns-proto/dns-over-https"] dns-over-https-openssl = [ "dns-over-https", "dns-over-openssl", ] dns-over-https-rustls = [ "dns-over-https", "dns-over-rustls", "trust-dns-proto/dns-over-https-rustls", ] dns-over-native-tls = [ "dns-over-tls", "trust-dns-proto/dns-over-native-tls", ] dns-over-openssl = [ "dns-over-tls", "dnssec-openssl", "openssl", ] dns-over-quic = [ "dns-over-rustls", "trust-dns-proto/dns-over-quic", ] dns-over-rustls = [ "dns-over-tls", "dnssec-ring", "rustls", "webpki", "trust-dns-proto/dns-over-rustls", ] dns-over-tls = [] dnssec = ["trust-dns-proto/dnssec"] dnssec-openssl = [ "dnssec", "openssl", "trust-dns-proto/dnssec-openssl", ] dnssec-ring = [ "dnssec", "ring", "trust-dns-proto/dnssec-ring", ] mdns = ["trust-dns-proto/mdns"] serde-config = ["serde"] [badges.codecov] branch = "main" repository = "bluejekyll/trust-dns" service = "github" [badges.maintenance] status = "actively-developed" trust-dns-client-0.22.0/Cargo.toml.orig0000644000000100110000000000100132620ustar [package] name = "trust-dns-client" version = "0.22.0" authors = ["Benjamin Fry "] edition = "2018" # A short blurb about the package. This is not rendered in any format when # uploaded to crates.io (aka this is not markdown) description = """ Trust-DNS is a safe and secure DNS library. This is the Client library with DNSec support. DNSSec with NSEC validation for negative records, is complete. The client supports dynamic DNS with SIG0 authenticated requests, implementing easy to use high level funtions. Trust-DNS is based on the Tokio and Futures libraries, which means it should be easily integrated into other software that also use those libraries. """ # These URLs point to more information about the repository documentation = "https://docs.rs/trust-dns" homepage = "http://www.trust-dns.org/index.html" repository = "https://github.com/bluejekyll/trust-dns" # This points to a file in the repository (relative to this Cargo.toml). The # contents of this file are stored and indexed in the registry. readme = "README.md" # This is a small list of keywords used to categorize and search for this # package. keywords = ["DNS", "BIND", "dig", "named", "dnssec"] categories = ["network-programming"] # This is a string description of the license for this package. Currently # crates.io will validate the license provided against a whitelist of known # license identifiers from http://spdx.org/licenses/. Multiple licenses can # be separated with a `/` license = "MIT/Apache-2.0" [badges] #github-actions = { repository = "bluejekyll/trust-dns", branch = "main", workflow = "test" } codecov = { repository = "bluejekyll/trust-dns", branch = "main", service = "github" } maintenance = { status = "actively-developed" } [features] backtrace = ["trust-dns-proto/backtrace"] # TODO: the rustls and openssl crates are not deps... should we change that to make them easier to use? # or change this to also be external? dns-over-https-openssl = ["dns-over-https", "dns-over-openssl"] dns-over-https-rustls = ["dns-over-https", "dns-over-rustls", "trust-dns-proto/dns-over-https-rustls"] dns-over-https = ["trust-dns-proto/dns-over-https"] dns-over-quic = ["dns-over-rustls", "trust-dns-proto/dns-over-quic"] dns-over-native-tls = ["dns-over-tls", "trust-dns-proto/dns-over-native-tls"] dns-over-openssl = ["dns-over-tls", "dnssec-openssl", "openssl"] dns-over-rustls = ["dns-over-tls", "dnssec-ring", "rustls", "webpki", "trust-dns-proto/dns-over-rustls"] dns-over-tls = [] dnssec-openssl = ["dnssec", "openssl", "trust-dns-proto/dnssec-openssl"] dnssec-ring = ["dnssec", "ring", "trust-dns-proto/dnssec-ring"] dnssec = ["trust-dns-proto/dnssec"] serde-config = ["serde"] # enables experimental the mDNS (multicast) feature mdns = ["trust-dns-proto/mdns"] [lib] name = "trust_dns_client" path = "src/lib.rs" [dependencies] cfg-if = "1" data-encoding = "2.2.0" futures-channel = { version = "0.3.5", default-features = false, features = ["std"] } futures-util = { version = "0.3.5", default-features = false, features = ["std"] } lazy_static = "1.2.0" openssl = { version = "0.10", features = ["v102", "v110"], optional = true } radix_trie = "0.2.0" rand = "0.8" ring = { version = "0.16", optional = true, features = ["std"]} rustls = { version = "0.20.0", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } thiserror = "1.0.20" time = "0.3" tracing = "0.1.30" tokio = { version = "1.0", features = ["rt"] } trust-dns-proto = { version = "0.22.0", path = "../proto"} webpki = { version = "0.22.0", optional = true } [dev-dependencies] futures = { version = "0.3.5", default-features = false, features = ["std", "executor"] } openssl = { version = "0.10", features = ["v102", "v110"], optional = false } tokio = { version = "1.0", features = ["rt", "macros"] } tracing-subscriber = { version = "0.3", features = ["std", "fmt", "env-filter"] } [package.metadata.docs.rs] all-features = true default-target = "x86_64-unknown-linux-gnu" targets = ["x86_64-apple-darwin", "x86_64-pc-windows-msvc"] rustdoc-args = ["--cfg", "docsrs"] trust-dns-client-0.22.0/Cargo.toml.orig000064400000000000000000000100111046102023000160040ustar 00000000000000[package] name = "trust-dns-client" version = "0.22.0" authors = ["Benjamin Fry "] edition = "2018" # A short blurb about the package. This is not rendered in any format when # uploaded to crates.io (aka this is not markdown) description = """ Trust-DNS is a safe and secure DNS library. This is the Client library with DNSec support. DNSSec with NSEC validation for negative records, is complete. The client supports dynamic DNS with SIG0 authenticated requests, implementing easy to use high level funtions. Trust-DNS is based on the Tokio and Futures libraries, which means it should be easily integrated into other software that also use those libraries. """ # These URLs point to more information about the repository documentation = "https://docs.rs/trust-dns" homepage = "http://www.trust-dns.org/index.html" repository = "https://github.com/bluejekyll/trust-dns" # This points to a file in the repository (relative to this Cargo.toml). The # contents of this file are stored and indexed in the registry. readme = "README.md" # This is a small list of keywords used to categorize and search for this # package. keywords = ["DNS", "BIND", "dig", "named", "dnssec"] categories = ["network-programming"] # This is a string description of the license for this package. Currently # crates.io will validate the license provided against a whitelist of known # license identifiers from http://spdx.org/licenses/. Multiple licenses can # be separated with a `/` license = "MIT/Apache-2.0" [badges] #github-actions = { repository = "bluejekyll/trust-dns", branch = "main", workflow = "test" } codecov = { repository = "bluejekyll/trust-dns", branch = "main", service = "github" } maintenance = { status = "actively-developed" } [features] backtrace = ["trust-dns-proto/backtrace"] # TODO: the rustls and openssl crates are not deps... should we change that to make them easier to use? # or change this to also be external? dns-over-https-openssl = ["dns-over-https", "dns-over-openssl"] dns-over-https-rustls = ["dns-over-https", "dns-over-rustls", "trust-dns-proto/dns-over-https-rustls"] dns-over-https = ["trust-dns-proto/dns-over-https"] dns-over-quic = ["dns-over-rustls", "trust-dns-proto/dns-over-quic"] dns-over-native-tls = ["dns-over-tls", "trust-dns-proto/dns-over-native-tls"] dns-over-openssl = ["dns-over-tls", "dnssec-openssl", "openssl"] dns-over-rustls = ["dns-over-tls", "dnssec-ring", "rustls", "webpki", "trust-dns-proto/dns-over-rustls"] dns-over-tls = [] dnssec-openssl = ["dnssec", "openssl", "trust-dns-proto/dnssec-openssl"] dnssec-ring = ["dnssec", "ring", "trust-dns-proto/dnssec-ring"] dnssec = ["trust-dns-proto/dnssec"] serde-config = ["serde"] # enables experimental the mDNS (multicast) feature mdns = ["trust-dns-proto/mdns"] [lib] name = "trust_dns_client" path = "src/lib.rs" [dependencies] cfg-if = "1" data-encoding = "2.2.0" futures-channel = { version = "0.3.5", default-features = false, features = ["std"] } futures-util = { version = "0.3.5", default-features = false, features = ["std"] } lazy_static = "1.2.0" openssl = { version = "0.10", features = ["v102", "v110"], optional = true } radix_trie = "0.2.0" rand = "0.8" ring = { version = "0.16", optional = true, features = ["std"]} rustls = { version = "0.20.0", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } thiserror = "1.0.20" time = "0.3" tracing = "0.1.30" tokio = { version = "1.0", features = ["rt"] } trust-dns-proto = { version = "0.22.0", path = "../proto"} webpki = { version = "0.22.0", optional = true } [dev-dependencies] futures = { version = "0.3.5", default-features = false, features = ["std", "executor"] } openssl = { version = "0.10", features = ["v102", "v110"], optional = false } tokio = { version = "1.0", features = ["rt", "macros"] } tracing-subscriber = { version = "0.3", features = ["std", "fmt", "env-filter"] } [package.metadata.docs.rs] all-features = true default-target = "x86_64-unknown-linux-gnu" targets = ["x86_64-apple-darwin", "x86_64-pc-windows-msvc"] rustdoc-args = ["--cfg", "docsrs"] trust-dns-client-0.22.0/LICENSE-APACHE000064400000000000000000000261361046102023000150600ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. trust-dns-client-0.22.0/LICENSE-MIT000064400000000000000000000021131046102023000145550ustar 00000000000000Copyright (c) 2015 The trust-dns Developers Copyright (c) 2017 Google LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. trust-dns-client-0.22.0/README.md000064400000000000000000000131751046102023000144120ustar 00000000000000# Overview Trust-DNS is a library which implements the DNS protocol and client side functions. This library contains basic implementations for DNS record serialization, and communication. It is capable of performing `query`, `update`, and `notify` operations. `update` has been proven to be compatible with `BIND9` and `SIG0` signed records for updates. It is built on top of the [tokio](https://tokio.rs) async-io project, this allows it to be integrated into other systems using the tokio and futures libraries. The Trust-DNS [project](https://github.com/bluejekyll/trust-dns) contains other libraries for DNS: a [resolver library](https://crates.io/crates/trust-dns-resolver) for lookups, a [server library](https://crates.io/crates/trust-dns) for hosting zones, and variations on the TLS implementation over [rustls](https://crates.io/crates/trust-dns-rustls) and [native-tls](https://crates.io/crates/trust-dns-native-tls). ## Features The `client` is capable of DNSSec validation as well as offering higher order functions for performing DNS operations: - [SyncDnssecClient](https://docs.rs/trust-dns/0.11.0/trust_dns/client/struct.SyncDnssecClient.html) - DNSSec validation - [create](https://docs.rs/trust-dns/0.11.0/trust_dns/client/trait.Client.html#method.create) - atomic create of a record, with authenticated request - [append](https://docs.rs/trust-dns/0.11.0/trust_dns/client/trait.Client.html#method.append) - verify existence of a record and append to it - [compare_and_swap](https://docs.rs/trust-dns/0.11.0/trust_dns/client/trait.Client.html#method.compare_and_swap) - atomic (depends on server) compare and swap - [delete_by_rdata](https://docs.rs/trust-dns/0.11.0/trust_dns/client/trait.Client.html#method.delete_by_rdata) - delete a specific record - [delete_rrset](https://docs.rs/trust-dns/0.11.0/trust_dns/client/trait.Client.html#method.delete_rrset) - delete an entire record set - [delete_all](https://docs.rs/trust-dns/0.11.0/trust_dns/client/trait.Client.html#method.delete_all) - delete all records sets with a given name - [notify](https://docs.rs/trust-dns/0.11.0/trust_dns/client/trait.Client.html#method.notify) - notify server that it should reload a zone ## Example ```rust 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::op::DnsResponse; use trust_dns_client::rr::{DNSClass, Name, RData, Record, RecordType}; let address = "8.8.8.8:53".parse().unwrap(); 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("www.example.com.").unwrap(); // NOTE: see 'Setup a connection' example above // Send the query and get a message response, see RecordType for all supported options let response: DnsResponse = client.query(&name, DNSClass::IN, RecordType::A).unwrap(); // Messages are the packets sent between client and server in DNS, DnsResonse's can be // dereferenced to a Message. There are many fields to a Message, It's beyond the scope // of these examples to explain them. See trust_dns::op::message::Message for more details. // generally we will be interested in the Message::answers let answers: &[Record] = response.answers(); // Records are generic objects which can contain any data. // In order to access it we need to first check what type of record it is // In this case we are interested in A, IPv4 address if let Some(RData::A(ref ip)) = answers[0].data() { assert_eq!(*ip, Ipv4Addr::new(93, 184, 216, 34)) } else { assert!(false, "unexpected result") } ``` ## DNS-over-TLS and DNS-over-HTTPS DoT and DoH are supported. This is accomplished through the use of one of `native-tls`, `openssl`, or `rustls` (only `rustls` is currently supported for DoH). To use with the `Client`, the `TlsClientConnection` or `HttpsClientConnection` should be used. Similarly, to use with the tokio `AsyncClient` the `TlsClientStream` or `HttpsClientStream` should be used. ClientAuth, mTLS, is currently not supported, there are some issues still being worked on. TLS is useful for Server authentication and connection privacy. To enable DoT one of the features `dns-over-native-tls`, `dns-over-openssl`, or `dns-over-rustls` must be enabled, `dns-over-https-rustls` is used for DoH. ## DNSSec status Currently the root key is hardcoded into the system. This gives validation of DNSKEY and DS records back to the root. NSEC is implemented, but not NSEC3. Because caching is not yet enabled, it has been noticed that some DNS servers appear to rate limit the connections, validating RRSIG records back to the root can require a significant number of additional queries for those records. Zones will be automatically resigned on any record updates via dynamic DNS. To enable DNSSEC, one of the features `dnssec-openssl` or `dnssec-ring` must be enabled. ## Minimum Rust Version The current minimum rustc version for this project is `1.59` ## Versioning Trust-DNS does it's best job to follow semver. Trust-DNS will be promoted to 1.0 upon stabilization of the publicly exposed APIs. This does not mean that Trust-DNS will necessarily break on upgrades between 0.x updates. Whenever possible, old APIs will be deprecated with notes on what replaced those deprecations. Trust-DNS will make a best effort to never break software which depends on it due to API changes, though this can not be guaranteed. Deprecated interfaces will be maintained for at minimum one major release after that in which they were deprecated (where possible), with the exception of the upgrade to 1.0 where all deprecated interfaces will be planned to be removed. trust-dns-client-0.22.0/src/client/async_client.rs000064400000000000000000001163201046102023000202150ustar 00000000000000// Copyright 2015-2019 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::future::Future; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; use futures_util::ready; use futures_util::stream::{Stream, StreamExt}; use rand; use tracing::debug; use trust_dns_proto::op::Edns; use crate::client::Signer; use crate::error::*; use crate::op::{update_message, Message, MessageType, OpCode, Query}; use crate::proto::error::{ProtoError, ProtoErrorKind}; use crate::proto::xfer::{ BufDnsStreamHandle, DnsClientStream, DnsExchange, DnsExchangeBackground, DnsExchangeSend, DnsHandle, DnsMultiplexer, DnsRequest, DnsRequestOptions, DnsRequestSender, DnsResponse, }; use crate::proto::TokioTime; use crate::rr::rdata::SOA; use crate::rr::{DNSClass, Name, RData, Record, RecordSet, RecordType}; // TODO: this should be configurable // > An EDNS buffer size of 1232 bytes will avoid fragmentation on nearly all current networks. // https://dnsflagday.net/2020/ pub(crate) const MAX_PAYLOAD_LEN: u16 = 1232; /// A DNS Client implemented over futures-rs. /// /// This Client is generic and capable of wrapping UDP, TCP, and other underlying DNS protocol /// implementations. pub type ClientFuture = AsyncClient; /// A DNS Client implemented over futures-rs. /// /// This Client is generic and capable of wrapping UDP, TCP, and other underlying DNS protocol /// implementations. #[derive(Clone)] pub struct AsyncClient { exchange: DnsExchange, use_edns: bool, } impl AsyncClient { /// Spawns a new AsyncClient Stream. This uses a default timeout of 5 seconds for all requests. /// /// # Arguments /// /// * `stream` - A stream of bytes that can be used to send/receive DNS messages /// (see TcpClientStream or UdpClientStream) /// * `stream_handle` - The handle for the `stream` on which bytes can be sent/received. /// * `signer` - An optional signer for requests, needed for Updates with Sig0, otherwise not needed #[allow(clippy::new_ret_no_self)] pub async fn new( stream: F, stream_handle: BufDnsStreamHandle, signer: Option>, ) -> Result< ( Self, DnsExchangeBackground, TokioTime>, ), ProtoError, > where F: Future> + Send + Unpin + 'static, S: DnsClientStream + 'static + Unpin, { Self::with_timeout(stream, stream_handle, Duration::from_secs(5), signer).await } /// Spawns a new AsyncClient Stream. /// /// # Arguments /// /// * `stream` - A stream of bytes that can be used to send/receive DNS messages /// (see TcpClientStream or UdpClientStream) /// * `timeout_duration` - All requests may fail due to lack of response, this is the time to /// wait for a response before canceling the request. /// * `stream_handle` - The handle for the `stream` on which bytes can be sent/received. /// * `signer` - An optional signer for requests, needed for Updates with Sig0, otherwise not needed pub async fn with_timeout( stream: F, stream_handle: BufDnsStreamHandle, timeout_duration: Duration, signer: Option>, ) -> Result< ( Self, DnsExchangeBackground, TokioTime>, ), ProtoError, > where F: Future> + 'static + Send + Unpin, S: DnsClientStream + 'static + Unpin, { let mp = DnsMultiplexer::with_timeout(stream, stream_handle, timeout_duration, signer); Self::connect(mp).await } /// Returns a future, which itself wraps a future which is awaiting connection. /// /// The connect_future should be lazy. /// /// # Returns /// /// This returns a tuple of Self a handle to send dns messages and an optional background. /// The background task must be run on an executor before handle is used, if it is Some. /// If it is None, then another thread has already run the background. pub async fn connect( connect_future: F, ) -> Result<(Self, DnsExchangeBackground), ProtoError> where S: DnsRequestSender, F: Future> + 'static + Send + Unpin, { let result = DnsExchange::connect(connect_future).await; let use_edns = true; result.map(|(exchange, bg)| (Self { exchange, use_edns }, bg)) } /// (Re-)enable usage of EDNS for outgoing messages pub fn enable_edns(&mut self) { self.use_edns = true; } /// Disable usage of EDNS for outgoing messages pub fn disable_edns(&mut self) { self.use_edns = false; } } impl DnsHandle for AsyncClient { type Response = DnsExchangeSend; type Error = ProtoError; fn send + Unpin + Send + 'static>(&mut self, request: R) -> Self::Response { self.exchange.send(request) } fn is_using_edns(&self) -> bool { self.use_edns } } impl ClientHandle for T where T: DnsHandle {} /// A trait for implementing high level functions of DNS. pub trait ClientHandle: 'static + Clone + DnsHandle + Send { /// A *classic* DNS query /// /// *Note* As of now, this will not recurse on PTR or CNAME record responses, that is up to /// the caller. /// /// # Arguments /// /// * `name` - the label to lookup /// * `query_class` - most likely this should always be DNSClass::IN /// * `query_type` - record type to lookup fn query( &mut self, name: Name, query_class: DNSClass, query_type: RecordType, ) -> ClientResponse<::Response> { let mut query = Query::query(name, query_type); query.set_query_class(query_class); let mut options = DnsRequestOptions::default(); options.use_edns = self.is_using_edns(); ClientResponse(self.lookup(query, options)) } /// Sends a NOTIFY message to the remote system /// /// [RFC 1996](https://tools.ietf.org/html/rfc1996), DNS NOTIFY, August 1996 /// /// /// ```text /// 1. Rationale and Scope /// /// 1.1. Slow propagation of new and changed data in a DNS zone can be /// due to a zone's relatively long refresh times. Longer refresh times /// are beneficial in that they reduce load on the Primary Zone Servers, but /// that benefit comes at the cost of long intervals of incoherence among /// authority servers whenever the zone is updated. /// /// 1.2. The DNS NOTIFY transaction allows Primary Zone Servers to inform Secondary /// Zone Servers when the zone has changed -- an interrupt as opposed to poll /// model -- which it is hoped will reduce propagation delay while not /// unduly increasing the masters' load. This specification only allows /// slaves to be notified of SOA RR changes, but the architecture of /// NOTIFY is intended to be extensible to other RR types. /// /// 1.3. This document intentionally gives more definition to the roles /// of "Primary", "Secondary" and "Stealth" servers, their enumeration in NS /// RRs, and the SOA MNAME field. In that sense, this document can be /// considered an addendum to [RFC1035]. /// /// ``` /// /// The below section describes how the Notify message should be constructed. The function /// implementation accepts a Record, but the actual data of the record should be ignored by the /// server, i.e. the server should make a request subsequent to receiving this Notification for /// the authority record, but could be used to decide to request an update or not: /// /// ```text /// 3.7. A NOTIFY request has QDCOUNT>0, ANCOUNT>=0, AUCOUNT>=0, /// ADCOUNT>=0. If ANCOUNT>0, then the answer section represents an /// unsecure hint at the new RRset for this . A /// Secondary receiving such a hint is free to treat equivilence of this /// answer section with its local data as a "no further work needs to be /// done" indication. If ANCOUNT=0, or ANCOUNT>0 and the answer section /// differs from the Secondary's local data, then the Secondary should query its /// known Primaries to retrieve the new data. /// ``` /// /// Client's should be ready to handle, or be aware of, a server response of NOTIMP: /// /// ```text /// 3.12. If a NOTIFY request is received by a Secondary who does not /// implement the NOTIFY opcode, it will respond with a NOTIMP /// (unimplemented feature error) message. A Primary Zone Server who receives /// such a NOTIMP should consider the NOTIFY transaction complete for /// that Secondary. /// ``` /// /// # Arguments /// /// * `name` - the label which is being notified /// * `query_class` - most likely this should always be DNSClass::IN /// * `query_type` - record type which has been updated /// * `rrset` - the new version of the record(s) being notified fn notify( &mut self, name: Name, query_class: DNSClass, query_type: RecordType, rrset: Option, ) -> ClientResponse<::Response> where R: Into, { debug!("notifying: {} {:?}", name, query_type); // build the message let mut message: Message = Message::new(); let id: u16 = rand::random(); message .set_id(id) // 3.3. NOTIFY is similar to QUERY in that it has a request message with // the header QR flag "clear" and a response message with QR "set". The // response message contains no useful information, but its reception by // the Primary is an indication that the Secondary has received the NOTIFY // and that the Primary Zone Server can remove the Secondary from any retry queue for // this NOTIFY event. .set_message_type(MessageType::Query) .set_op_code(OpCode::Notify); // Extended dns if self.is_using_edns() { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } // add the query let mut query: Query = Query::new(); query .set_name(name) .set_query_class(query_class) .set_query_type(query_type); message.add_query(query); // add the notify message, see https://tools.ietf.org/html/rfc1996, section 3.7 if let Some(rrset) = rrset { message.add_answers(rrset.into()); } ClientResponse(self.send(message)) } /// Sends a record to create on the server, this will fail if the record exists (atomicity /// depends on the server) /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.3 - RRset Does Not Exist /// /// No RRs with a specified NAME and TYPE (in the zone and class denoted /// by the Zone Section) can exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the RRset whose nonexistence /// is required. The RDLENGTH of this record is zero (0), and RDATA /// field is therefore empty. CLASS must be specified as NONE in order /// to distinguish this condition from a valid RR whose RDLENGTH is /// naturally zero (0) (for example, the NULL RR). TTL must be specified /// as zero (0). /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary Zone /// Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to create /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection) fn create( &mut self, rrset: R, zone_origin: Name, ) -> ClientResponse<::Response> where R: Into, { let rrset = rrset.into(); let message = update_message::create(rrset, zone_origin, self.is_using_edns()); ClientResponse(self.send(message)) } /// Appends a record to an existing rrset, optionally require the rrset to exist (atomicity /// depends on the server) /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary Zone /// Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to append to an RRSet /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `must_exist` - if true, the request will fail if the record does not exist /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be created. fn append( &mut self, rrset: R, zone_origin: Name, must_exist: bool, ) -> ClientResponse<::Response> where R: Into, { let rrset = rrset.into(); let message = update_message::append(rrset, zone_origin, must_exist, self.is_using_edns()); ClientResponse(self.send(message)) } /// Compares and if it matches, swaps it for the new value (atomicity depends on the server) /// /// ```text /// 2.4.2 - RRset Exists (Value Dependent) /// /// A set of RRs with a specified NAME and TYPE exists and has the same /// members with the same RDATAs as the RRset specified here in this /// section. While RRset ordering is undefined and therefore not /// significant to this comparison, the sets be identical in their /// extent. /// /// For this prerequisite, a requestor adds to the section an entire /// RRset whose preexistence is required. NAME and TYPE are that of the /// RRset being denoted. CLASS is that of the zone. TTL must be /// specified as zero (0) and is ignored when comparing RRsets for /// identity. /// /// 2.5.4 - Delete An RR From An RRset /// /// RRs to be deleted are added to the Update Section. The NAME, TYPE, /// RDLENGTH and RDATA must match the RR being deleted. TTL must be /// specified as zero (0) and will otherwise be ignored by the Primary /// Zone Server. CLASS must be specified as NONE to distinguish this from an /// RR addition. If no such RRs exist, then this Update RR will be /// silently ignored by the Primary Zone Server. /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary /// Zone Server. /// ``` /// /// # Arguments /// /// * `current` - the current rrset which must exist for the swap to complete /// * `new` - the new rrset with which to replace the current rrset /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). fn compare_and_swap( &mut self, current: C, new: N, zone_origin: Name, ) -> ClientResponse<::Response> where C: Into, N: Into, { let current = current.into(); let new = new.into(); let message = update_message::compare_and_swap(current, new, zone_origin, self.is_using_edns()); ClientResponse(self.send(message)) } /// Deletes a record (by rdata) from an rrset, optionally require the rrset to exist. /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.4 - Delete An RR From An RRset /// /// RRs to be deleted are added to the Update Section. The NAME, TYPE, /// RDLENGTH and RDATA must match the RR being deleted. TTL must be /// specified as zero (0) and will otherwise be ignored by the Primary /// Zone Server. CLASS must be specified as NONE to distinguish this from an /// RR addition. If no such RRs exist, then this Update RR will be /// silently ignored by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to delete from a RRSet, the name, type and rdata must match the /// record to delete /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `signer` - the signer, with private key, to use to sign the request /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be deleted. fn delete_by_rdata( &mut self, rrset: R, zone_origin: Name, ) -> ClientResponse<::Response> where R: Into, { let rrset = rrset.into(); let message = update_message::delete_by_rdata(rrset, zone_origin, self.is_using_edns()); ClientResponse(self.send(message)) } /// Deletes an entire rrset, optionally require the rrset to exist. /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.2 - Delete An RRset /// /// One RR is added to the Update Section whose NAME and TYPE are those /// of the RRset to be deleted. TTL must be specified as zero (0) and is /// otherwise not used by the Primary Zone Server. CLASS must be specified as /// ANY. RDLENGTH must be zero (0) and RDATA must therefore be empty. /// If no such RRset exists, then this Update RR will be silently ignored /// by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `record` - The name, class and record_type will be used to match and delete the RecordSet /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be deleted. fn delete_rrset( &mut self, record: Record, zone_origin: Name, ) -> ClientResponse<::Response> { assert!(zone_origin.zone_of(record.name())); let message = update_message::delete_rrset(record, zone_origin, self.is_using_edns()); ClientResponse(self.send(message)) } /// Deletes all records at the specified name /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.5.3 - Delete All RRsets From A Name /// /// One RR is added to the Update Section whose NAME is that of the name /// to be cleansed of RRsets. TYPE must be specified as ANY. TTL must /// be specified as zero (0) and is otherwise not used by the Primary /// Zone Server. CLASS must be specified as ANY. RDLENGTH must be zero (0) /// and RDATA must therefore be empty. If no such RRsets exist, then /// this Update RR will be silently ignored by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `name_of_records` - the name of all the record sets to delete /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `dns_class` - the class of the SOA /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). This /// operation attempts to delete all resource record sets the specified name regardless of /// the record type. fn delete_all( &mut self, name_of_records: Name, zone_origin: Name, dns_class: DNSClass, ) -> ClientResponse<::Response> { assert!(zone_origin.zone_of(&name_of_records)); let message = update_message::delete_all( name_of_records, zone_origin, dns_class, self.is_using_edns(), ); ClientResponse(self.send(message)) } /// Download all records from a zone, or all records modified since given SOA was observed. /// The request will either be a AXFR Query (ask for full zone transfer) if a SOA was not /// provided, or a IXFR Query (incremental zone transfer) if a SOA was provided. /// /// # Arguments /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `last_soa` - the last SOA known, if any. If provided, name must match `zone_origin` fn zone_transfer( &mut self, zone_origin: Name, last_soa: Option, ) -> ClientStreamXfr<::Response> { let ixfr = last_soa.is_some(); let message = update_message::zone_transfer(zone_origin, last_soa); ClientStreamXfr::new(self.send(message), ixfr) } } /// A stream result of a Client Request #[must_use = "stream do nothing unless polled"] pub struct ClientStreamingResponse(pub(crate) R) where R: Stream> + Send + Unpin + 'static; impl Stream for ClientStreamingResponse where R: Stream> + Send + Unpin + 'static, { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Poll::Ready(ready!(self.0.poll_next_unpin(cx)).map(|r| r.map_err(ClientError::from))) } } /// A future result of a Client Request #[must_use = "futures do nothing unless polled"] pub struct ClientResponse(pub(crate) R) where R: Stream> + Send + Unpin + 'static; impl Future for ClientResponse where R: Stream> + Send + Unpin + 'static, { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Poll::Ready( match ready!(self.0.poll_next_unpin(cx)) { Some(r) => r, None => Err(ProtoError::from(ProtoErrorKind::Timeout)), } .map_err(ClientError::from), ) } } /// A stream result of a zone transfer Client Request /// Accept messages until the end of a zone transfer. For AXFR, it search for a starting and an /// ending SOA. For IXFR, it do so taking into account there will be other SOA inbetween #[must_use = "stream do nothing unless polled"] pub struct ClientStreamXfr where R: Stream> + Send + Unpin + 'static, { state: ClientStreamXfrState, } impl ClientStreamXfr where R: Stream> + Send + Unpin + 'static, { fn new(inner: R, maybe_incr: bool) -> Self { Self { state: ClientStreamXfrState::Start { inner, maybe_incr }, } } } /// State machine for ClientStreamXfr, implementing almost all logic #[derive(Debug)] enum ClientStreamXfrState { Start { inner: R, maybe_incr: bool, }, Second { inner: R, expected_serial: u32, maybe_incr: bool, }, Axfr { inner: R, expected_serial: u32, }, Ixfr { inner: R, even: bool, expected_serial: u32, }, Ended, Invalid, } impl ClientStreamXfrState { /// Helper to get the stream from the enum fn inner(&mut self) -> &mut R { use ClientStreamXfrState::*; match self { Start { inner, .. } => inner, Second { inner, .. } => inner, Axfr { inner, .. } => inner, Ixfr { inner, .. } => inner, Ended | Invalid => unreachable!(), } } /// Helper to ingest answer Records // TODO: this is complex enough it should get its own tests fn process(&mut self, answers: &[Record]) -> Result<(), ClientError> { use ClientStreamXfrState::*; fn get_serial(r: &Record) -> Option { r.data().and_then(RData::as_soa).map(SOA::serial) } if answers.is_empty() { return Ok(()); } match std::mem::replace(self, Invalid) { Start { inner, maybe_incr } => { if let Some(expected_serial) = get_serial(&answers[0]) { *self = Second { inner, maybe_incr, expected_serial, }; self.process(&answers[1..]) } else { *self = Ended; Ok(()) } } Second { inner, maybe_incr, expected_serial, } => { if let Some(serial) = get_serial(&answers[0]) { // maybe IXFR, or empty AXFR if serial == expected_serial { // empty AXFR *self = Ended; if answers.len() == 1 { Ok(()) } else { // invalid answer : trailing records Err(ClientErrorKind::Message( "invalid zone transfer, contains trailing records", ) .into()) } } else if maybe_incr { *self = Ixfr { inner, expected_serial, even: true, }; self.process(&answers[1..]) } else { *self = Ended; Err(ClientErrorKind::Message( "invalid zone transfer, expected AXFR, got IXFR", ) .into()) } } else { // standard AXFR *self = Axfr { inner, expected_serial, }; self.process(&answers[1..]) } } Axfr { inner, expected_serial, } => { let soa_count = answers .iter() .filter(|a| a.rr_type() == RecordType::SOA) .count(); match soa_count { 0 => { *self = Axfr { inner, expected_serial, }; Ok(()) } 1 => { *self = Ended; match answers.last().map(|r| r.rr_type()) { Some(RecordType::SOA) => Ok(()), _ => Err(ClientErrorKind::Message( "invalid zone transfer, contains trailing records", ) .into()), } } _ => { *self = Ended; Err(ClientErrorKind::Message( "invalid zone transfer, contains trailing records", ) .into()) } } } Ixfr { inner, even, expected_serial, } => { let even = answers .iter() .fold(even, |even, a| even ^ (a.rr_type() == RecordType::SOA)); if even { if let Some(serial) = get_serial(answers.last().unwrap()) { if serial == expected_serial { *self = Ended; return Ok(()); } } } *self = Ixfr { inner, even, expected_serial, }; Ok(()) } Ended | Invalid => { unreachable!(); } } } } impl Stream for ClientStreamXfr where R: Stream> + Send + Unpin + 'static, { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { use ClientStreamXfrState::*; if matches!(self.state, Ended) { return Poll::Ready(None); } let message = ready!(self.state.inner().poll_next_unpin(cx)).map(|response| { let ok = response?; self.state.process(ok.answers())?; Ok(ok) }); Poll::Ready(message) } } #[cfg(test)] mod tests { use super::*; use crate::rr::rdata::soa::SOA; use futures_util::stream::iter; use ClientStreamXfrState::*; fn soa_record(serial: u32) -> Record { let soa = RData::SOA(SOA::new( Name::from_ascii("example.com.").unwrap(), Name::from_ascii("admin.example.com.").unwrap(), serial, 60, 60, 60, 60, )); Record::from_rdata(Name::from_ascii("example.com.").unwrap(), 600, soa) } fn a_record(ip: u32) -> Record { let a = RData::A(ip.into()); Record::from_rdata(Name::from_ascii("www.example.com.").unwrap(), 600, a) } fn get_stream_testcase( records: Vec>, ) -> impl Stream> + Send + Unpin + 'static { let stream = records.into_iter().map(|r| { Ok({ let mut m = Message::new(); m.insert_answers(r); m } .into()) }); iter(stream) } #[tokio::test] async fn test_stream_xfr_valid_axfr() { let stream = get_stream_testcase(vec![vec![ soa_record(3), a_record(1), a_record(2), soa_record(3), ]]); let mut stream = ClientStreamXfr::new(stream, false); assert!(matches!(stream.state, Start { .. })); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Ended)); assert_eq!(response.answers().len(), 4); assert!(stream.next().await.is_none()); } #[tokio::test] async fn test_stream_xfr_valid_axfr_multipart() { let stream = get_stream_testcase(vec![ vec![soa_record(3)], vec![a_record(1)], vec![soa_record(3)], vec![a_record(2)], // will be ignored as connection is dropped before reading this message ]); let mut stream = ClientStreamXfr::new(stream, false); assert!(matches!(stream.state, Start { .. })); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Second { .. })); assert_eq!(response.answers().len(), 1); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Axfr { .. })); assert_eq!(response.answers().len(), 1); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Ended)); assert_eq!(response.answers().len(), 1); assert!(stream.next().await.is_none()); } #[tokio::test] async fn test_stream_xfr_empty_axfr() { let stream = get_stream_testcase(vec![vec![soa_record(3)], vec![soa_record(3)]]); let mut stream = ClientStreamXfr::new(stream, false); assert!(matches!(stream.state, Start { .. })); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Second { .. })); assert_eq!(response.answers().len(), 1); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Ended)); assert_eq!(response.answers().len(), 1); assert!(stream.next().await.is_none()); } #[tokio::test] async fn test_stream_xfr_axfr_with_ixfr_reply() { let stream = get_stream_testcase(vec![vec![ soa_record(3), soa_record(2), a_record(1), soa_record(3), a_record(2), soa_record(3), ]]); let mut stream = ClientStreamXfr::new(stream, false); assert!(matches!(stream.state, Start { .. })); stream.next().await.unwrap().unwrap_err(); assert!(matches!(stream.state, Ended)); assert!(stream.next().await.is_none()); } #[tokio::test] async fn test_stream_xfr_axfr_with_non_xfr_reply() { let stream = get_stream_testcase(vec![ vec![a_record(1)], // assume this is an error response, not a zone transfer vec![a_record(2)], ]); let mut stream = ClientStreamXfr::new(stream, false); assert!(matches!(stream.state, Start { .. })); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Ended)); assert_eq!(response.answers().len(), 1); assert!(stream.next().await.is_none()); } #[tokio::test] async fn test_stream_xfr_invalid_axfr_multipart() { let stream = get_stream_testcase(vec![ vec![soa_record(3)], vec![a_record(1)], vec![soa_record(3), a_record(2)], vec![soa_record(3)], ]); let mut stream = ClientStreamXfr::new(stream, false); assert!(matches!(stream.state, Start { .. })); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Second { .. })); assert_eq!(response.answers().len(), 1); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Axfr { .. })); assert_eq!(response.answers().len(), 1); stream.next().await.unwrap().unwrap_err(); assert!(matches!(stream.state, Ended)); assert!(stream.next().await.is_none()); } #[tokio::test] async fn test_stream_xfr_valid_ixfr() { let stream = get_stream_testcase(vec![vec![ soa_record(3), soa_record(2), a_record(1), soa_record(3), a_record(2), soa_record(3), ]]); let mut stream = ClientStreamXfr::new(stream, true); assert!(matches!(stream.state, Start { .. })); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Ended)); assert_eq!(response.answers().len(), 6); assert!(stream.next().await.is_none()); } #[tokio::test] async fn test_stream_xfr_valid_ixfr_multipart() { let stream = get_stream_testcase(vec![ vec![soa_record(3)], vec![soa_record(2)], vec![a_record(1)], vec![soa_record(3)], vec![a_record(2)], vec![soa_record(3)], vec![a_record(3)], // ]); let mut stream = ClientStreamXfr::new(stream, true); assert!(matches!(stream.state, Start { .. })); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Second { .. })); assert_eq!(response.answers().len(), 1); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Ixfr { even: true, .. })); assert_eq!(response.answers().len(), 1); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Ixfr { even: true, .. })); assert_eq!(response.answers().len(), 1); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Ixfr { even: false, .. })); assert_eq!(response.answers().len(), 1); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Ixfr { even: false, .. })); assert_eq!(response.answers().len(), 1); let response = stream.next().await.unwrap().unwrap(); assert!(matches!(stream.state, Ended)); assert_eq!(response.answers().len(), 1); assert!(stream.next().await.is_none()); } } trust-dns-client-0.22.0/src/client/async_secure_client.rs000064400000000000000000000075011046102023000215630ustar 00000000000000// Copyright 2015-2019 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. #![cfg(feature = "dnssec")] use std::future::Future; use std::pin::Pin; use futures_util::stream::Stream; use crate::client::AsyncClient; use crate::proto::error::ProtoError; use crate::proto::rr::dnssec::TrustAnchor; use crate::proto::xfer::{ DnsExchangeBackground, DnsHandle, DnsRequest, DnsRequestSender, DnsResponse, }; use crate::proto::DnssecDnsHandle; use crate::proto::TokioTime; /// A DNSSEC Client implemented over futures-rs. /// /// This Client is generic and capable of wrapping UDP, TCP, and other underlying DNS protocol /// implementations. pub struct AsyncDnssecClient { client: DnssecDnsHandle, } impl AsyncDnssecClient { /// Returns a DNSSEC verifying client with a TrustAnchor that can be replaced pub fn builder(connect_future: F) -> AsyncSecureClientBuilder where F: Future> + 'static + Send + Unpin, S: DnsRequestSender + 'static, { AsyncSecureClientBuilder { connect_future, trust_anchor: None, } } /// Returns a DNSSEC verifying client with the default TrustAnchor pub async fn connect( connect_future: F, ) -> Result<(Self, DnsExchangeBackground), ProtoError> where S: DnsRequestSender, F: Future> + 'static + Send + Unpin, { Self::builder(connect_future).build().await } fn from_client(client: AsyncClient, trust_anchor: TrustAnchor) -> Self { Self { client: DnssecDnsHandle::with_trust_anchor(client, trust_anchor), } } } impl Clone for AsyncDnssecClient { fn clone(&self) -> Self { Self { client: self.client.clone(), } } } impl DnsHandle for AsyncDnssecClient { type Response = Pin> + Send + 'static)>>; type Error = ProtoError; fn send + Unpin + Send + 'static>(&mut self, request: R) -> Self::Response { self.client.send(request) } } /// A builder to allow a custom trust to be used for validating all signed records #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub struct AsyncSecureClientBuilder where F: Future> + 'static + Send + Unpin, S: DnsRequestSender + 'static, { connect_future: F, trust_anchor: Option, } #[cfg(feature = "dnssec")] impl AsyncSecureClientBuilder where F: Future> + 'static + Send + Unpin, S: DnsRequestSender + 'static, { /// This variant allows for the trust_anchor to be replaced /// /// # Arguments /// /// * `trust_anchor` - the set of trusted DNSKEY public_keys, by default this only contains the /// root public_key. pub fn trust_anchor(mut self, trust_anchor: TrustAnchor) -> Self { self.trust_anchor = Some(trust_anchor); self } /// Construct the new client pub async fn build( mut self, ) -> Result<(AsyncDnssecClient, DnsExchangeBackground), ProtoError> { let trust_anchor = if let Some(trust_anchor) = self.trust_anchor.take() { trust_anchor } else { TrustAnchor::default() }; let result = AsyncClient::connect(self.connect_future).await; result.map(|(client, bg)| (AsyncDnssecClient::from_client(client, trust_anchor), bg)) } } trust-dns-client-0.22.0/src/client/client.rs000064400000000000000000000567561046102023000170400ustar 00000000000000// Copyright (C) 2015 - 2016 Benjamin Fry // // 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. use std::future::Future; use std::pin::Pin; use std::sync::Arc; use futures_util::stream::{Stream, StreamExt}; use tokio::runtime::{self, Runtime}; use trust_dns_proto::xfer::DnsRequest; use crate::client::async_client::ClientStreamXfr; use crate::client::{AsyncClient, ClientConnection, ClientHandle, Signer}; use crate::error::*; use crate::proto::{ error::ProtoError, xfer::{DnsExchangeSend, DnsHandle, DnsResponse}, }; use crate::rr::rdata::SOA; use crate::rr::{DNSClass, Name, Record, RecordSet, RecordType}; #[cfg(feature = "dnssec")] use { crate::client::AsyncDnssecClient, crate::rr::dnssec::{tsig::TSigner, SigSigner, TrustAnchor}, }; use super::ClientStreamingResponse; #[allow(clippy::type_complexity)] pub(crate) type NewFutureObj = Pin< Box< dyn Future< Output = Result< ( H, Box> + 'static + Send + Unpin>, ), ProtoError, >, > + 'static + Send, >, >; /// Client trait which implements basic DNS Client operations. /// /// As of 0.10.0, the Client is now a wrapper around the `AsyncClient`, which is a futures-rs /// and tokio-rs based implementation. This trait implements synchronous functions for ease of use. /// /// There was a strong attempt to make it backwards compatible, but making it a drop in replacement /// for the old Client was not possible. This trait has two implementations, the `SyncClient` which /// is a standard DNS Client, and the `SyncDnssecClient` which is a wrapper on `DnssecDnsHandle` /// providing DNSSec validation. /// /// *note* When upgrading from previous usage, both `SyncClient` and `SyncDnssecClient` have an /// signer which can be optionally associated to the Client. This replaces the previous per-function /// parameter, and it will sign all update requests (this matches the `AsyncClient` API). #[allow(unreachable_code)] pub trait Client { /// The result stream that will resolve into a DnsResponse type Response: Stream> + 'static + Send + Unpin; /// The AsyncClient type used type Handle: DnsHandle + 'static + Send + Unpin; /// Return the inner Futures items /// /// Consumes the connection and allows for future based operations afterward. fn new_future(&self) -> NewFutureObj; /// This will create a new AsyncClient and spawn it into a new Runtime fn spawn_client(&self) -> ClientResult<(Self::Handle, Runtime)> { let mut builder = runtime::Builder::new_current_thread(); builder.enable_all(); let reactor = builder.build()?; let client = self.new_future(); let (client, bg) = reactor.block_on(client)?; // TODO: should we return this? let _join_bg = reactor.spawn(bg); Ok((client, reactor)) } /// Sends an arbitrary `DnsRequest` to the client fn send + Unpin + Send + 'static>( &self, msg: R, ) -> Vec> { let (mut client, runtime) = match self.spawn_client() { Ok(c_r) => c_r, Err(e) => return vec![Err(e)], }; runtime.block_on(ClientStreamingResponse(client.send(msg)).collect::>()) } /// A *classic* DNS query, i.e. does not perform any DNSSec operations /// /// *Note* As of now, this will not recurse on PTR record responses, that is up to /// the caller. /// /// # Arguments /// /// * `name` - the label to lookup /// * `query_class` - most likely this should always be DNSClass::IN /// * `query_type` - record type to lookup fn query( &self, name: &Name, query_class: DNSClass, query_type: RecordType, ) -> ClientResult { let (mut client, runtime) = self.spawn_client()?; runtime.block_on(client.query(name.clone(), query_class, query_type)) } /// Sends a NOTIFY message to the remote system /// /// # Arguments /// /// * `name` - the label which is being notified /// * `query_class` - most likely this should always be DNSClass::IN /// * `query_type` - record type which has been updated /// * `rrset` - the new version of the record(s) being notified fn notify( &mut self, name: Name, query_class: DNSClass, query_type: RecordType, rrset: Option, ) -> ClientResult where R: Into, { let (mut client, runtime) = self.spawn_client()?; runtime.block_on(client.notify(name, query_class, query_type, rrset)) } /// Sends a record to create on the server, this will fail if the record exists (atomicity /// depends on the server) /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.3 - RRset Does Not Exist /// /// No RRs with a specified NAME and TYPE (in the zone and class denoted /// by the Zone Section) can exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the RRset whose nonexistence /// is required. The RDLENGTH of this record is zero (0), and RDATA /// field is therefore empty. CLASS must be specified as NONE in order /// to distinguish this condition from a valid RR whose RDLENGTH is /// naturally zero (0) (for example, the NULL RR). TTL must be specified /// as zero (0). /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary /// Zone Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to create /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection) fn create(&self, rrset: R, zone_origin: Name) -> ClientResult where R: Into, { let (mut client, runtime) = self.spawn_client()?; runtime.block_on(client.create(rrset, zone_origin)) } /// Appends a record to an existing rrset, optionally require the rrset to exist (atomicity /// depends on the server) /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary /// Zone Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to append to an RRSet /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `must_exist` - if true, the request will fail if the record does not exist /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be created. fn append(&self, rrset: R, zone_origin: Name, must_exist: bool) -> ClientResult where R: Into, { let (mut client, runtime) = self.spawn_client()?; runtime.block_on(client.append(rrset, zone_origin, must_exist)) } /// Compares and if it matches, swaps it for the new value (atomicity depends on the server) /// /// ```text /// 2.4.2 - RRset Exists (Value Dependent) /// /// A set of RRs with a specified NAME and TYPE exists and has the same /// members with the same RDATAs as the RRset specified here in this /// section. While RRset ordering is undefined and therefore not /// significant to this comparison, the sets be identical in their /// extent. /// /// For this prerequisite, a requestor adds to the section an entire /// RRset whose preexistence is required. NAME and TYPE are that of the /// RRset being denoted. CLASS is that of the zone. TTL must be /// specified as zero (0) and is ignored when comparing RRsets for /// identity. /// /// 2.5.4 - Delete An RR From An RRset /// /// RRs to be deleted are added to the Update Section. The NAME, TYPE, /// RDLENGTH and RDATA must match the RR being deleted. TTL must be /// specified as zero (0) and will otherwise be ignored by the Primary /// Zone Server. CLASS must be specified as NONE to distinguish this from an /// RR addition. If no such RRs exist, then this Update RR will be /// silently ignored by the Primary Zone Server. /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary /// Zone Server. /// ``` /// /// # Arguments /// /// * `current` - the current rrset which must exist for the swap to complete /// * `new` - the new rrset with which to replace the current rrset /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). fn compare_and_swap( &self, current: CR, new: NR, zone_origin: Name, ) -> ClientResult where CR: Into, NR: Into, { let (mut client, runtime) = self.spawn_client()?; runtime.block_on(client.compare_and_swap(current, new, zone_origin)) } /// Deletes a record (by rdata) from an rrset, optionally require the rrset to exist. /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.4 - Delete An RR From An RRset /// /// RRs to be deleted are added to the Update Section. The NAME, TYPE, /// RDLENGTH and RDATA must match the RR being deleted. TTL must be /// specified as zero (0) and will otherwise be ignored by the Primary /// Zone Server. CLASS must be specified as NONE to distinguish this from an /// RR addition. If no such RRs exist, then this Update RR will be /// silently ignored by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to delete from a RRSet, the name, type and rdata must match the /// record to delete /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be deleted. fn delete_by_rdata(&self, record: R, zone_origin: Name) -> ClientResult where R: Into, { let (mut client, runtime) = self.spawn_client()?; runtime.block_on(client.delete_by_rdata(record, zone_origin)) } /// Deletes an entire rrset, optionally require the rrset to exist. /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.2 - Delete An RRset /// /// One RR is added to the Update Section whose NAME and TYPE are those /// of the RRset to be deleted. TTL must be specified as zero (0) and is /// otherwise not used by the Primary Zone Sever. CLASS must be specified as /// ANY. RDLENGTH must be zero (0) and RDATA must therefore be empty. /// If no such RRset exists, then this Update RR will be silently ignored /// by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `record` - the record to delete from a RRSet, the name, and type must match the /// record set to delete /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be deleted. fn delete_rrset(&self, record: Record, zone_origin: Name) -> ClientResult { let (mut client, runtime) = self.spawn_client()?; runtime.block_on(client.delete_rrset(record, zone_origin)) } /// Deletes all records at the specified name /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.5.3 - Delete All RRsets From A Name /// /// One RR is added to the Update Section whose NAME is that of the name /// to be cleansed of RRsets. TYPE must be specified as ANY. TTL must /// be specified as zero (0) and is otherwise not used by the Primary /// Zone Server. CLASS must be specified as ANY. RDLENGTH must be zero (0) /// and RDATA must therefore be empty. If no such RRsets exist, then /// this Update RR will be silently ignored by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `name_of_records` - the name of all the record sets to delete /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `dns_class` - the class of the SOA /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). This /// operation attempts to delete all resource record sets the specified name regardless of /// the record type. fn delete_all( &self, name_of_records: Name, zone_origin: Name, dns_class: DNSClass, ) -> ClientResult { let (mut client, runtime) = self.spawn_client()?; runtime.block_on(client.delete_all(name_of_records, zone_origin, dns_class)) } /// Download all records from a zone, or all records modified since given SOA was observed. /// The request will either be a AXFR Query (ask for full zone transfer) if a SOA was not /// provided, or a IXFR Query (incremental zone transfer) if a SOA was provided. /// /// # Arguments /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `last_soa` - the last SOA known, if any. If provided, name must match `zone_origin` fn zone_transfer( &self, name: &Name, last_soa: Option, ) -> ClientResult::Response>>> { let (mut client, runtime) = self.spawn_client()?; Ok(BlockingStream { inner: client.zone_transfer(name.clone(), last_soa), runtime, }) } } /// The Client is abstracted over either trust_dns_client::tcp::TcpClientConnection or /// trust_dns_client::udp::UdpClientConnection. /// /// Usage of TCP or UDP is up to the user. Some DNS servers /// disallow TCP in some cases, so if TCP double check if UDP works. pub struct SyncClient { conn: CC, signer: Option>, } impl SyncClient { /// Creates a new DNS client with the specified connection type /// /// # Arguments /// /// * `conn` - the [`ClientConnection`] to use for all communication pub fn new(conn: CC) -> Self { Self { conn, signer: None } } /// Creates a new DNS client with the specified connection type and a SIG0 signer. /// /// This is necessary for signed update requests to update trust-dns-server entries. /// /// # Arguments /// /// * `conn` - the [`ClientConnection`] to use for all communication /// * `signer` - signer to use, this needs an associated private key #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub fn with_signer(conn: CC, signer: SigSigner) -> Self { Self { conn, signer: Some(Arc::new(signer.into())), } } /// Creates a new DNS client with the specified connection type and TSIG signer. /// /// This is necessary for signed update requests to update certain servers entries. /// /// # Arguments /// /// * `conn` - the [`ClientConnection`] to use for all communication /// * `signer` - signer to use #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub fn with_tsigner(conn: CC, signer: TSigner) -> Self { Self { conn, signer: Some(Arc::new(signer.into())), } } } impl Client for SyncClient { type Response = DnsExchangeSend; type Handle = AsyncClient; fn new_future(&self) -> NewFutureObj { let stream = self.conn.new_stream(self.signer.clone()); let connect = async move { let (client, bg) = AsyncClient::connect(stream).await?; let bg = Box::new(bg) as _; Ok((client, bg)) }; Box::pin(connect) } } /// An iterator based on a `Stream` of dns response. /// Calling `next` on this iterator is a blocking operation. pub struct BlockingStream { inner: T, runtime: Runtime, } impl Iterator for BlockingStream where T: Stream + Unpin, R: Into>, { type Item = ClientResult; fn next(&mut self) -> Option { self.runtime.block_on(self.inner.next()).map(Into::into) } } /// A DNS client which will validate DNSSec records upon receipt #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub struct SyncDnssecClient { conn: CC, signer: Option>, trust_anchor: Option, } #[cfg(feature = "dnssec")] impl SyncDnssecClient { /// Creates a new DNS client with the specified connection type /// /// # Arguments /// /// * `client_connection` - the client_connection to use for all communication #[allow(clippy::new_ret_no_self)] pub fn new(conn: CC) -> SecureSyncClientBuilder { SecureSyncClientBuilder { conn, trust_anchor: None, signer: None, } } } #[cfg(feature = "dnssec")] impl Client for SyncDnssecClient { type Response = Pin> + Send + 'static)>>; type Handle = AsyncDnssecClient; #[allow(clippy::type_complexity)] fn new_future(&self) -> NewFutureObj { let stream = self.conn.new_stream(self.signer.clone()); let mut builder = AsyncDnssecClient::builder(stream); if let Some(trust_anchor) = &self.trust_anchor { builder = builder.trust_anchor(trust_anchor.clone()); } let connect = builder.build(); let connect = async move { let (client, bg) = connect.await?; let bg = Box::new(bg) as _; Ok((client, bg)) }; Box::pin(connect) } } #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub struct SecureSyncClientBuilder { conn: CC, signer: Option>, trust_anchor: Option, } #[cfg(feature = "dnssec")] impl SecureSyncClientBuilder { /// This variant allows for the trust_anchor to be replaced /// /// # Arguments /// /// * `trust_anchor` - the set of trusted DNSKEY public_keys, by default this only contains the /// root public_key. pub fn trust_anchor(mut self, trust_anchor: TrustAnchor) -> Self { self.trust_anchor = Some(trust_anchor); self } /// Associate a signer to produce a SIG0 for all update requests /// /// This is necessary for signed update requests to update trust-dns-server entries /// /// # Arguments /// /// * `signer` - signer to use, this needs an associated private key pub fn signer(mut self, signer: Signer) -> Self { self.signer = Some(Arc::new(signer)); self } pub fn build(self) -> SyncDnssecClient { SyncDnssecClient { conn: self.conn, signer: self.signer, trust_anchor: self.trust_anchor, } } } #[cfg(test)] fn assert_send_and_sync() {} #[test] fn test_sync_client_send_and_sync() { use crate::tcp::TcpClientConnection; use crate::udp::UdpClientConnection; assert_send_and_sync::>(); assert_send_and_sync::>(); } #[test] #[cfg(feature = "dnssec")] fn test_secure_client_send_and_sync() { use crate::tcp::TcpClientConnection; use crate::udp::UdpClientConnection; assert_send_and_sync::>(); assert_send_and_sync::>(); } trust-dns-client-0.22.0/src/client/client_connection.rs000064400000000000000000000057431046102023000212450ustar 00000000000000// Copyright (C) 2016 Benjamin Fry // // 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. //! Trait for client connections use std::future::Future; use std::sync::Arc; use trust_dns_proto::{error::ProtoError, xfer::DnsRequestSender}; use crate::op::{MessageFinalizer, MessageVerifier}; #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] use crate::rr::dnssec::tsig::TSigner; #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] use crate::rr::dnssec::SigSigner; use crate::proto::error::ProtoResult; use crate::proto::op::Message; use crate::proto::rr::Record; /// List of currently supported signers #[allow(missing_copy_implementations)] pub enum Signer { /// A Sig0 based signer #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] Sig0(Box), /// A TSIG based signer #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] TSIG(TSigner), } #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] impl From for Signer { fn from(s: SigSigner) -> Self { Self::Sig0(Box::new(s)) } } #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] impl From for Signer { fn from(s: TSigner) -> Self { Self::TSIG(s) } } impl MessageFinalizer for Signer { #[allow(unreachable_patterns, unused_variables)] fn finalize_message( &self, message: &Message, time: u32, ) -> ProtoResult<(Vec, Option)> { match self { #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] Self::Sig0(s0) => s0.finalize_message(message, time), #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] Self::TSIG(tsig) => tsig.finalize_message(message, time), _ => unreachable!("the feature `dnssec` is required for Message signing"), } } } /// Trait for client connections pub trait ClientConnection: 'static + Sized + Send + Sync + Unpin { /// The associated DNS RequestSender type. type Sender: DnsRequestSender; /// A future that resolves to the RequestSender type SenderFuture: Future> + 'static + Send + Unpin; /// Construct a new stream for use in the Client fn new_stream(&self, signer: Option>) -> Self::SenderFuture; } trust-dns-client-0.22.0/src/client/memoize_client_handle.rs000064400000000000000000000125411046102023000220600ustar 00000000000000// Copyright 2015-2016 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::collections::HashMap; use std::pin::Pin; use std::sync::Arc; use futures_util::future::FutureExt; use futures_util::lock::Mutex; use futures_util::stream::Stream; use trust_dns_proto::{ error::ProtoError, xfer::{DnsHandle, DnsRequest, DnsResponse}, }; use crate::client::rc_stream::{rc_stream, RcStream}; use crate::client::ClientHandle; use crate::op::Query; // TODO: move to proto /// A ClientHandle for memoized (cached) responses to queries. /// /// This wraps a ClientHandle, changing the implementation `send()` to store the response against /// the Message.Query that was sent. This should reduce network traffic especially during things /// like DNSSec validation. *Warning* this will currently cache for the life of the Client. #[derive(Clone)] #[must_use = "queries can only be sent through a ClientHandle"] pub struct MemoizeClientHandle { client: H, active_queries: Arc::Response>>>>, } impl MemoizeClientHandle where H: ClientHandle, { /// Returns a new handle wrapping the specified client pub fn new(client: H) -> Self { Self { client, active_queries: Arc::new(Mutex::new(HashMap::new())), } } async fn inner_send( request: DnsRequest, active_queries: Arc::Response>>>>, mut client: H, ) -> impl Stream> { // TODO: what if we want to support multiple queries (non-standard)? let query = request.queries().first().expect("no query!").clone(); // lock all the currently running queries let mut active_queries = active_queries.lock().await; // TODO: we need to consider TTL on the records here at some point // If the query is running, grab that existing one... if let Some(rc_stream) = active_queries.get(&query) { return rc_stream.clone(); }; // Otherwise issue a new query and store in the map active_queries .entry(query) .or_insert_with(|| rc_stream(client.send(request))) .clone() } } impl DnsHandle for MemoizeClientHandle where H: ClientHandle, { type Response = Pin> + Send>>; type Error = ProtoError; fn send>(&mut self, request: R) -> Self::Response { let request = request.into(); Box::pin( Self::inner_send( request, Arc::clone(&self.active_queries), self.client.clone(), ) .flatten_stream(), ) } } #[cfg(test)] mod test { #![allow(clippy::dbg_macro, clippy::print_stdout)] use std::pin::Pin; use std::sync::Arc; use futures::lock::Mutex; use futures::*; use trust_dns_proto::{ error::ProtoError, xfer::{DnsHandle, DnsRequest, DnsResponse}, }; use crate::client::*; use crate::op::*; use crate::rr::*; use trust_dns_proto::xfer::FirstAnswer; #[derive(Clone)] struct TestClient { i: Arc>, } impl DnsHandle for TestClient { type Response = Pin> + Send>>; type Error = ProtoError; fn send + Send + 'static>(&mut self, request: R) -> Self::Response { let i = Arc::clone(&self.i); let future = async { let i = i; let request = request; let mut message = Message::new(); let mut i = i.lock().await; message.set_id(*i); println!( "sending {}: {}", *i, request.into().queries().first().expect("no query!").clone() ); *i += 1; Ok(message.into()) }; Box::pin(stream::once(future)) } } #[test] fn test_memoized() { use futures::executor::block_on; let mut client = MemoizeClientHandle::new(TestClient { i: Arc::new(Mutex::new(0)), }); let mut test1 = Message::new(); test1.add_query(Query::new().set_query_type(RecordType::A).clone()); let mut test2 = Message::new(); test2.add_query(Query::new().set_query_type(RecordType::AAAA).clone()); let result = block_on(client.send(test1.clone()).first_answer()) .ok() .unwrap(); assert_eq!(result.id(), 0); let result = block_on(client.send(test2.clone()).first_answer()) .ok() .unwrap(); assert_eq!(result.id(), 1); // should get the same result for each... let result = block_on(client.send(test1).first_answer()).ok().unwrap(); assert_eq!(result.id(), 0); let result = block_on(client.send(test2).first_answer()).ok().unwrap(); assert_eq!(result.id(), 1); } } trust-dns-client-0.22.0/src/client/mod.rs000064400000000000000000000030661046102023000163230ustar 00000000000000/* * Copyright (C) 2015-2016 Benjamin Fry * * 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. */ //! DNS Client associated classes for performing queries and other operations. pub(crate) mod async_client; #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub(crate) mod async_secure_client; #[allow(clippy::module_inception)] mod client; pub mod client_connection; mod memoize_client_handle; mod rc_stream; #[allow(deprecated)] pub use self::async_client::{AsyncClient, ClientFuture, ClientHandle, ClientStreamingResponse}; #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub use self::async_secure_client::{AsyncDnssecClient, AsyncSecureClientBuilder}; #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub use self::client::SyncDnssecClient; #[allow(deprecated)] pub use self::client::{BlockingStream, Client, SyncClient}; pub use self::client_connection::ClientConnection; pub use self::client_connection::Signer; pub use self::memoize_client_handle::MemoizeClientHandle; trust-dns-client-0.22.0/src/client/rc_stream.rs000064400000000000000000000070411046102023000175200ustar 00000000000000// Copyright 2015-2016 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; use futures_util::lock::Mutex; use futures_util::stream::{Fuse, Stream, StreamExt}; use futures_util::{ready, FutureExt}; #[allow(clippy::type_complexity)] #[must_use = "stream do nothing unless polled"] pub(crate) struct RcStream where S: Stream + Send + Unpin, S::Item: Clone + Send, { stream_and_result: Arc, Vec)>>, pos: usize, } pub(crate) fn rc_stream(stream: S) -> RcStream where S: Stream + Unpin + Send, S::Item: Clone + Send, { let stream_and_result = Arc::new(Mutex::new((stream.fuse(), vec![]))); RcStream { stream_and_result, pos: 0, } } impl Stream for RcStream where S: Stream + Send + Unpin, S::Item: Clone + Send, { type Item = S::Item; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // try and get a mutable reference to execute the future // at least one caller should be able to get a mut reference... others will // wait for it to complete. let mut stream_and_result = ready!(self.stream_and_result.lock().poll_unpin(cx)); let (ref mut stream, ref mut stored_result) = *stream_and_result; if stored_result.len() > self.pos { let result = stored_result[self.pos].clone(); drop(stream_and_result); // release lock early to please borrow checker self.pos += 1; return Poll::Ready(Some(result)); } // if pending it's either done, or it's actually pending match stream.poll_next_unpin(cx) { Poll::Pending => Poll::Pending, Poll::Ready(result) => { if let Some(ref result) = result { stored_result.push(result.clone()); } Poll::Ready(result) } } } } impl Clone for RcStream where S: Stream + Send + Unpin, S::Item: Clone + Send + Unpin, { fn clone(&self) -> Self { Self { stream_and_result: Arc::clone(&self.stream_and_result), pos: 0, // index is not kept to allow to read first messages } } } #[cfg(test)] mod tests { use futures::executor::block_on; use futures::future; use futures_util::stream::once; use crate::proto::error::{ProtoError, ProtoErrorKind}; use crate::proto::xfer::FirstAnswer; use super::*; #[test] fn test_rc_stream() { let future = future::ok::(1_usize); let rc = rc_stream(once(future)); let i = block_on(rc.clone().first_answer()).ok().unwrap(); assert_eq!(i, 1); let i = block_on(rc.first_answer()).ok().unwrap(); assert_eq!(i, 1); } #[test] fn test_rc_stream_failed() { let future = future::err::(ProtoError::from(ProtoErrorKind::Busy)); let rc = rc_stream(once(future)); let i = block_on(rc.clone().first_answer()).err().unwrap(); assert!(matches!(i.kind(), ProtoErrorKind::Busy)); let i = block_on(rc.first_answer()).err().unwrap(); assert!(matches!(i.kind(), ProtoErrorKind::Busy)); } } trust-dns-client-0.22.0/src/error/client_error.rs000064400000000000000000000111001046102023000200720ustar 00000000000000// Copyright 2015-2020 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Error types for the crate use std::{fmt, io}; use futures_channel::mpsc; use thiserror::Error; use trust_dns_proto::error::{ProtoError, ProtoErrorKind}; use crate::error::{DnsSecError, DnsSecErrorKind}; #[cfg(feature = "backtrace")] use crate::proto::{trace, ExtBacktrace}; /// An alias for results returned by functions of this crate pub type Result = ::std::result::Result; /// The error kind for errors that get returned in the crate #[derive(Debug, Error)] #[non_exhaustive] pub enum ErrorKind { /// An error with an arbitrary message, referenced as &'static str #[error("{0}")] Message(&'static str), /// An error with an arbitrary message, stored as String #[error("{0}")] Msg(String), // foreign /// A dnssec error #[error("dnssec error")] DnsSec(#[from] DnsSecError), /// An error got returned from IO #[error("io error")] Io(#[from] std::io::Error), /// An error got returned by the trust-dns-proto crate #[error("proto error")] Proto(#[from] ProtoError), /// Queue send error #[error("error sending to mpsc: {0}")] SendError(#[from] mpsc::SendError), /// A request timed out #[error("request timed out")] Timeout, } impl Clone for ErrorKind { fn clone(&self) -> Self { use self::ErrorKind::*; match self { Message(msg) => Message(msg), Msg(ref msg) => Msg(msg.clone()), // foreign DnsSec(dnssec) => DnsSec(dnssec.clone()), Io(io) => Io(std::io::Error::from(io.kind())), Proto(proto) => Proto(proto.clone()), SendError(e) => SendError(e.clone()), Timeout => Timeout, } } } /// The error type for errors that get returned in the crate #[derive(Debug, Error, Clone)] pub struct Error { kind: ErrorKind, #[cfg(feature = "backtrace")] backtrack: Option, } impl Error { /// Get the kind of the error pub fn kind(&self) -> &ErrorKind { &self.kind } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { cfg_if::cfg_if! { if #[cfg(feature = "backtrace")] { if let Some(ref backtrace) = self.backtrack { fmt::Display::fmt(&self.kind, f)?; fmt::Debug::fmt(backtrace, f) } else { fmt::Display::fmt(&self.kind, f) } } else { fmt::Display::fmt(&self.kind, f) } } } } impl From for Error { fn from(kind: ErrorKind) -> Self { Self { kind, #[cfg(feature = "backtrace")] backtrack: trace!(), } } } impl From<&'static str> for Error { fn from(msg: &'static str) -> Self { ErrorKind::Message(msg).into() } } impl From for Error { fn from(e: mpsc::SendError) -> Self { ErrorKind::from(e).into() } } impl From for Error { fn from(msg: String) -> Self { ErrorKind::Msg(msg).into() } } impl From for Error { fn from(e: DnsSecError) -> Self { match *e.kind() { DnsSecErrorKind::Timeout => ErrorKind::Timeout.into(), _ => ErrorKind::from(e).into(), } } } impl From for Error { fn from(e: io::Error) -> Self { match e.kind() { io::ErrorKind::TimedOut => ErrorKind::Timeout.into(), _ => ErrorKind::from(e).into(), } } } impl From for Error { fn from(e: ProtoError) -> Self { match *e.kind() { ProtoErrorKind::Timeout => ErrorKind::Timeout.into(), _ => ErrorKind::from(e).into(), } } } impl From for io::Error { fn from(e: Error) -> Self { match *e.kind() { ErrorKind::Timeout => Self::new(io::ErrorKind::TimedOut, e), _ => Self::new(io::ErrorKind::Other, e), } } } #[test] fn test_conversion() { let io_error = io::Error::new(io::ErrorKind::TimedOut, "mock timeout"); let error = Error::from(io_error); match *error.kind() { ErrorKind::Timeout => (), _ => panic!("incorrect type: {}", error), } } trust-dns-client-0.22.0/src/error/dnssec_error.rs000064400000000000000000000130451046102023000201050ustar 00000000000000// Copyright 2015-2020 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Dnssec error types for the crate use std::fmt; #[cfg(not(feature = "openssl"))] use self::not_openssl::SslErrorStack; #[cfg(not(feature = "ring"))] use self::not_ring::{KeyRejected, Unspecified}; #[cfg(feature = "openssl")] use openssl::error::ErrorStack as SslErrorStack; #[cfg(feature = "ring")] use ring::error::{KeyRejected, Unspecified}; use thiserror::Error; use trust_dns_proto::error::{ProtoError, ProtoErrorKind}; #[cfg(feature = "backtrace")] use crate::proto::{trace, ExtBacktrace}; /// An alias for dnssec results returned by functions of this crate pub type Result = ::std::result::Result; /// The error kind for dnssec errors that get returned in the crate #[allow(unreachable_pub)] #[derive(Debug, Error)] #[non_exhaustive] pub enum ErrorKind { /// An error with an arbitrary message, referenced as &'static str #[error("{0}")] Message(&'static str), /// An error with an arbitrary message, stored as String #[error("{0}")] Msg(String), // foreign /// An error got returned by the trust-dns-proto crate #[error("proto error: {0}")] Proto(#[from] ProtoError), /// A ring error #[error("ring error: {0}")] RingKeyRejected(#[from] KeyRejected), /// A ring error #[error("ring error: {0}")] RingUnspecified(#[from] Unspecified), /// An ssl error #[error("ssl error: {0}")] SSL(#[from] SslErrorStack), /// A request timed out #[error("request timed out")] Timeout, } impl Clone for ErrorKind { fn clone(&self) -> Self { use self::ErrorKind::*; match self { Message(msg) => Message(msg), Msg(ref msg) => Msg(msg.clone()), // foreign Proto(proto) => Proto(proto.clone()), RingKeyRejected(r) => Msg(format!("Ring rejected key: {}", r)), RingUnspecified(_r) => RingUnspecified(Unspecified), SSL(ssl) => Msg(format!("SSL had an error: {}", ssl)), Timeout => Timeout, } } } /// The error type for dnssec errors that get returned in the crate #[derive(Debug, Clone, Error)] pub struct Error { kind: ErrorKind, #[cfg(feature = "backtrace")] backtrack: Option, } impl Error { /// Get the kind of the error pub fn kind(&self) -> &ErrorKind { &self.kind } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { cfg_if::cfg_if! { if #[cfg(feature = "backtrace")] { if let Some(ref backtrace) = self.backtrack { fmt::Display::fmt(&self.kind, f)?; fmt::Debug::fmt(backtrace, f) } else { fmt::Display::fmt(&self.kind, f) } } else { fmt::Display::fmt(&self.kind, f) } } } } impl From for Error { fn from(kind: ErrorKind) -> Self { Self { kind, #[cfg(feature = "backtrace")] backtrack: trace!(), } } } impl From<&'static str> for Error { fn from(msg: &'static str) -> Self { ErrorKind::Message(msg).into() } } impl From for Error { fn from(msg: String) -> Self { ErrorKind::Msg(msg).into() } } impl From for Error { fn from(e: ProtoError) -> Self { match *e.kind() { ProtoErrorKind::Timeout => ErrorKind::Timeout.into(), _ => ErrorKind::from(e).into(), } } } impl From for Error { fn from(e: KeyRejected) -> Self { ErrorKind::from(e).into() } } impl From for Error { fn from(e: Unspecified) -> Self { ErrorKind::from(e).into() } } impl From for Error { fn from(e: SslErrorStack) -> Self { ErrorKind::from(e).into() } } #[allow(unreachable_pub)] #[cfg(not(feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "openssl"))))] pub mod not_openssl { use std; #[derive(Clone, Copy, Debug)] pub struct SslErrorStack; impl std::fmt::Display for SslErrorStack { fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { Ok(()) } } impl std::error::Error for SslErrorStack { fn description(&self) -> &str { "openssl feature not enabled" } } } #[allow(unreachable_pub)] #[cfg(not(feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub mod not_ring { use std; #[derive(Clone, Copy, Debug)] pub struct KeyRejected; #[derive(Clone, Copy, Debug)] pub struct Unspecified; impl std::fmt::Display for KeyRejected { fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { Ok(()) } } impl std::error::Error for KeyRejected { fn description(&self) -> &str { "ring feature not enabled" } } impl std::fmt::Display for Unspecified { fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { Ok(()) } } impl std::error::Error for Unspecified { fn description(&self) -> &str { "ring feature not enabled" } } } trust-dns-client-0.22.0/src/error/lexer_error.rs000064400000000000000000000054111046102023000177430ustar 00000000000000// Copyright 2015-2020 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Lexer error types for the crate use std::fmt; use thiserror::Error; #[cfg(feature = "backtrace")] use crate::proto::{trace, ExtBacktrace}; /// An alias for lexer results returned by functions of this crate pub type Result = ::std::result::Result; /// The error kind for lexer errors that get returned in the crate #[derive(Eq, PartialEq, Debug, Error, Clone)] #[non_exhaustive] pub enum ErrorKind { /// Unexpected end of input #[error("unexpected end of input")] EOF, /// An illegal character was found #[error("illegal character input: {0}")] IllegalCharacter(char), /// An illegal state was reached #[error("illegal state: {0}")] IllegalState(&'static str), /// An error with an arbitrary message, referenced as &'static str #[error("{0}")] Message(&'static str), /// An unclosed list was found #[error("unclosed list, missing ')'")] UnclosedList, /// An unclosed quoted string was found #[error("unclosed quoted string")] UnclosedQuotedString, /// An unrecognized character was found #[error("unrecognized character input: {0}")] UnrecognizedChar(char), /// An unrecognized dollar content was found #[error("unrecognized dollar content: {0}")] UnrecognizedDollar(String), /// An unrecognized octet was found #[error("unrecognized octet: {0:x}")] UnrecognizedOctet(u32), } /// The error type for lexer errors that get returned in the crate #[derive(Clone, Error, Debug)] pub struct Error { kind: ErrorKind, #[cfg(feature = "backtrace")] backtrack: Option, } impl Error { /// Get the kind of the error pub fn kind(&self) -> &ErrorKind { &self.kind } } impl From for Error { fn from(kind: ErrorKind) -> Self { Self { kind, #[cfg(feature = "backtrace")] backtrack: trace!(), } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { cfg_if::cfg_if! { if #[cfg(feature = "backtrace")] { if let Some(ref backtrace) = self.backtrack { fmt::Display::fmt(&self.kind, f)?; fmt::Debug::fmt(backtrace, f) } else { fmt::Display::fmt(&self.kind, f) } } else { fmt::Display::fmt(&self.kind, f) } } } } trust-dns-client-0.22.0/src/error/mod.rs000064400000000000000000000025511046102023000161740ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! All defined errors for Trust-DNS #![deny(missing_docs)] mod client_error; mod dnssec_error; mod lexer_error; mod parse_error; pub use self::client_error::Error as ClientError; pub use self::dnssec_error::Error as DnsSecError; pub use self::lexer_error::Error as LexerError; pub use self::parse_error::Error as ParseError; pub use self::client_error::ErrorKind as ClientErrorKind; pub use self::dnssec_error::ErrorKind as DnsSecErrorKind; pub use self::lexer_error::ErrorKind as LexerErrorKind; pub use self::parse_error::ErrorKind as ParseErrorKind; pub use self::client_error::Result as ClientResult; pub use self::dnssec_error::Result as DnsSecResult; pub use self::lexer_error::Result as LexerResult; pub use self::parse_error::Result as ParseResult; trust-dns-client-0.22.0/src/error/parse_error.rs000064400000000000000000000140031046102023000177330ustar 00000000000000// Copyright 2015-2020 Benjamin Fry // Copyright (C) 2017 Google LLC. // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Parse error types for the crate use std::{fmt, io}; use thiserror::Error; use super::LexerError; use crate::proto::{ error::{ProtoError, ProtoErrorKind}, rr::RecordType, }; #[cfg(feature = "backtrace")] use crate::proto::{trace, ExtBacktrace}; use crate::serialize::txt::Token; /// An alias for parse results returned by functions of this crate pub type Result = ::std::result::Result; /// The error kind for parse errors that get returned in the crate #[derive(Debug, Error)] #[non_exhaustive] pub enum ErrorKind { /// An invalid numerical character was found #[error("invalid numerical character: {0}")] CharToInt(char), /// An error with an arbitrary message, referenced as &'static str #[error("{0}")] Message(&'static str), /// A token is missing #[error("token is missing: {0}")] MissingToken(String), /// An error with an arbitrary message, stored as String #[error("{0}")] Msg(String), /// A time string could not be parsed #[error("invalid time string: {0}")] ParseTime(String), /// Found an unexpected token in a stream #[error("unrecognized token in stream: {0:?}")] UnexpectedToken(Token), // foreign /// An address parse error #[error("network address parse error: {0}")] AddrParse(#[from] std::net::AddrParseError), /// A data encoding error #[error("data encoding error: {0}")] DataEncoding(#[from] data_encoding::DecodeError), /// An error got returned from IO #[error("io error: {0}")] Io(#[from] std::io::Error), /// An error from the lexer #[error("lexer error: {0}")] Lexer(#[from] LexerError), /// A number parsing error #[error("error parsing number: {0}")] ParseInt(#[from] std::num::ParseIntError), /// An error got returned by the trust-dns-proto crate #[error("proto error: {0}")] Proto(#[from] ProtoError), /// Unknown RecordType #[error("unknown RecordType: {0}")] UnknownRecordType(u16), /// Unknown RecordType #[error("unsupported RecordType: {0}")] UnsupportedRecordType(RecordType), /// A request timed out #[error("request timed out")] Timeout, } impl Clone for ErrorKind { fn clone(&self) -> Self { use self::ErrorKind::*; match self { CharToInt(c) => CharToInt(*c), Message(msg) => Message(msg), MissingToken(ref s) => MissingToken(s.clone()), Msg(ref msg) => Msg(msg.clone()), ParseTime(ref s) => ParseTime(s.clone()), UnexpectedToken(ref token) => UnexpectedToken(token.clone()), AddrParse(e) => AddrParse(e.clone()), DataEncoding(e) => DataEncoding(*e), Io(e) => Io(std::io::Error::from(e.kind())), Lexer(e) => Lexer(e.clone()), ParseInt(e) => ParseInt(e.clone()), Proto(e) => Proto(e.clone()), UnsupportedRecordType(ty) => UnsupportedRecordType(*ty), UnknownRecordType(ty) => UnknownRecordType(*ty), Timeout => Timeout, } } } /// The error type for parse errors that get returned in the crate #[derive(Error, Debug)] pub struct Error { kind: ErrorKind, #[cfg(feature = "backtrace")] backtrack: Option, } impl Error { /// Get the kind of the error pub fn kind(&self) -> &ErrorKind { &self.kind } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { cfg_if::cfg_if! { if #[cfg(feature = "backtrace")] { if let Some(ref backtrace) = self.backtrack { fmt::Display::fmt(&self.kind, f)?; fmt::Debug::fmt(backtrace, f) } else { fmt::Display::fmt(&self.kind, f) } } else { fmt::Display::fmt(&self.kind, f) } } } } impl From for Error { fn from(kind: ErrorKind) -> Self { Self { kind, #[cfg(feature = "backtrace")] backtrack: trace!(), } } } impl From<&'static str> for Error { fn from(msg: &'static str) -> Self { ErrorKind::Message(msg).into() } } impl From for Error { fn from(msg: String) -> Self { ErrorKind::Msg(msg).into() } } impl From for Error { fn from(e: std::net::AddrParseError) -> Self { ErrorKind::from(e).into() } } impl From<::data_encoding::DecodeError> for Error { fn from(e: data_encoding::DecodeError) -> Self { ErrorKind::from(e).into() } } impl From for Error { fn from(e: io::Error) -> Self { match e.kind() { io::ErrorKind::TimedOut => ErrorKind::Timeout.into(), _ => ErrorKind::from(e).into(), } } } impl From for Error { fn from(e: LexerError) -> Self { ErrorKind::from(e).into() } } impl From for Error { fn from(e: std::num::ParseIntError) -> Self { ErrorKind::from(e).into() } } impl From for Error { fn from(e: ProtoError) -> Self { match *e.kind() { ProtoErrorKind::Timeout => ErrorKind::Timeout.into(), _ => ErrorKind::from(e).into(), } } } impl From for Error { fn from(_e: std::convert::Infallible) -> Self { panic!("infallible") } } impl From for io::Error { fn from(e: Error) -> Self { match *e.kind() { ErrorKind::Timeout => Self::new(io::ErrorKind::TimedOut, e), _ => Self::new(io::ErrorKind::Other, e), } } } trust-dns-client-0.22.0/src/https_client_connection.rs000064400000000000000000000063411046102023000212040ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! UDP based DNS client connection for Client impls use std::marker::PhantomData; use std::net::SocketAddr; use std::sync::Arc; use rustls::ClientConfig; use trust_dns_proto::https::{HttpsClientConnect, HttpsClientStream, HttpsClientStreamBuilder}; use trust_dns_proto::tcp::Connect; use crate::client::{ClientConnection, Signer}; /// UDP based DNS Client connection /// /// Use with `trust_dns_client::client::Client` impls #[derive(Clone)] pub struct HttpsClientConnection { name_server: SocketAddr, bind_addr: Option, dns_name: String, client_config: Arc, marker: PhantomData, } impl HttpsClientConnection { /// Creates a new client connection. /// /// *Note* this has side affects of starting the listening event_loop. Expect this to change in /// the future. /// /// # Arguments /// /// * `name_server` - IP and Port for the remote DNS resolver /// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate /// * `client_config` - The TLS config #[allow(clippy::new_ret_no_self)] pub fn new( name_server: SocketAddr, dns_name: String, client_config: Arc, ) -> Self { Self::new_with_bind_addr(name_server, None, dns_name, client_config) } /// Creates a new client connection with a specified source address. /// /// *Note* this has side affects of starting the listening event_loop. Expect this to change in /// the future. /// /// # Arguments /// /// * `name_server` - IP and Port for the remote DNS resolver /// * `bind_addr` - IP and port to connect from /// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate /// * `client_config` - The TLS config #[allow(clippy::new_ret_no_self)] pub fn new_with_bind_addr( name_server: SocketAddr, bind_addr: Option, dns_name: String, client_config: Arc, ) -> Self { Self { name_server, bind_addr, dns_name, client_config, marker: PhantomData, } } } impl ClientConnection for HttpsClientConnection where T: Connect, { type Sender = HttpsClientStream; type SenderFuture = HttpsClientConnect; fn new_stream( &self, // TODO: maybe signer needs to be applied in https... _signer: Option>, ) -> Self::SenderFuture { // TODO: maybe signer needs to be applied in https... let mut https_builder = HttpsClientStreamBuilder::with_client_config(Arc::clone(&self.client_config)); if let Some(bind_addr) = self.bind_addr { https_builder.bind_addr(bind_addr); } https_builder.build(self.name_server, self.dns_name.clone()) } } trust-dns-client-0.22.0/src/lib.rs000064400000000000000000000312141046102023000150300ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ // LIBRARY WARNINGS #![warn( clippy::default_trait_access, clippy::dbg_macro, clippy::print_stdout, clippy::unimplemented, clippy::use_self, missing_copy_implementations, missing_docs, non_snake_case, non_upper_case_globals, rust_2018_idioms, unreachable_pub )] #![allow( clippy::needless_doctest_main, clippy::single_component_path_imports, clippy::upper_case_acronyms, // can be removed on a major release boundary )] #![recursion_limit = "1024"] #![cfg_attr(docsrs, feature(doc_cfg))] //! Trust-DNS is intended to be a fully compliant domain name server and client library. //! //! The Client library is responsible for the basic protocols responsible for communicating with DNS servers (authorities) and resolvers. It can be used for managing DNS records through the use of update operations. It is possible to send raw DNS Messages with the Client, but for ease of use the `query` and various other update operations are recommended for general use. //! //! For a system-like resolver, see [trust-dns-resolver](https://docs.rs/trust-dns-resolver). This is most likely what you want if all you want to do is lookup IP addresses. //! //! For serving DNS serving, see [trust-dns-server](https://docs.rs/trust-dns-server). //! //! # Goals //! //! * Only safe Rust //! * All errors handled //! * Simple to manage servers //! * High level abstraction for clients //! * Secure dynamic update //! * New features for securing public information //! //! # Usage //! //! This shows basic usage of the SyncClient. More examples will be associated directly with other types. //! //! ## Dependency //! //! ```toml //! [dependencies] //! trust-dns-client = "*" //! ``` //! //! By default DNSSec validation is built in with OpenSSL, this can be disabled with: //! //! ```toml //! [dependencies] //! trust-dns-client = { version = "*", default-features = false } //! ``` //! //! ## Objects //! //! There are two variations of implementations of the Client. The `SyncClient`, a synchronous client, and the `AsyncClient`, a Tokio async client. `SyncClient` is an implementation of the `Client` trait, there is another implementation, `SyncDnssecClient`, which validates DNSSec records. For these basic examples we'll only look at the `SyncClient` //! //! First we must decide on the type of connection, there are three supported by Trust-DNS today, UDP, TCP and TLS. TLS requires OpenSSL by default, see also [trust-dns-native-tls](https://docs.rs/trust-dns-native-tls) and [trust-dns-rustls](https://docs.rs/trust-dns-rustls) for other TLS options. //! //! ## Setup a connection //! //! ```rust //! use trust_dns_proto::DnsStreamHandle; //! use trust_dns_client::client::{Client, ClientConnection, SyncClient}; //! use trust_dns_client::udp::UdpClientConnection; //! //! let address = "8.8.8.8:53".parse().unwrap(); //! let conn = UdpClientConnection::new(address).unwrap(); //! //! // and then create the Client //! let client = SyncClient::new(conn); //! ``` //! //! At this point the client is ready to be used. See also `client::SyncDnssecClient` for DNSSec validation. The rest of these examples will assume that the above boilerplate has already been performed. //! //! ## Querying //! //! Using the Client to query for DNS records is easy enough, though it performs no resolution. The `trust-dns-resolver` has a simpler interface if that's what is desired. Over time that library will gain more features to generically query for different types. //! //! ```rust //! 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::op::DnsResponse; //! use trust_dns_client::rr::{DNSClass, Name, RData, Record, RecordType}; //! # //! # let address = "8.8.8.8:53".parse().unwrap(); //! # 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("www.example.com.").unwrap(); //! //! // NOTE: see 'Setup a connection' example above //! // Send the query and get a message response, see RecordType for all supported options //! let response: DnsResponse = client.query(&name, DNSClass::IN, RecordType::A).unwrap(); //! //! // Messages are the packets sent between client and server in DNS. //! // there are many fields to a Message, DnsResponse can be dereferenced into //! // a Message. It's beyond the scope of these examples //! // to explain all the details of a Message. See trust_dns_client::op::message::Message for more details. //! // generally we will be interested in the Message::answers //! let answers: &[Record] = response.answers(); //! //! // Records are generic objects which can contain any data. //! // In order to access it we need to first check what type of record it is //! // In this case we are interested in A, IPv4 address //! if let Some(RData::A(ref ip)) = answers[0].data() { //! assert_eq!(*ip, Ipv4Addr::new(93, 184, 216, 34)) //! } else { //! assert!(false, "unexpected result") //! } //! ``` //! //! In the above example we successfully queried for a A record. There are many other types, each can be independently queried and the associated `trust_dns_client::rr::record_data::RData` has a variant with the deserialized data for the record stored. //! //! ## Dynamic update //! //! Currently `trust-dns-client` supports SIG(0) signed records for authentication and authorization of dynamic DNS updates. It's beyond the scope of these examples to show how to setup SIG(0) authorization on the server. `trust-dns-client` is known to work with BIND9 and `trust-dns-server`. Expect in the future for TLS to become a potentially better option for authorization with certificate chains. These examples show using SIG(0) for auth, requires OpenSSL. It's beyond the scope of these examples to describe the configuration for the server. //! //! ```rust,no_run //! //! #[cfg(all(feature = "openssl", feature = "dnssec"))] //! # fn main() { //! //! use std::fs::File; //! use std::io::Read; //! use std::net::Ipv4Addr; //! use std::str::FromStr; //! //! use time::Duration; //! //! # #[cfg(feature = "openssl")] //! use openssl::rsa::Rsa; //! use trust_dns_client::client::{Client, SyncClient}; //! use trust_dns_client::udp::UdpClientConnection; //! use trust_dns_client::rr::{Name, RData, Record, RecordType}; //! use trust_dns_client::rr::dnssec::{Algorithm, SigSigner, KeyPair}; //! use trust_dns_client::op::ResponseCode; //! use trust_dns_client::rr::rdata::key::KEY; //! //! # let address = "0.0.0.0:53".parse().unwrap(); //! # let conn = UdpClientConnection::new(address).unwrap(); //! //! // The format of the key is dependent on the KeyPair type, in this example we're using RSA //! // if the key was generated with BIND, the binary in Trust-DNS client lib `dnskey-to-pem` //! // can be used to convert this to a pem file //! let mut pem = File::open("my_private_key.pem").unwrap(); //! let mut pem_buf = Vec::::new(); //! pem.read_to_end(&mut pem_buf).unwrap(); //! //! // Create the RSA key //! let rsa = Rsa::private_key_from_pem(&pem_buf).unwrap(); //! let key = KeyPair::from_rsa(rsa).unwrap(); //! //! // Create the RData KEY associated with the key. This example uses defaults for all the //! // KeyTrust, KeyUsage, UpdateScope, Protocol. Many of these have been deprecated in current //! // DNS RFCs, but are still supported by many servers for auth. See auth docs of the remote //! // server for help in understanding it's requirements and support of these options. //! let sig0key = KEY::new(Default::default(), //! Default::default(), //! Default::default(), //! Default::default(), //! Algorithm::RSASHA256, //! key.to_public_bytes().unwrap()); //! //! // Create the Trust-DNS SIG(0) signing facility. Generally the signer_name is the label //! // associated with KEY record in the server. //! let signer = SigSigner::sig0(sig0key, //! key, //! Name::from_str("update.example.com.").unwrap()); //! //! // Create the DNS client, see above for creating the connection //! let client = SyncClient::with_signer(conn, signer); //! //! // At this point we should have a client capable of sending signed SIG(0) records. //! //! // Now we can send updates... let's create a new Record //! let mut record = Record::with(Name::from_str("new.example.com").unwrap(), //! RecordType::A, //! Duration::minutes(5).whole_seconds() as u32); //! record.set_data(Some(RData::A(Ipv4Addr::new(100, 10, 100, 10)))); //! //! // the server must be authoritative for this zone //! let origin = Name::from_str("example.com.").unwrap(); //! //! // Create the record. //! let result = client.create(record, origin).unwrap(); //! assert_eq!(result.response_code(), ResponseCode::NoError); //! # } //! # #[cfg(not(all(feature = "openssl", feature = "dnssec")))] //! # fn main() { //! # } //! ``` //! //! *Note*: The dynamic DNS functions defined by Trust-DNS are expressed as atomic operations, but this depends on support of the remote server. For example, the `create` operation shown above, should only succeed if there is no `RecordSet` of the specified type at the specified label. The other update operations are `append`, `compare_and_swap`, `delete_by_rdata`, `delete_rrset`, and `delete_all`. See the documentation for each of these methods on the `Client` trait. //! //! //! ## Async client usage //! //! This example is meant to show basic usage, using the #[tokio::main] macro to setup a simple runtime. //! The Tokio documentation should be reviewed for more advanced usage. //! //! ```rust //! use std::net::Ipv4Addr; //! use std::str::FromStr; //! use tokio::net::TcpStream as TokioTcpStream; //! use trust_dns_client::client::{AsyncClient, ClientHandle}; //! use trust_dns_client::proto::iocompat::AsyncIoTokioAsStd; //! use trust_dns_client::rr::{DNSClass, Name, RData, RecordType}; //! use trust_dns_client::tcp::TcpClientStream; //! //! #[tokio::main] //! async fn main() { //! // Since we used UDP in the previous examples, let's change things up a bit and use TCP here //! let (stream, sender) = //! TcpClientStream::>::new(([8, 8, 8, 8], 53).into()); //! //! // Create a new client, the bg is a background future which handles //! // the multiplexing of the DNS requests to the server. //! // the client is a handle to an unbounded queue for sending requests via the //! // background. The background must be scheduled to run before the client can //! // send any dns requests //! let client = AsyncClient::new(stream, sender, None); //! //! // await the connection to be established //! let (mut client, bg) = client.await.expect("connection failed"); //! //! // make sure to run the background task //! tokio::spawn(bg); //! //! // Create a query future //! let query = client.query( //! Name::from_str("www.example.com.").unwrap(), //! DNSClass::IN, //! RecordType::A, //! ); //! //! // wait for its response //! let response = query.await.unwrap(); //! //! // validate it's what we expected //! if let Some(RData::A(addr)) = response.answers()[0].data() { //! assert_eq!(*addr, Ipv4Addr::new(93, 184, 216, 34)); //! } //! } //! ``` pub mod client; pub mod error; #[cfg(feature = "mdns")] #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))] pub mod multicast; pub mod op; pub mod rr; pub mod serialize; pub mod tcp; pub mod udp; // TODO: consider removing tcp/udp/https modules... #[cfg(feature = "dns-over-https")] mod https_client_connection; pub use trust_dns_proto as proto; /// The https module which contains all https related connection types #[cfg(feature = "dns-over-https")] #[cfg_attr(docsrs, doc(cfg(feature = "dns-over-https")))] pub mod https { pub use super::https_client_connection::HttpsClientConnection; } /// Returns a version as specified in Cargo.toml pub fn version() -> &'static str { env!("CARGO_PKG_VERSION") } trust-dns-client-0.22.0/src/multicast/mdns_client_connection.rs000064400000000000000000000041531046102023000230070ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! MDNS based DNS client connection for Client impls use std::net::{Ipv4Addr, SocketAddr}; use std::sync::Arc; use crate::proto::{ multicast::{MdnsClientConnect, MdnsClientStream, MdnsQueryType, MDNS_IPV4, MDNS_IPV6}, xfer::{DnsMultiplexer, DnsMultiplexerConnect}, }; use crate::client::{ClientConnection, Signer}; /// MDNS based DNS Client connection /// /// Use with `trust_dns_client::client::Client` impls #[derive(Clone, Copy)] pub struct MdnsClientConnection { multicast_addr: SocketAddr, packet_ttl: Option, ipv4_if: Option, ipv6_if: Option, } impl MdnsClientConnection { /// associates the socket to the well-known ipv4 multicast address pub fn new_ipv4(packet_ttl: Option, ipv4_if: Option) -> Self { Self { multicast_addr: *MDNS_IPV4, packet_ttl, ipv4_if, ipv6_if: None, } } /// associates the socket to the well-known ipv6 multicast address pub fn new_ipv6(packet_ttl: Option, ipv6_if: Option) -> Self { Self { multicast_addr: *MDNS_IPV6, packet_ttl, ipv4_if: None, ipv6_if, } } } impl ClientConnection for MdnsClientConnection { type Sender = DnsMultiplexer; type SenderFuture = DnsMultiplexerConnect; fn new_stream(&self, signer: Option>) -> Self::SenderFuture { let (mdns_client_stream, handle) = MdnsClientStream::new( self.multicast_addr, MdnsQueryType::OneShot, self.packet_ttl, self.ipv4_if, self.ipv6_if, ); DnsMultiplexer::new(mdns_client_stream, handle, signer) } } trust-dns-client-0.22.0/src/multicast/mod.rs000064400000000000000000000011441046102023000170450ustar 00000000000000// Copyright 2015-2018 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! UDP protocol related components for DNS mod mdns_client_connection; use crate::proto::multicast; pub use self::mdns_client_connection::MdnsClientConnection; pub use self::multicast::{MdnsClientStream, MdnsQueryType, MdnsStream, MDNS_IPV4, MDNS_IPV6}; trust-dns-client-0.22.0/src/op/lower_query.rs000064400000000000000000000057661046102023000172720ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::fmt::{self, Display}; use crate::op::Query; use crate::proto::error::*; use crate::rr::{DNSClass, LowerName, RecordType}; use crate::serialize::binary::*; /// Identical to [crate::op::Query], except that the Name is guaranteed to be in lower case form #[derive(Clone, Debug, PartialEq, Eq)] pub struct LowerQuery { name: LowerName, original: Query, } impl LowerQuery { /// Create a new query from name and type, class defaults to IN pub fn query(query: Query) -> Self { Self { name: LowerName::new(query.name()), original: query, } } /// ```text /// QNAME a domain name represented as a sequence of labels, where /// each label consists of a length octet followed by that /// number of octets. The domain name terminates with the /// zero length octet for the null label of the root. Note /// that this field may be an odd number of octets; no /// padding is used. /// ``` pub fn name(&self) -> &LowerName { &self.name } /// Returns the original with the `Name`s case preserved pub fn original(&self) -> &Query { &self.original } /// ```text /// QTYPE a two octet code which specifies the type of the query. /// The values for this field include all codes valid for a /// TYPE field, together with some more general codes which /// can match more than one type of RR. /// ``` pub fn query_type(&self) -> RecordType { self.original.query_type() } /// ```text /// QCLASS a two octet code that specifies the class of the query. /// For example, the QCLASS field is IN for the Internet. /// ``` pub fn query_class(&self) -> DNSClass { self.original.query_class() } } impl From for LowerQuery { fn from(query: Query) -> Self { Self::query(query) } } impl BinEncodable for LowerQuery { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { self.original.emit(encoder) } } impl<'r> BinDecodable<'r> for LowerQuery { fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult { let original = Query::read(decoder)?; Ok(Self::query(original)) } } impl Display for LowerQuery { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, "name: {} type: {} class: {}", self.name, self.original.query_type(), self.original.query_class() ) } } trust-dns-client-0.22.0/src/op/mod.rs000064400000000000000000000015011046102023000154530ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Operations to send with a `Client` or server, e.g. `Query`, `Message`, or `UpdateMessage` can //! be used together to either query or update resource records sets. mod lower_query; pub mod update_message; pub use self::lower_query::LowerQuery; pub use self::update_message::UpdateMessage; pub use crate::proto::{ op::{ Edns, Header, Message, MessageFinalizer, MessageType, MessageVerifier, OpCode, Query, ResponseCode, }, xfer::DnsResponse, }; trust-dns-client-0.22.0/src/op/update_message.rs000064400000000000000000000513441046102023000176740ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! Update related operations for Messages use std::fmt::Debug; use trust_dns_proto::op::Edns; use crate::client::async_client::MAX_PAYLOAD_LEN; use crate::op::{Message, MessageType, OpCode, Query}; use crate::rr::rdata::{NULL, SOA}; use crate::rr::{DNSClass, Name, RData, Record, RecordSet, RecordType}; /// To reduce errors in using the Message struct as an Update, this will do the call throughs /// to properly do that. /// /// Generally rather than constructing this by hand, see the update methods on `Client` pub trait UpdateMessage: Debug { /// see `Header::id` fn id(&self) -> u16; /// Adds the zone section, i.e. name.example.com would be example.com fn add_zone(&mut self, query: Query); /// Add the pre-requisite records /// /// These must exist, or not, for the Update request to go through. fn add_pre_requisite(&mut self, record: Record); /// Add all the Records from the Iterator to the pre-requisites section fn add_pre_requisites(&mut self, records: R) where R: IntoIterator, I: Iterator; /// Add the Record to be updated fn add_update(&mut self, record: Record); /// Add the Records from the Iterator to the updates section fn add_updates(&mut self, records: R) where R: IntoIterator, I: Iterator; /// Add Records to the additional Section of the UpdateMessage fn add_additional(&mut self, record: Record); /// Returns the Zones to be updated, generally should only be one. fn zones(&self) -> &[Query]; /// Returns the pre-requisites fn prerequisites(&self) -> &[Record]; /// Returns the records to be updated fn updates(&self) -> &[Record]; /// Returns the additional records fn additionals(&self) -> &[Record]; /// This is used to authenticate update messages. /// /// see `Message::sig0()` for more information. fn sig0(&self) -> &[Record]; } /// to reduce errors in using the Message struct as an Update, this will do the call throughs /// to properly do that. impl UpdateMessage for Message { fn id(&self) -> u16 { self.id() } fn add_zone(&mut self, query: Query) { self.add_query(query); } fn add_pre_requisite(&mut self, record: Record) { self.add_answer(record); } fn add_pre_requisites(&mut self, records: R) where R: IntoIterator, I: Iterator, { self.add_answers(records); } fn add_update(&mut self, record: Record) { self.add_name_server(record); } fn add_updates(&mut self, records: R) where R: IntoIterator, I: Iterator, { self.add_name_servers(records); } fn add_additional(&mut self, record: Record) { self.add_additional(record); } fn zones(&self) -> &[Query] { self.queries() } fn prerequisites(&self) -> &[Record] { self.answers() } fn updates(&self) -> &[Record] { self.name_servers() } fn additionals(&self) -> &[Record] { self.additionals() } fn sig0(&self) -> &[Record] { self.sig0() } } /// Sends a record to create on the server, this will fail if the record exists (atomicity /// depends on the server) /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.3 - RRset Does Not Exist /// /// No RRs with a specified NAME and TYPE (in the zone and class denoted /// by the Zone Section) can exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the RRset whose nonexistence /// is required. The RDLENGTH of this record is zero (0), and RDATA /// field is therefore empty. CLASS must be specified as NONE in order /// to distinguish this condition from a valid RR whose RDLENGTH is /// naturally zero (0) (for example, the NULL RR). TTL must be specified /// as zero (0). /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary /// Zone Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to create /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection) pub fn create(rrset: RecordSet, zone_origin: Name, use_edns: bool) -> Message { // TODO: assert non-empty rrset? assert!(zone_origin.zone_of(rrset.name())); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(rrset.dns_class()) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); let mut prerequisite = Record::with(rrset.name().clone(), rrset.record_type(), 0); prerequisite.set_dns_class(DNSClass::NONE); message.add_pre_requisite(prerequisite); message.add_updates(rrset); // Extended dns if use_edns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } /// Appends a record to an existing rrset, optionally require the rrset to exist (atomicity /// depends on the server) /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary /// Zone Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to append to an RRSet /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `must_exist` - if true, the request will fail if the record does not exist /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be created. pub fn append(rrset: RecordSet, zone_origin: Name, must_exist: bool, use_edns: bool) -> Message { assert!(zone_origin.zone_of(rrset.name())); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(rrset.dns_class()) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); if must_exist { let mut prerequisite = Record::with(rrset.name().clone(), rrset.record_type(), 0); prerequisite.set_dns_class(DNSClass::ANY); message.add_pre_requisite(prerequisite); } message.add_updates(rrset); // Extended dns if use_edns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } /// Compares and if it matches, swaps it for the new value (atomicity depends on the server) /// /// ```text /// 2.4.2 - RRset Exists (Value Dependent) /// /// A set of RRs with a specified NAME and TYPE exists and has the same /// members with the same RDATAs as the RRset specified here in this /// section. While RRset ordering is undefined and therefore not /// significant to this comparison, the sets be identical in their /// extent. /// /// For this prerequisite, a requestor adds to the section an entire /// RRset whose preexistence is required. NAME and TYPE are that of the /// RRset being denoted. CLASS is that of the zone. TTL must be /// specified as zero (0) and is ignored when comparing RRsets for /// identity. /// /// 2.5.4 - Delete An RR From An RRset /// /// RRs to be deleted are added to the Update Section. The NAME, TYPE, /// RDLENGTH and RDATA must match the RR being deleted. TTL must be /// specified as zero (0) and will otherwise be ignored by the Primary /// Zone Server. CLASS must be specified as NONE to distinguish this from an /// RR addition. If no such RRs exist, then this Update RR will be /// silently ignored by the Primary Zone Server. /// /// 2.5.1 - Add To An RRset /// /// RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH /// and RDATA are those being added, and CLASS is the same as the zone /// class. Any duplicate RRs will be silently ignored by the Primary /// Zone Server. /// ``` /// /// # Arguments /// /// * `current` - the current rrset which must exist for the swap to complete /// * `new` - the new rrset with which to replace the current rrset /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). pub fn compare_and_swap( current: RecordSet, new: RecordSet, zone_origin: Name, use_edns: bool, ) -> Message { assert!(zone_origin.zone_of(current.name())); assert!(zone_origin.zone_of(new.name())); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(new.dns_class()) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); // make sure the record is what is expected let mut prerequisite = current.clone(); prerequisite.set_ttl(0); message.add_pre_requisites(prerequisite); // add the delete for the old record let mut delete = current; // the class must be none for delete delete.set_dns_class(DNSClass::NONE); // the TTL should be 0 delete.set_ttl(0); message.add_updates(delete); // insert the new record... message.add_updates(new); // Extended dns if use_edns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } /// Deletes a record (by rdata) from an rrset, optionally require the rrset to exist. /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.4 - Delete An RR From An RRset /// /// RRs to be deleted are added to the Update Section. The NAME, TYPE, /// RDLENGTH and RDATA must match the RR being deleted. TTL must be /// specified as zero (0) and will otherwise be ignored by the Primary /// Zone Server. CLASS must be specified as NONE to distinguish this from an /// RR addition. If no such RRs exist, then this Update RR will be /// silently ignored by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `rrset` - the record(s) to delete from a RRSet, the name, type and rdata must match the /// record to delete /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `signer` - the signer, with private key, to use to sign the request /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be deleted. pub fn delete_by_rdata(mut rrset: RecordSet, zone_origin: Name, use_edns: bool) -> Message { assert!(zone_origin.zone_of(rrset.name())); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(rrset.dns_class()) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); // the class must be none for delete rrset.set_dns_class(DNSClass::NONE); // the TTL should be 0 rrset.set_ttl(0); message.add_updates(rrset); // Extended dns if use_edns { message .extensions_mut() .get_or_insert(Edns::new()) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } /// Deletes an entire rrset, optionally require the rrset to exist. /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.4.1 - RRset Exists (Value Independent) /// /// At least one RR with a specified NAME and TYPE (in the zone and class /// specified in the Zone Section) must exist. /// /// For this prerequisite, a requestor adds to the section a single RR /// whose NAME and TYPE are equal to that of the zone RRset whose /// existence is required. RDLENGTH is zero and RDATA is therefore /// empty. CLASS must be specified as ANY to differentiate this /// condition from that of an actual RR whose RDLENGTH is naturally zero /// (0) (e.g., NULL). TTL is specified as zero (0). /// /// 2.5.2 - Delete An RRset /// /// One RR is added to the Update Section whose NAME and TYPE are those /// of the RRset to be deleted. TTL must be specified as zero (0) and is /// otherwise not used by the Primary Zone Server. CLASS must be specified as /// ANY. RDLENGTH must be zero (0) and RDATA must therefore be empty. /// If no such RRset exists, then this Update RR will be silently ignored /// by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `record` - The name, class and record_type will be used to match and delete the RecordSet /// * `zone_origin` - the zone name to update, i.e. SOA name /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). If /// the rrset does not exist and must_exist is false, then the RRSet will be deleted. pub fn delete_rrset(mut record: Record, zone_origin: Name, use_edns: bool) -> Message { assert!(zone_origin.zone_of(record.name())); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(record.dns_class()) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); // the class must be none for an rrset delete record.set_dns_class(DNSClass::ANY); // the TTL should be 0 record.set_ttl(0); // the rdata must be null to delete all rrsets record.set_data(Some(RData::NULL(NULL::new()))); message.add_update(record); // Extended dns if use_edns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } /// Deletes all records at the specified name /// /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997 /// /// ```text /// 2.5.3 - Delete All RRsets From A Name /// /// One RR is added to the Update Section whose NAME is that of the name /// to be cleansed of RRsets. TYPE must be specified as ANY. TTL must /// be specified as zero (0) and is otherwise not used by the Primary /// Zone Server. CLASS must be specified as ANY. RDLENGTH must be zero (0) /// and RDATA must therefore be empty. If no such RRsets exist, then /// this Update RR will be silently ignored by the Primary Zone Server. /// ``` /// /// # Arguments /// /// * `name_of_records` - the name of all the record sets to delete /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `dns_class` - the class of the SOA /// /// The update must go to a zone authority (i.e. the server used in the ClientConnection). This /// operation attempts to delete all resource record sets the specified name regardless of /// the record type. pub fn delete_all( name_of_records: Name, zone_origin: Name, dns_class: DNSClass, use_edns: bool, ) -> Message { assert!(zone_origin.zone_of(&name_of_records)); // for updates, the query section is used for the zone let mut zone: Query = Query::new(); zone.set_name(zone_origin) .set_query_class(dns_class) .set_query_type(RecordType::SOA); // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_op_code(OpCode::Update) .set_recursion_desired(false); message.add_zone(zone); // the TTL should be 0 // the rdata must be null to delete all rrsets // the record type must be any let mut record = Record::with(name_of_records, RecordType::ANY, 0); // the class must be none for an rrset delete record.set_dns_class(DNSClass::ANY); message.add_update(record); // Extended dns if use_edns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } // not an update per-se, but it fits nicely with other functions here /// Download all records from a zone, or all records modified since given SOA was observed. /// The request will either be a AXFR Query (ask for full zone transfer) if a SOA was not /// provided, or a IXFR Query (incremental zone transfer) if a SOA was provided. /// /// # Arguments /// * `zone_origin` - the zone name to update, i.e. SOA name /// * `last_soa` - the last SOA known, if any. If provided, name must match `zone_origin` pub fn zone_transfer(zone_origin: Name, last_soa: Option) -> Message { if let Some(ref soa) = last_soa { assert_eq!(zone_origin, *soa.mname()); } let mut zone: Query = Query::new(); zone.set_name(zone_origin).set_query_class(DNSClass::IN); if last_soa.is_some() { zone.set_query_type(RecordType::IXFR); } else { zone.set_query_type(RecordType::AXFR); } // build the message let mut message: Message = Message::new(); message .set_id(rand::random()) .set_message_type(MessageType::Query) .set_recursion_desired(false); message.add_zone(zone); if let Some(soa) = last_soa { // for IXFR, old SOA is put as authority to indicate last known version let record = Record::from_rdata(soa.mname().clone(), 0, RData::SOA(soa)); message.add_name_server(record); } // Extended dns { message .extensions_mut() .get_or_insert_with(Edns::new) .set_max_payload(MAX_PAYLOAD_LEN) .set_version(0); } message } trust-dns-client-0.22.0/src/rr/dnssec/key_format.rs000064400000000000000000000341001046102023000203210ustar 00000000000000#[cfg(feature = "openssl")] use openssl::ec::EcKey; #[cfg(feature = "openssl")] use openssl::rsa::Rsa; #[cfg(feature = "openssl")] use openssl::symm::Cipher; #[cfg(feature = "ring")] use ring::signature::{ EcdsaKeyPair, Ed25519KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING, }; use crate::error::*; use crate::rr::dnssec::{Algorithm, KeyPair, Private}; /// The format of the binary key #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum KeyFormat { /// A der encoded key Der, /// A pem encoded key, the default of OpenSSL Pem, /// Pkcs8, a pkcs8 formatted private key Pkcs8, } impl KeyFormat { /// Decode private key #[allow(unused, clippy::match_single_binding)] pub fn decode_key( self, bytes: &[u8], password: Option<&str>, algorithm: Algorithm, ) -> DnsSecResult> { // empty string prevents openssl from triggering a read from stdin... let password = password.unwrap_or(""); let password = password.as_bytes(); #[allow(deprecated)] match algorithm { Algorithm::Unknown(v) => Err(format!("unknown algorithm: {}", v).into()), #[cfg(feature = "openssl")] e @ Algorithm::RSASHA1 | e @ Algorithm::RSASHA1NSEC3SHA1 => { Err(format!("unsupported Algorithm (insecure): {:?}", e).into()) } #[cfg(feature = "openssl")] Algorithm::RSASHA256 | Algorithm::RSASHA512 => { let key = match self { Self::Der => Rsa::private_key_from_der(bytes) .map_err(|e| format!("error reading RSA as DER: {}", e))?, Self::Pem => { let key = Rsa::private_key_from_pem_passphrase(bytes, password); key.map_err(|e| { format!("could not decode RSA from PEM, bad password?: {}", e) })? } e => { return Err(format!( "unsupported key format with RSA (DER or PEM only): \ {:?}", e ) .into()) } }; Ok(KeyPair::from_rsa(key) .map_err(|e| format!("could not tranlate RSA to KeyPair: {}", e))?) } Algorithm::ECDSAP256SHA256 | Algorithm::ECDSAP384SHA384 => match self { #[cfg(feature = "openssl")] Self::Der => { let key = EcKey::private_key_from_der(bytes) .map_err(|e| format!("error reading EC as DER: {}", e))?; Ok(KeyPair::from_ec_key(key) .map_err(|e| format!("could not tranlate RSA to KeyPair: {}", e))?) } #[cfg(feature = "openssl")] Self::Pem => { let key = EcKey::private_key_from_pem_passphrase(bytes, password).map_err(|e| { format!("could not decode EC from PEM, bad password?: {}", e) })?; Ok(KeyPair::from_ec_key(key) .map_err(|e| format!("could not tranlate RSA to KeyPair: {}", e))?) } #[cfg(feature = "ring")] Self::Pkcs8 => { let ring_algorithm = if algorithm == Algorithm::ECDSAP256SHA256 { &ECDSA_P256_SHA256_FIXED_SIGNING } else { &ECDSA_P384_SHA384_FIXED_SIGNING }; let key = EcdsaKeyPair::from_pkcs8(ring_algorithm, bytes)?; Ok(KeyPair::from_ecdsa(key)) } e => Err(format!("unsupported key format with EC: {:?}", e).into()), }, Algorithm::ED25519 => match self { #[cfg(feature = "ring")] Self::Pkcs8 => { let key = Ed25519KeyPair::from_pkcs8(bytes)?; Ok(KeyPair::from_ed25519(key)) } e => Err(format!( "unsupported key format with ED25519 (only Pkcs8 supported): {:?}", e ) .into()), }, e => Err(format!( "unsupported Algorithm, enable openssl or ring feature: {:?}", e ) .into()), } } /// Generate a new key and encode to the specified format pub fn generate_and_encode( self, algorithm: Algorithm, password: Option<&str>, ) -> DnsSecResult> { // on encoding, if the password is empty string, ignore it (empty string is ok on decode) #[allow(unused)] let password = password .iter() .filter(|s| !s.is_empty()) .map(|s| s.as_bytes()) .next(); // generate the key #[allow(unused, deprecated)] let key_pair: KeyPair = match algorithm { Algorithm::Unknown(v) => return Err(format!("unknown algorithm: {}", v).into()), #[cfg(feature = "openssl")] e @ Algorithm::RSASHA1 | e @ Algorithm::RSASHA1NSEC3SHA1 => { return Err(format!("unsupported Algorithm (insecure): {:?}", e).into()) } #[cfg(feature = "openssl")] Algorithm::RSASHA256 | Algorithm::RSASHA512 => KeyPair::generate(algorithm)?, Algorithm::ECDSAP256SHA256 | Algorithm::ECDSAP384SHA384 => match self { #[cfg(feature = "openssl")] Self::Der | Self::Pem => KeyPair::generate(algorithm)?, #[cfg(feature = "ring")] Self::Pkcs8 => return KeyPair::generate_pkcs8(algorithm), e => return Err(format!("unsupported key format with EC: {:?}", e).into()), }, #[cfg(feature = "ring")] Algorithm::ED25519 => return KeyPair::generate_pkcs8(algorithm), e => { return Err(format!( "unsupported Algorithm, enable openssl or ring feature: {:?}", e ) .into()) } }; // encode the key #[allow(unreachable_code)] match key_pair { #[cfg(feature = "openssl")] KeyPair::EC(ref pkey) | KeyPair::RSA(ref pkey) => { match self { Self::Der => { // to avoid accidentally storing a key where there was an expectation that it was password protected if password.is_some() { return Err(format!("Can only password protect PEM: {:?}", self).into()); } pkey.private_key_to_der() .map_err(|e| format!("error writing key as DER: {}", e).into()) } Self::Pem => { let key = if let Some(password) = password { pkey.private_key_to_pem_pkcs8_passphrase( Cipher::aes_256_cbc(), password, ) } else { pkey.private_key_to_pem_pkcs8() }; key.map_err(|e| format!("error writing key as PEM: {}", e).into()) } e => Err(format!( "unsupported key format with RSA or EC (DER or PEM \ only): {:?}", e ) .into()), } } #[cfg(feature = "ring")] KeyPair::ECDSA(..) | KeyPair::ED25519(..) => panic!("should have returned early"), #[cfg(not(feature = "openssl"))] KeyPair::Phantom(..) => panic!("Phantom disallowed"), #[cfg(not(any(feature = "openssl", feature = "ring")))] _ => Err(format!( "unsupported Algorithm, enable openssl feature (encode not supported with ring)" ) .into()), } } /// Encode private key #[deprecated] pub fn encode_key( self, key_pair: &KeyPair, password: Option<&str>, ) -> DnsSecResult> { // on encoding, if the password is empty string, ignore it (empty string is ok on decode) #[allow(unused)] let password = password .iter() .filter(|s| !s.is_empty()) .map(|s| s.as_bytes()) .next(); match *key_pair { #[cfg(feature = "openssl")] KeyPair::EC(ref pkey) | KeyPair::RSA(ref pkey) => { match self { Self::Der => { // to avoid accidentally storing a key where there was an expectation that it was password protected if password.is_some() { return Err(format!("Can only password protect PEM: {:?}", self).into()); } pkey.private_key_to_der() .map_err(|e| format!("error writing key as DER: {}", e).into()) } Self::Pem => { let key = if let Some(password) = password { pkey.private_key_to_pem_pkcs8_passphrase( Cipher::aes_256_cbc(), password, ) } else { pkey.private_key_to_pem_pkcs8() }; key.map_err(|e| format!("error writing key as PEM: {}", e).into()) } e => Err(format!( "unsupported key format with RSA or EC (DER or PEM \ only): {:?}", e ) .into()), } } #[cfg(any(feature = "ring", not(feature = "openssl")))] _ => Err( "unsupported Algorithm, enable openssl feature (encode not supported with ring)" .into(), ), } } } #[cfg(test)] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; #[test] #[cfg(feature = "openssl")] fn test_rsa_encode_decode_der() { let algorithm = Algorithm::RSASHA256; encode_decode_with_format(KeyFormat::Der, algorithm, false, true); } #[test] #[cfg(feature = "openssl")] fn test_rsa_encode_decode_pem() { let algorithm = Algorithm::RSASHA256; encode_decode_with_format(KeyFormat::Pem, algorithm, true, true); } #[test] #[cfg(feature = "openssl")] fn test_ec_encode_decode_der() { let algorithm = Algorithm::ECDSAP256SHA256; encode_decode_with_format(KeyFormat::Der, algorithm, false, true); } #[test] #[cfg(feature = "openssl")] fn test_ec_encode_decode_pem() { let algorithm = Algorithm::ECDSAP256SHA256; encode_decode_with_format(KeyFormat::Pem, algorithm, true, true); } #[test] #[cfg(feature = "ring")] fn test_ec_encode_decode_pkcs8() { let algorithm = Algorithm::ECDSAP256SHA256; encode_decode_with_format(KeyFormat::Pkcs8, algorithm, true, true); } #[test] #[cfg(feature = "ring")] fn test_ed25519_encode_decode_pkcs8() { let algorithm = Algorithm::ED25519; encode_decode_with_format(KeyFormat::Pkcs8, algorithm, true, true); } #[cfg(test)] fn encode_decode_with_format( key_format: KeyFormat, algorithm: Algorithm, ok_pass: bool, ok_empty_pass: bool, ) { let password = Some("test password"); let empty_password = Some(""); let no_password = None::<&str>; encode_decode_with_password(key_format, password, password, algorithm, ok_pass, true); encode_decode_with_password( key_format, empty_password, empty_password, algorithm, ok_empty_pass, true, ); encode_decode_with_password( key_format, no_password, no_password, algorithm, ok_empty_pass, true, ); encode_decode_with_password( key_format, no_password, empty_password, algorithm, ok_empty_pass, true, ); encode_decode_with_password( key_format, empty_password, no_password, algorithm, ok_empty_pass, true, ); // TODO: disabled for now... add back in if ring supports passwords on pkcs8 // encode_decode_with_password(key_format, // password, // no_password, // algorithm, // ok_pass, // false); } #[cfg(test)] fn encode_decode_with_password( key_format: KeyFormat, en_pass: Option<&str>, de_pass: Option<&str>, algorithm: Algorithm, encode: bool, decode: bool, ) { println!( "test params: format: {:?}, en_pass: {:?}, de_pass: {:?}, alg: {:?}, encode: {}, decode: {}", key_format, en_pass, de_pass, algorithm, encode, decode ); let encoded_rslt = key_format.generate_and_encode(algorithm, en_pass); if encode { let encoded = encoded_rslt.expect("Encoding error"); let decoded = key_format.decode_key(&encoded, de_pass, algorithm); assert_eq!(decoded.is_ok(), decode); } else { assert!(encoded_rslt.is_err()); } } } trust-dns-client-0.22.0/src/rr/dnssec/keypair.rs000064400000000000000000000576741046102023000176520ustar 00000000000000// Copyright 2015-2016 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. #[cfg(not(feature = "openssl"))] use std::marker::PhantomData; #[cfg(feature = "openssl")] use openssl::bn::BigNumContext; #[cfg(feature = "openssl")] use openssl::ec::{EcGroup, EcKey, PointConversionForm}; #[cfg(feature = "openssl")] use openssl::nid::Nid; #[cfg(feature = "openssl")] use openssl::pkey::PKey; #[cfg(feature = "openssl")] use openssl::rsa::Rsa as OpenSslRsa; #[cfg(feature = "openssl")] use openssl::sign::Signer; #[cfg(feature = "ring")] use ring::{ rand, signature::{ EcdsaKeyPair, Ed25519KeyPair, KeyPair as RingKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING, }, }; #[allow(deprecated)] use trust_dns_proto::rr::dnssec::rdata::key::{KeyTrust, Protocol, UpdateScope}; use crate::error::*; #[cfg(any(feature = "openssl", feature = "ring"))] use crate::rr::dnssec::DigestType; use crate::rr::dnssec::{Algorithm, PublicKeyBuf}; use crate::rr::dnssec::{HasPrivate, HasPublic, Private, TBS}; use crate::rr::rdata::key::KeyUsage; #[cfg(any(feature = "openssl", feature = "ring"))] use crate::rr::rdata::DS; use crate::rr::rdata::{DNSKEY, KEY}; #[cfg(any(feature = "openssl", feature = "ring"))] use crate::rr::Name; /// A public and private key pair, the private portion is not required. /// /// This supports all the various public/private keys which Trust-DNS is capable of using. Given /// differing features, some key types may not be available. The `openssl` feature will enable RSA and EC /// (P256 and P384). The `ring` feature enables ED25519, in the future, Ring will also be used for other keys. #[allow(clippy::large_enum_variant)] pub enum KeyPair { /// RSA keypair, supported by OpenSSL #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] RSA(PKey), /// Elliptic curve keypair, supported by OpenSSL #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] EC(PKey), #[cfg(not(feature = "openssl"))] #[doc(hidden)] Phantom(PhantomData), /// *ring* ECDSA keypair #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] ECDSA(EcdsaKeyPair), /// ED25519 encryption and hash defined keypair #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] ED25519(Ed25519KeyPair), } impl KeyPair { /// Creates an RSA type keypair. #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn from_rsa(rsa: OpenSslRsa) -> DnsSecResult { PKey::from_rsa(rsa).map(Self::RSA).map_err(Into::into) } /// Given a known pkey of an RSA key, return the wrapped keypair #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn from_rsa_pkey(pkey: PKey) -> Self { Self::RSA(pkey) } /// Creates an EC, elliptic curve, type keypair, only P256 or P384 are supported. #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn from_ec_key(ec_key: EcKey) -> DnsSecResult { PKey::from_ec_key(ec_key).map(Self::EC).map_err(Into::into) } /// Given a known pkey of an EC key, return the wrapped keypair #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn from_ec_pkey(pkey: PKey) -> Self { Self::EC(pkey) } /// Creates an ECDSA keypair with ring. #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn from_ecdsa(ec_key: EcdsaKeyPair) -> Self { Self::ECDSA(ec_key) } /// Creates an ED25519 keypair. #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn from_ed25519(ed_key: Ed25519KeyPair) -> Self { Self::ED25519(ed_key) } } impl KeyPair { /// Converts this keypair to the DNS binary form of the public_key. /// /// If there is a private key associated with this keypair, it will not be included in this /// format. Only the public key material will be included. pub fn to_public_bytes(&self) -> DnsSecResult> { #[allow(unreachable_patterns)] match *self { // see from_vec() RSA sections for reference #[cfg(feature = "openssl")] Self::RSA(ref pkey) => { let mut bytes: Vec = Vec::new(); // TODO: make these expects a try! and Err() let rsa: OpenSslRsa = pkey .rsa() .expect("pkey should have been initialized with RSA"); // this is to get us access to the exponent and the modulus let e: Vec = rsa.e().to_vec(); let n: Vec = rsa.n().to_vec(); if e.len() > 255 { bytes.push(0); bytes.push((e.len() >> 8) as u8); } bytes.push(e.len() as u8); bytes.extend_from_slice(&e); bytes.extend_from_slice(&n); Ok(bytes) } // see from_vec() ECDSA sections for reference #[cfg(feature = "openssl")] Self::EC(ref pkey) => { // TODO: make these expects a try! and Err() let ec_key: EcKey = pkey .ec_key() .expect("pkey should have been initialized with EC"); let group = ec_key.group(); let point = ec_key.public_key(); let mut bytes = BigNumContext::new() .and_then(|mut ctx| { point.to_bytes(group, PointConversionForm::UNCOMPRESSED, &mut ctx) }) .map_err(DnsSecError::from)?; // Remove OpenSSL header byte bytes.remove(0); Ok(bytes) } #[cfg(feature = "ring")] Self::ECDSA(ref ec_key) => { let mut bytes: Vec = ec_key.public_key().as_ref().to_vec(); bytes.remove(0); Ok(bytes) } #[cfg(feature = "ring")] Self::ED25519(ref ed_key) => Ok(ed_key.public_key().as_ref().to_vec()), #[cfg(not(feature = "openssl"))] Self::Phantom(..) => panic!("Phantom disallowed"), #[cfg(not(any(feature = "openssl", feature = "ring")))] _ => Err(DnsSecErrorKind::Message("openssl or ring feature(s) not enabled").into()), } } /// Returns a PublicKeyBuf of the KeyPair pub fn to_public_key(&self) -> DnsSecResult { Ok(PublicKeyBuf::new(self.to_public_bytes()?)) } /// The key tag is calculated as a hash to more quickly lookup a DNSKEY. /// /// [RFC 1035](https://tools.ietf.org/html/rfc1035), DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987 /// /// ```text /// RFC 2535 DNS Security Extensions March 1999 /// /// 4.1.6 Key Tag Field /// /// The "key Tag" is a two octet quantity that is used to efficiently /// select between multiple keys which may be applicable and thus check /// that a public key about to be used for the computationally expensive /// effort to check the signature is possibly valid. For algorithm 1 /// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two /// octets of the public key modulus needed to decode the signature /// field. That is to say, the most significant 16 of the least /// significant 24 bits of the modulus in network (big endian) order. For /// all other algorithms, including private algorithms, it is calculated /// as a simple checksum of the KEY RR as described in Appendix C. /// /// Appendix C: Key Tag Calculation /// /// The key tag field in the SIG RR is just a means of more efficiently /// selecting the correct KEY RR to use when there is more than one KEY /// RR candidate available, for example, in verifying a signature. It is /// possible for more than one candidate key to have the same tag, in /// which case each must be tried until one works or all fail. The /// following reference implementation of how to calculate the Key Tag, /// for all algorithms other than algorithm 1, is in ANSI C. It is coded /// for clarity, not efficiency. (See section 4.1.6 for how to determine /// the Key Tag of an algorithm 1 key.) /// /// /* assumes int is at least 16 bits /// first byte of the key tag is the most significant byte of return /// value /// second byte of the key tag is the least significant byte of /// return value /// */ /// /// int keytag ( /// /// unsigned char key[], /* the RDATA part of the KEY RR */ /// unsigned int keysize, /* the RDLENGTH */ /// ) /// { /// long int ac; /* assumed to be 32 bits or larger */ /// /// for ( ac = 0, i = 0; i < keysize; ++i ) /// ac += (i&1) ? key[i] : key[i]<<8; /// ac += (ac>>16) & 0xFFFF; /// return ac & 0xFFFF; /// } /// ``` pub fn key_tag(&self) -> DnsSecResult { let mut ac: usize = 0; for (i, k) in self.to_public_bytes()?.iter().enumerate() { ac += if i & 0x0001 == 0x0001 { *k as usize } else { (*k as usize) << 8 }; } ac += (ac >> 16) & 0xFFFF; Ok((ac & 0xFFFF) as u16) // this is unnecessary, no? } /// Creates a Record that represents the public key for this Signer /// /// # Arguments /// /// * `algorithm` - algorithm of the DNSKEY /// /// # Return /// /// the DNSKEY record data pub fn to_dnskey(&self, algorithm: Algorithm) -> DnsSecResult { self.to_public_bytes() .map(|bytes| DNSKEY::new(true, true, false, algorithm, bytes)) } /// Convert this keypair into a KEY record type for usage with SIG0 /// with key type entity (`KeyUsage::Entity`). /// /// # Arguments /// /// * `algorithm` - algorithm of the KEY /// /// # Return /// /// the KEY record data pub fn to_sig0key(&self, algorithm: Algorithm) -> DnsSecResult { self.to_sig0key_with_usage(algorithm, KeyUsage::default()) } /// Convert this keypair into a KEY record type for usage with SIG0 /// with a given key (usage) type. /// /// # Arguments /// /// * `algorithm` - algorithm of the KEY /// * `usage` - the key type /// /// # Return /// /// the KEY record data pub fn to_sig0key_with_usage( &self, algorithm: Algorithm, usage: KeyUsage, ) -> DnsSecResult { self.to_public_bytes().map(|bytes| { KEY::new( KeyTrust::default(), usage, #[allow(deprecated)] UpdateScope::default(), Protocol::default(), algorithm, bytes, ) }) } /// Creates a DS record for this KeyPair associated to the given name /// /// # Arguments /// /// * `name` - name of the DNSKEY record covered by the new DS record /// * `algorithm` - the algorithm of the DNSKEY /// * `digest_type` - the digest_type used to #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))] pub fn to_ds( &self, name: &Name, algorithm: Algorithm, digest_type: DigestType, ) -> DnsSecResult { self.to_dnskey(algorithm) .and_then(|dnskey| self.key_tag().map(|key_tag| (key_tag, dnskey))) .and_then(|(key_tag, dnskey)| { dnskey .to_digest(name, digest_type) .map(|digest| (key_tag, digest)) .map_err(Into::into) }) .map(|(key_tag, digest)| { DS::new(key_tag, algorithm, digest_type, digest.as_ref().to_owned()) }) } } impl KeyPair { /// Signs a hash. /// /// This will panic if the `key` is not a private key and can be used for signing. /// /// # Arguments /// /// * `message` - the message bytes to be signed, see `rrset_tbs`. /// /// # Return value /// /// The signature, ready to be stored in an `RData::RRSIG`. #[allow(unused)] pub fn sign(&self, algorithm: Algorithm, tbs: &TBS) -> DnsSecResult> { use std::iter; match *self { #[cfg(feature = "openssl")] Self::RSA(ref pkey) | Self::EC(ref pkey) => { let digest_type = DigestType::from(algorithm).to_openssl_digest()?; let mut signer = Signer::new(digest_type, pkey)?; signer.update(tbs.as_ref())?; signer.sign_to_vec().map_err(Into::into).and_then(|bytes| { if let Self::RSA(_) = *self { return Ok(bytes); } // Convert DER signature to raw signature (see RFC 6605 Section 4) if bytes.len() < 8 { return Err("unexpected signature format (length too short)".into()); } let expect = |pos: usize, expected: u8| -> DnsSecResult<()> { if bytes[pos] != expected { return Err(format!( "unexpected signature format ({}, {}))", pos, expected ) .into()); } Ok(()) }; // Sanity checks expect(0, 0x30)?; expect(1, (bytes.len() - 2) as u8)?; expect(2, 0x02)?; let p1_len = bytes[3] as usize; let p2_pos = 4 + p1_len; expect(p2_pos, 0x02)?; let p2_len = bytes[p2_pos + 1] as usize; if p2_pos + 2 + p2_len > bytes.len() { return Err("unexpected signature format (invalid length)".into()); } let p1 = &bytes[4..p2_pos]; let p2 = &bytes[p2_pos + 2..p2_pos + 2 + p2_len]; // For P-256, each integer MUST be encoded as 32 octets; // for P-384, each integer MUST be encoded as 48 octets. let part_len = match algorithm { Algorithm::ECDSAP256SHA256 => 32, Algorithm::ECDSAP384SHA384 => 48, _ => return Err("unexpected algorithm".into()), }; let mut ret = Vec::::new(); { let mut write_part = |mut part: &[u8]| -> DnsSecResult<()> { // We need to pad or trim the octet string to expected length if part.len() > part_len + 1 { return Err("invalid signature data".into()); } if part.len() == part_len + 1 { // Trim leading zero if part[0] != 0x00 { return Err("invalid signature data".into()); } part = &part[1..]; } // Pad with zeros. All numbers are big-endian here. ret.extend(iter::repeat(0x00).take(part_len - part.len())); ret.extend(part); Ok(()) }; write_part(p1)?; write_part(p2)?; } assert_eq!(ret.len(), part_len * 2); Ok(ret) }) } #[cfg(feature = "ring")] Self::ECDSA(ref ec_key) => { let rng = rand::SystemRandom::new(); Ok(ec_key.sign(&rng, tbs.as_ref())?.as_ref().to_vec()) } #[cfg(feature = "ring")] Self::ED25519(ref ed_key) => Ok(ed_key.sign(tbs.as_ref()).as_ref().to_vec()), #[cfg(not(feature = "openssl"))] Self::Phantom(..) => panic!("Phantom disallowed"), #[cfg(not(any(feature = "openssl", feature = "ring")))] _ => Err(DnsSecErrorKind::Message("openssl nor ring feature(s) not enabled").into()), } } } impl KeyPair { /// Generates a new private and public key pair for the specified algorithm. /// /// RSA keys are hardcoded to 2048bits at the moment. Other keys have predefined sizes. pub fn generate(algorithm: Algorithm) -> DnsSecResult { #[allow(deprecated)] match algorithm { Algorithm::Unknown(_) => Err(DnsSecErrorKind::Message("unknown algorithm").into()), #[cfg(feature = "openssl")] Algorithm::RSASHA1 | Algorithm::RSASHA1NSEC3SHA1 | Algorithm::RSASHA256 | Algorithm::RSASHA512 => { // TODO: the only keysize right now, would be better for people to use other algorithms... OpenSslRsa::generate(2048) .map_err(Into::into) .and_then(Self::from_rsa) } #[cfg(feature = "openssl")] Algorithm::ECDSAP256SHA256 => EcGroup::from_curve_name(Nid::X9_62_PRIME256V1) .and_then(|group| EcKey::generate(&group)) .map_err(Into::into) .and_then(Self::from_ec_key), #[cfg(feature = "openssl")] Algorithm::ECDSAP384SHA384 => EcGroup::from_curve_name(Nid::SECP384R1) .and_then(|group| EcKey::generate(&group)) .map_err(Into::into) .and_then(Self::from_ec_key), #[cfg(feature = "ring")] Algorithm::ED25519 => Err(DnsSecErrorKind::Message( "use generate_pkcs8 for generating private key and encoding", ) .into()), _ => Err(DnsSecErrorKind::Message("openssl nor ring feature(s) not enabled").into()), } } /// Generates a key, securing it with pkcs8 #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub fn generate_pkcs8(algorithm: Algorithm) -> DnsSecResult> { #[allow(deprecated)] match algorithm { Algorithm::Unknown(_) => Err(DnsSecErrorKind::Message("unknown algorithm").into()), #[cfg(feature = "openssl")] Algorithm::RSASHA1 | Algorithm::RSASHA1NSEC3SHA1 | Algorithm::RSASHA256 | Algorithm::RSASHA512 => { Err(DnsSecErrorKind::Message("openssl does not yet support pkcs8").into()) } #[cfg(feature = "ring")] Algorithm::ECDSAP256SHA256 => { let rng = rand::SystemRandom::new(); EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &rng) .map_err(Into::into) .map(|pkcs8_bytes| pkcs8_bytes.as_ref().to_vec()) } #[cfg(feature = "ring")] Algorithm::ECDSAP384SHA384 => { let rng = rand::SystemRandom::new(); EcdsaKeyPair::generate_pkcs8(&ECDSA_P384_SHA384_FIXED_SIGNING, &rng) .map_err(Into::into) .map(|pkcs8_bytes| pkcs8_bytes.as_ref().to_vec()) } #[cfg(feature = "ring")] Algorithm::ED25519 => { let rng = rand::SystemRandom::new(); Ed25519KeyPair::generate_pkcs8(&rng) .map_err(Into::into) .map(|pkcs8_bytes| pkcs8_bytes.as_ref().to_vec()) } _ => Err(DnsSecErrorKind::Message("openssl nor ring feature(s) not enabled").into()), } } } #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg(test)] mod tests { use crate::rr::dnssec::TBS; use crate::rr::dnssec::*; #[cfg(feature = "openssl")] #[test] fn test_rsa() { public_key_test(Algorithm::RSASHA256, KeyFormat::Der); hash_test(Algorithm::RSASHA256, KeyFormat::Der); } #[cfg(feature = "openssl")] #[test] fn test_ec_p256() { public_key_test(Algorithm::ECDSAP256SHA256, KeyFormat::Der); hash_test(Algorithm::ECDSAP256SHA256, KeyFormat::Der); } #[cfg(feature = "ring")] #[test] fn test_ec_p256_pkcs8() { public_key_test(Algorithm::ECDSAP256SHA256, KeyFormat::Pkcs8); hash_test(Algorithm::ECDSAP256SHA256, KeyFormat::Pkcs8); } #[cfg(feature = "openssl")] #[test] fn test_ec_p384() { public_key_test(Algorithm::ECDSAP384SHA384, KeyFormat::Der); hash_test(Algorithm::ECDSAP384SHA384, KeyFormat::Der); } #[cfg(feature = "ring")] #[test] fn test_ec_p384_pkcs8() { public_key_test(Algorithm::ECDSAP384SHA384, KeyFormat::Pkcs8); hash_test(Algorithm::ECDSAP384SHA384, KeyFormat::Pkcs8); } #[cfg(feature = "ring")] #[test] fn test_ed25519() { public_key_test(Algorithm::ED25519, KeyFormat::Pkcs8); hash_test(Algorithm::ED25519, KeyFormat::Pkcs8); } fn public_key_test(algorithm: Algorithm, key_format: KeyFormat) { let key = key_format .decode_key( &key_format.generate_and_encode(algorithm, None).unwrap(), None, algorithm, ) .unwrap(); let pk = key.to_public_key().unwrap(); let tbs = TBS::from(&b"www.example.com"[..]); let mut sig = key.sign(algorithm, &tbs).unwrap(); assert!( pk.verify(algorithm, tbs.as_ref(), &sig).is_ok(), "algorithm: {:?} (public key)", algorithm ); sig[10] = !sig[10]; assert!( pk.verify(algorithm, tbs.as_ref(), &sig).is_err(), "algorithm: {:?} (public key, neg)", algorithm ); } fn hash_test(algorithm: Algorithm, key_format: KeyFormat) { let tbs = TBS::from(&b"www.example.com"[..]); // TODO: convert to stored keys... let key = key_format .decode_key( &key_format.generate_and_encode(algorithm, None).unwrap(), None, algorithm, ) .unwrap(); let pub_key = key.to_public_key().unwrap(); let neg = key_format .decode_key( &key_format.generate_and_encode(algorithm, None).unwrap(), None, algorithm, ) .unwrap(); let neg_pub_key = neg.to_public_key().unwrap(); let sig = key.sign(algorithm, &tbs).unwrap(); assert!( pub_key.verify(algorithm, tbs.as_ref(), &sig).is_ok(), "algorithm: {:?}", algorithm ); assert!( key.to_dnskey(algorithm) .unwrap() .verify(tbs.as_ref(), &sig) .is_ok(), "algorithm: {:?} (dnskey)", algorithm ); assert!( neg_pub_key.verify(algorithm, tbs.as_ref(), &sig).is_err(), "algorithm: {:?} (neg)", algorithm ); assert!( neg.to_dnskey(algorithm) .unwrap() .verify(tbs.as_ref(), &sig) .is_err(), "algorithm: {:?} (dnskey, neg)", algorithm ); } } trust-dns-client-0.22.0/src/rr/dnssec/mod.rs000064400000000000000000000052721046102023000167500ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! dns security extension related modules #[cfg(any(feature = "openssl", feature = "ring"))] mod key_format; mod keypair; mod signer; pub mod tsig; use crate::proto::rr::dnssec; pub use self::dnssec::tbs; pub use self::dnssec::Algorithm; pub use self::dnssec::DigestType; pub use self::dnssec::Nsec3HashAlgorithm; pub use self::dnssec::PublicKey; pub use self::dnssec::PublicKeyBuf; pub use self::dnssec::PublicKeyEnum; pub use self::dnssec::SupportedAlgorithms; pub use self::dnssec::TrustAnchor; pub use self::dnssec::Verifier; pub use self::dnssec::TBS; #[cfg(any(feature = "openssl", feature = "ring"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "ring"))))] pub use self::key_format::KeyFormat; pub use self::keypair::KeyPair; #[allow(deprecated)] pub use self::signer::{SigSigner, Signer}; pub use crate::error::DnsSecError; pub use crate::error::DnsSecErrorKind; pub use crate::error::DnsSecResult; #[cfg(all(not(feature = "ring"), feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(all(not(feature = "ring"), feature = "openssl"))))] pub use openssl::hash::DigestBytes as Digest; #[cfg(feature = "ring")] #[cfg_attr(docsrs, doc(cfg(feature = "ring")))] pub use ring::digest::Digest; #[cfg(feature = "openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub use openssl::pkey::{HasPrivate, HasPublic, Private, Public}; #[cfg(not(feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "openssl"))))] pub use self::faux_key_type::{HasPrivate, HasPublic, Private, Public}; #[cfg(not(feature = "openssl"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "openssl"))))] mod faux_key_type { /// A key that contains public key material pub trait HasPublic {} /// A key that contains private key material pub trait HasPrivate {} impl HasPublic for K {} /// Faux implementation of the Openssl Public key types #[derive(Clone, Copy)] pub enum Public {} impl HasPublic for Public {} /// Faux implementation of the Openssl Public key types #[derive(Clone, Copy)] pub enum Private {} impl HasPrivate for Private {} } trust-dns-client-0.22.0/src/rr/dnssec/signer.rs000064400000000000000000001074531046102023000174640ustar 00000000000000// Copyright 2015-2019 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! signer is a structure for performing many of the signing processes of the DNSSec specification use tracing::debug; #[cfg(feature = "dnssec")] use std::time::Duration; use crate::op::{Message, MessageFinalizer, MessageVerifier}; use crate::proto::error::{ProtoErrorKind, ProtoResult}; use crate::rr::Record; #[cfg(feature = "dnssec")] use { crate::error::DnsSecResult, crate::proto::rr::dnssec::{tbs, TBS}, crate::rr::dnssec::{Algorithm, KeyPair, Private}, crate::rr::rdata::{DNSSECRData, DNSKEY, KEY, SIG}, crate::rr::{DNSClass, Name, RData, RecordType}, crate::serialize::binary::BinEncoder, }; /// Use for performing signing and validation of DNSSec based components. The SigSigner can be used for singing requests and responses with SIG0, or DNSSEC RRSIG records. The format is based on the SIG record type. /// /// TODO: warning this struct and it's impl are under high volatility, expect breaking changes /// /// [RFC 4035](https://tools.ietf.org/html/rfc4035), DNSSEC Protocol Modifications, March 2005 /// /// ```text /// 5.3. Authenticating an RRset with an RRSIG RR /// /// A validator can use an RRSIG RR and its corresponding DNSKEY RR to /// attempt to authenticate RRsets. The validator first checks the RRSIG /// RR to verify that it covers the RRset, has a valid time interval, and /// identifies a valid DNSKEY RR. The validator then constructs the /// canonical form of the signed data by appending the RRSIG RDATA /// (excluding the Signature Field) with the canonical form of the /// covered RRset. Finally, the validator uses the public key and /// signature to authenticate the signed data. Sections 5.3.1, 5.3.2, /// and 5.3.3 describe each step in detail. /// /// 5.3.1. Checking the RRSIG RR Validity /// /// A security-aware resolver can use an RRSIG RR to authenticate an /// RRset if all of the following conditions hold: /// /// o The RRSIG RR and the RRset MUST have the same owner name and the /// same class. /// /// o The RRSIG RR's Signer's Name field MUST be the name of the zone /// that contains the RRset. /// /// o The RRSIG RR's Type Covered field MUST equal the RRset's type. /// /// o The number of labels in the RRset owner name MUST be greater than /// or equal to the value in the RRSIG RR's Labels field. /// /// o The validator's notion of the current time MUST be less than or /// equal to the time listed in the RRSIG RR's Expiration field. /// /// o The validator's notion of the current time MUST be greater than or /// equal to the time listed in the RRSIG RR's Inception field. /// /// o The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST /// match the owner name, algorithm, and key tag for some DNSKEY RR in /// the zone's apex DNSKEY RRset. /// /// o The matching DNSKEY RR MUST be present in the zone's apex DNSKEY /// RRset, and MUST have the Zone Flag bit (DNSKEY RDATA Flag bit 7) /// set. /// /// It is possible for more than one DNSKEY RR to match the conditions /// above. In this case, the validator cannot predetermine which DNSKEY /// RR to use to authenticate the signature, and it MUST try each /// matching DNSKEY RR until either the signature is validated or the /// validator has run out of matching public keys to try. /// /// Note that this authentication process is only meaningful if the /// validator authenticates the DNSKEY RR before using it to validate /// signatures. The matching DNSKEY RR is considered to be authentic if: /// /// o the apex DNSKEY RRset containing the DNSKEY RR is considered /// authentic; or /// /// o the RRset covered by the RRSIG RR is the apex DNSKEY RRset itself, /// and the DNSKEY RR either matches an authenticated DS RR from the /// parent zone or matches a trust anchor. /// /// 5.3.2. Reconstructing the Signed Data /// /// Once the RRSIG RR has met the validity requirements described in /// Section 5.3.1, the validator has to reconstruct the original signed /// data. The original signed data includes RRSIG RDATA (excluding the /// Signature field) and the canonical form of the RRset. Aside from /// being ordered, the canonical form of the RRset might also differ from /// the received RRset due to DNS name compression, decremented TTLs, or /// wildcard expansion. The validator should use the following to /// reconstruct the original signed data: /// /// signed_data = RRSIG_RDATA | RR(1) | RR(2)... where /// /// "|" denotes concatenation /// /// RRSIG_RDATA is the wire format of the RRSIG RDATA fields /// with the Signature field excluded and the Signer's Name /// in canonical form. /// /// RR(i) = name | type | class | OrigTTL | RDATA length | RDATA /// /// name is calculated according to the function below /// /// class is the RRset's class /// /// type is the RRset type and all RRs in the class /// /// OrigTTL is the value from the RRSIG Original TTL field /// /// All names in the RDATA field are in canonical form /// /// The set of all RR(i) is sorted into canonical order. /// /// To calculate the name: /// let rrsig_labels = the value of the RRSIG Labels field /// /// let fqdn = RRset's fully qualified domain name in /// canonical form /// /// let fqdn_labels = Label count of the fqdn above. /// /// if rrsig_labels = fqdn_labels, /// name = fqdn /// /// if rrsig_labels < fqdn_labels, /// name = "*." | the rightmost rrsig_label labels of the /// fqdn /// /// if rrsig_labels > fqdn_labels /// the RRSIG RR did not pass the necessary validation /// checks and MUST NOT be used to authenticate this /// RRset. /// /// The canonical forms for names and RRsets are defined in [RFC4034]. /// /// NSEC RRsets at a delegation boundary require special processing. /// There are two distinct NSEC RRsets associated with a signed delegated /// name. One NSEC RRset resides in the parent zone, and specifies which /// RRsets are present at the parent zone. The second NSEC RRset resides /// at the child zone and identifies which RRsets are present at the apex /// in the child zone. The parent NSEC RRset and child NSEC RRset can /// always be distinguished as only a child NSEC RR will indicate that an /// SOA RRset exists at the name. When reconstructing the original NSEC /// RRset for the delegation from the parent zone, the NSEC RRs MUST NOT /// be combined with NSEC RRs from the child zone. When reconstructing /// the original NSEC RRset for the apex of the child zone, the NSEC RRs /// MUST NOT be combined with NSEC RRs from the parent zone. /// /// Note that each of the two NSEC RRsets at a delegation point has a /// corresponding RRSIG RR with an owner name matching the delegated /// name, and each of these RRSIG RRs is authoritative data associated /// with the same zone that contains the corresponding NSEC RRset. If /// necessary, a resolver can tell these RRSIG RRs apart by checking the /// Signer's Name field. /// /// 5.3.3. Checking the Signature /// /// Once the resolver has validated the RRSIG RR as described in Section /// 5.3.1 and reconstructed the original signed data as described in /// Section 5.3.2, the validator can attempt to use the cryptographic /// signature to authenticate the signed data, and thus (finally!) /// authenticate the RRset. /// /// The Algorithm field in the RRSIG RR identifies the cryptographic /// algorithm used to generate the signature. The signature itself is /// contained in the Signature field of the RRSIG RDATA, and the public /// key used to verify the signature is contained in the Public Key field /// of the matching DNSKEY RR(s) (found in Section 5.3.1). [RFC4034] /// provides a list of algorithm types and provides pointers to the /// documents that define each algorithm's use. /// /// Note that it is possible for more than one DNSKEY RR to match the /// conditions in Section 5.3.1. In this case, the validator can only /// determine which DNSKEY RR is correct by trying each matching public /// key until the validator either succeeds in validating the signature /// or runs out of keys to try. /// /// If the Labels field of the RRSIG RR is not equal to the number of /// labels in the RRset's fully qualified owner name, then the RRset is /// either invalid or the result of wildcard expansion. The resolver /// MUST verify that wildcard expansion was applied properly before /// considering the RRset to be authentic. Section 5.3.4 describes how /// to determine whether a wildcard was applied properly. /// /// If other RRSIG RRs also cover this RRset, the local resolver security /// policy determines whether the resolver also has to test these RRSIG /// RRs and how to resolve conflicts if these RRSIG RRs lead to differing /// results. /// /// If the resolver accepts the RRset as authentic, the validator MUST /// set the TTL of the RRSIG RR and each RR in the authenticated RRset to /// a value no greater than the minimum of: /// /// o the RRset's TTL as received in the response; /// /// o the RRSIG RR's TTL as received in the response; /// /// o the value in the RRSIG RR's Original TTL field; and /// /// o the difference of the RRSIG RR's Signature Expiration time and the /// current time. /// /// 5.3.4. Authenticating a Wildcard Expanded RRset Positive Response /// /// If the number of labels in an RRset's owner name is greater than the /// Labels field of the covering RRSIG RR, then the RRset and its /// covering RRSIG RR were created as a result of wildcard expansion. /// Once the validator has verified the signature, as described in /// Section 5.3, it must take additional steps to verify the non- /// existence of an exact match or closer wildcard match for the query. /// Section 5.4 discusses these steps. /// /// Note that the response received by the resolver should include all /// NSEC RRs needed to authenticate the response (see Section 3.1.3). /// ``` #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub struct SigSigner { // TODO: this should really be a trait and generic struct over KEY and DNSKEY key_rdata: RData, key: KeyPair, algorithm: Algorithm, signer_name: Name, sig_duration: Duration, is_zone_signing_key: bool, } /// Placeholder type for when OpenSSL and *ring* are disabled; enable OpenSSL and Ring for support #[cfg(not(feature = "dnssec"))] #[allow(missing_copy_implementations)] pub struct SigSigner; /// See [`SigSigner`](crate::rr::dnssec::SigSigner) #[deprecated(note = "renamed to SigSigner")] pub type Signer = SigSigner; #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] impl SigSigner { /// Version of Signer for verifying RRSIGs and SIG0 records. /// /// # Arguments /// /// * `key_rdata` - the DNSKEY and public key material /// * `key` - the private key for signing, unless validating, where just the public key is necessary /// * `signer_name` - name in the zone to which this DNSKEY is bound /// * `sig_duration` - time period for which this key is valid, 0 when verifying /// * `is_zone_update_auth` - this key may be used for updating the zone pub fn dnssec( key_rdata: DNSKEY, key: KeyPair, signer_name: Name, sig_duration: Duration, ) -> Self { let algorithm = key_rdata.algorithm(); let is_zone_signing_key = key_rdata.zone_key(); Self { key_rdata: key_rdata.into(), key, algorithm, signer_name, sig_duration, is_zone_signing_key, } } /// Version of Signer for verifying RRSIGs and SIG0 records. /// /// # Arguments /// /// * `key_rdata` - the KEY and public key material /// * `key` - the private key for signing, unless validating, where just the public key is necessary /// * `signer_name` - name in the zone to which this DNSKEY is bound /// * `is_zone_update_auth` - this key may be used for updating the zone pub fn sig0(key_rdata: KEY, key: KeyPair, signer_name: Name) -> Self { let algorithm = key_rdata.algorithm(); Self { key_rdata: key_rdata.into(), key, algorithm, signer_name, // can be Duration::ZERO after min Rust version 1.53 sig_duration: Duration::new(0, 0), is_zone_signing_key: false, } } /// Version of Signer for signing RRSIGs and SIG0 records. #[deprecated(note = "use SIG0 or DNSSec constructors")] pub fn new( algorithm: Algorithm, key: KeyPair, signer_name: Name, sig_duration: Duration, is_zone_signing_key: bool, _: bool, ) -> Self { let dnskey = key .to_dnskey(algorithm) .expect("something went wrong, use one of the SIG0 or DNSSec constructors"); Self { key_rdata: dnskey.into(), key, algorithm, signer_name, sig_duration, is_zone_signing_key, } } /// Return the key used for validation/signing pub fn key(&self) -> &KeyPair { &self.key } /// Returns the duration that this signature is valid for pub fn sig_duration(&self) -> Duration { self.sig_duration } /// A hint to the DNSKey associated with this Signer can be used to sign/validate records in the zone pub fn is_zone_signing_key(&self) -> bool { self.is_zone_signing_key } /// Signs a hash. /// /// This will panic if the `key` is not a private key and can be used for signing. /// /// # Arguments /// /// * `hash` - the hashed resource record set, see `rrset_tbs`. /// /// # Return value /// /// The signature, ready to be stored in an `RData::RRSIG`. pub fn sign(&self, tbs: &TBS) -> ProtoResult> { self.key .sign(self.algorithm, tbs) .map_err(|e| ProtoErrorKind::Msg(format!("signing error: {}", e)).into()) } /// Returns the algorithm this Signer will use to either sign or validate a signature pub fn algorithm(&self) -> Algorithm { self.algorithm } /// The name of the signing entity, e.g. the DNS server name. /// /// This should match the name on key in the zone. pub fn signer_name(&self) -> &Name { &self.signer_name } // TODO: move this to DNSKEY/KEY? /// The key tag is calculated as a hash to more quickly lookup a DNSKEY. /// /// [RFC 1035](https://tools.ietf.org/html/rfc1035), DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987 /// /// ```text /// RFC 2535 DNS Security Extensions March 1999 /// /// 4.1.6 Key Tag Field /// /// The "key Tag" is a two octet quantity that is used to efficiently /// select between multiple keys which may be applicable and thus check /// that a public key about to be used for the computationally expensive /// effort to check the signature is possibly valid. For algorithm 1 /// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two /// octets of the public key modulus needed to decode the signature /// field. That is to say, the most significant 16 of the least /// significant 24 bits of the modulus in network (big endian) order. For /// all other algorithms, including private algorithms, it is calculated /// as a simple checksum of the KEY RR as described in Appendix C. /// /// Appendix C: Key Tag Calculation /// /// The key tag field in the SIG RR is just a means of more efficiently /// selecting the correct KEY RR to use when there is more than one KEY /// RR candidate available, for example, in verifying a signature. It is /// possible for more than one candidate key to have the same tag, in /// which case each must be tried until one works or all fail. The /// following reference implementation of how to calculate the Key Tag, /// for all algorithms other than algorithm 1, is in ANSI C. It is coded /// for clarity, not efficiency. (See section 4.1.6 for how to determine /// the Key Tag of an algorithm 1 key.) /// /// /* assumes int is at least 16 bits /// first byte of the key tag is the most significant byte of return /// value /// second byte of the key tag is the least significant byte of /// return value /// */ /// /// int keytag ( /// /// unsigned char key[], /* the RDATA part of the KEY RR */ /// unsigned int keysize, /* the RDLENGTH */ /// ) /// { /// long int ac; /* assumed to be 32 bits or larger */ /// /// for ( ac = 0, i = 0; i < keysize; ++i ) /// ac += (i&1) ? key[i] : key[i]<<8; /// ac += (ac>>16) & 0xFFFF; /// return ac & 0xFFFF; /// } /// ``` pub fn calculate_key_tag(&self) -> ProtoResult { let mut bytes: Vec = Vec::with_capacity(512); { let mut e = BinEncoder::new(&mut bytes); self.key_rdata.emit(&mut e)?; } Ok(DNSKEY::calculate_key_tag_internal(&bytes)) } /// Signs the given message, returning the signature bytes. /// /// # Arguments /// /// * `message` - the message to sign /// /// [rfc2535](https://tools.ietf.org/html/rfc2535#section-4.1.8.1), Domain Name System Security Extensions, 1999 /// /// ```text /// 4.1.8.1 Calculating Transaction and Request SIGs /// /// A response message from a security aware server may optionally /// contain a special SIG at the end of the additional information /// section to authenticate the transaction. /// /// This SIG has a "type covered" field of zero, which is not a valid RR /// type. It is calculated by using a "data" (see Section 4.1.8) of the /// entire preceding DNS reply message, including DNS header but not the /// IP header and before the reply RR counts have been adjusted for the /// inclusion of any transaction SIG, concatenated with the entire DNS /// query message that produced this response, including the query's DNS /// header and any request SIGs but not its IP header. That is /// /// data = full response (less transaction SIG) | full query /// /// Verification of the transaction SIG (which is signed by the server /// host key, not the zone key) by the requesting resolver shows that the /// query and response were not tampered with in transit, that the /// response corresponds to the intended query, and that the response /// comes from the queried server. /// /// A DNS request may be optionally signed by including one or more SIGs /// at the end of the query. Such SIGs are identified by having a "type /// covered" field of zero. They sign the preceding DNS request message /// including DNS header but not including the IP header or any request /// SIGs at the end and before the request RR counts have been adjusted /// for the inclusions of any request SIG(s). /// /// WARNING: Request SIGs are unnecessary for any currently defined /// request other than update [RFC 2136, 2137] and will cause some old /// DNS servers to give an error return or ignore a query. However, such /// SIGs may in the future be needed for other requests. /// /// Except where needed to authenticate an update or similar privileged /// request, servers are not required to check request SIGs. /// ``` /// --- /// /// NOTE: In classic RFC style, this is unclear, it implies that each SIG record is not included in /// the Additional record count, but this makes it more difficult to process and calculate more /// than one SIG0 record. Annoyingly, it means that the Header is signed with different material /// (i.e. additional record count - #SIG0 records), so the exact header sent is NOT the header /// being verified. /// /// --- pub fn sign_message(&self, message: &Message, pre_sig0: &SIG) -> ProtoResult> { tbs::message_tbs(message, pre_sig0).and_then(|tbs| self.sign(&tbs)) } /// Extracts a public KEY from this Signer pub fn to_dnskey(&self) -> DnsSecResult { // TODO: this interface should allow for setting if this is a secure entry point vs. ZSK self.key .to_public_bytes() .map(|bytes| DNSKEY::new(self.is_zone_signing_key, true, false, self.algorithm, bytes)) } /// Test that this key is capable of signing and verifying data pub fn test_key(&self) -> DnsSecResult<()> { // use proto::rr::dnssec::PublicKey; // // TODO: why doesn't this work for ecdsa_256 and 384? // let test_data = TBS::from(b"DEADBEEF" as &[u8]); // let signature = self.sign(&test_data).map_err(|e| {println!("failed to sign, {:?}", e); e})?; // let pk = self.key.to_public_key()?; // pk.verify(self.algorithm, test_data.as_ref(), &signature).map_err(|e| {println!("failed to verify, {:?}", e); e})?; Ok(()) } } impl MessageFinalizer for SigSigner { #[cfg(feature = "dnssec")] fn finalize_message( &self, message: &Message, current_time: u32, ) -> ProtoResult<(Vec, Option)> { debug!("signing message: {:?}", message); let key_tag: u16 = self.calculate_key_tag()?; // this is based on RFCs 2535, 2931 and 3007 // 'For all SIG(0) RRs, the owner name, class, TTL, and original TTL, are // meaningless.' - 2931 let mut sig0 = Record::new(); // The TTL fields SHOULD be zero sig0.set_ttl(0); // The CLASS field SHOULD be ANY sig0.set_dns_class(DNSClass::ANY); // The owner name SHOULD be root (a single zero octet). sig0.set_name(Name::root()); let num_labels = sig0.name().num_labels(); let expiration_time: u32 = current_time + (5 * 60); // +5 minutes in seconds sig0.set_rr_type(RecordType::SIG); let pre_sig0 = SIG::new( // type covered in SIG(0) is 0 which is what makes this SIG0 vs a standard SIG RecordType::ZERO, self.algorithm(), num_labels, // see above, original_ttl is meaningless, The TTL fields SHOULD be zero 0, // recommended time is +5 minutes from now, to prevent timing attacks, 2 is probably good expiration_time, // current time, this should be UTC // unsigned numbers of seconds since the start of 1 January 1970, GMT current_time, key_tag, // can probably get rid of this clone if the ownership is correct self.signer_name().clone(), Vec::new(), ); let signature: Vec = self.sign_message(message, &pre_sig0)?; sig0.set_data(Some(RData::DNSSEC(DNSSECRData::SIG( pre_sig0.set_sig(signature), )))); Ok((vec![sig0], None)) } #[cfg(not(feature = "dnssec"))] fn finalize_message( &self, _: &Message, _: u32, ) -> ProtoResult<(Vec, Option)> { Err( ProtoErrorKind::Message("the ring or openssl feature must be enabled for signing") .into(), ) } } #[cfg(test)] #[cfg(feature = "openssl")] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use openssl::bn::BigNum; use openssl::pkey::Private; use openssl::rsa::Rsa; use crate::op::{Message, Query}; use crate::rr::dnssec::*; use crate::rr::rdata::key::KeyUsage; use crate::rr::rdata::{DNSSECRData, SIG}; use crate::rr::{DNSClass, Name, Record, RecordType}; use super::*; fn assert_send_and_sync() {} #[test] fn test_send_and_sync() { assert_send_and_sync::(); } fn pre_sig0(signer: &SigSigner, inception_time: u32, expiration_time: u32) -> SIG { SIG::new( // type covered in SIG(0) is 0 which is what makes this SIG0 vs a standard SIG RecordType::ZERO, signer.algorithm(), 0, // see above, original_ttl is meaningless, The TTL fields SHOULD be zero 0, // recommended time is +5 minutes from now, to prevent timing attacks, 2 is probably good expiration_time, // current time, this should be UTC // unsigned numbers of seconds since the start of 1 January 1970, GMT inception_time, signer.calculate_key_tag().unwrap(), // can probably get rid of this clone if the ownership is correct signer.signer_name().clone(), Vec::new(), ) } #[test] fn test_sign_and_verify_message_sig0() { let origin: Name = Name::parse("example.com.", None).unwrap(); let mut question: Message = Message::new(); let mut query: Query = Query::new(); query.set_name(origin); question.add_query(query); let rsa = Rsa::generate(2048).unwrap(); let key = KeyPair::from_rsa(rsa).unwrap(); let sig0key = key.to_sig0key(Algorithm::RSASHA256).unwrap(); let signer = SigSigner::sig0(sig0key.clone(), key, Name::root()); let pre_sig0 = pre_sig0(&signer, 0, 300); let sig = signer.sign_message(&question, &pre_sig0).unwrap(); println!("sig: {:?}", sig); assert!(!sig.is_empty()); assert!(sig0key.verify_message(&question, &sig, &pre_sig0).is_ok()); // now test that the sig0 record works correctly. assert!(question.sig0().is_empty()); question.finalize(&signer, 0).expect("should have signed"); assert!(!question.sig0().is_empty()); let sig = signer.sign_message(&question, &pre_sig0); println!("sig after sign: {:?}", sig); if let Some(RData::DNSSEC(DNSSECRData::SIG(ref sig))) = question.sig0()[0].data() { assert!(sig0key.verify_message(&question, sig.sig(), sig).is_ok()); } } #[test] #[allow(deprecated)] fn test_sign_and_verify_rrset() { let rsa = Rsa::generate(2048).unwrap(); let key = KeyPair::from_rsa(rsa).unwrap(); let sig0key = key .to_sig0key_with_usage(Algorithm::RSASHA256, KeyUsage::Zone) .unwrap(); let signer = SigSigner::sig0(sig0key, key, Name::root()); let origin: Name = Name::parse("example.com.", None).unwrap(); let rrsig = Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::DNSSEC(DNSSECRData::SIG(SIG::new( RecordType::NS, Algorithm::RSASHA256, origin.num_labels(), 86400, 5, 0, signer.calculate_key_tag().unwrap(), origin.clone(), vec![], ))))) .clone(); let rrset = vec![ Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS( Name::parse("a.iana-servers.net.", None).unwrap(), ))) .clone(), Record::new() .set_name(origin) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS( Name::parse("b.iana-servers.net.", None).unwrap(), ))) .clone(), ]; let tbs = tbs::rrset_tbs_with_rrsig(&rrsig, &rrset).unwrap(); let sig = signer.sign(&tbs).unwrap(); let pub_key = signer.key().to_public_bytes().unwrap(); let pub_key = PublicKeyEnum::from_public_bytes(&pub_key, Algorithm::RSASHA256).unwrap(); assert!(pub_key .verify(Algorithm::RSASHA256, tbs.as_ref(), &sig) .is_ok()); } fn get_rsa_from_vec(params: &[u32]) -> Result, openssl::error::ErrorStack> { Rsa::from_private_components( BigNum::from_u32(params[0]).unwrap(), // modulus: n BigNum::from_u32(params[1]).unwrap(), // public exponent: e, BigNum::from_u32(params[2]).unwrap(), // private exponent: de, BigNum::from_u32(params[3]).unwrap(), // prime1: p, BigNum::from_u32(params[4]).unwrap(), // prime2: q, BigNum::from_u32(params[5]).unwrap(), // exponent1: dp, BigNum::from_u32(params[6]).unwrap(), // exponent2: dq, BigNum::from_u32(params[7]).unwrap(), // coefficient: qi ) } #[test] #[allow(deprecated)] #[allow(clippy::unreadable_literal)] fn test_calculate_key_tag() { let test_vectors = vec![ (vec![33, 3, 21, 11, 3, 1, 1, 1], 9739), ( vec![ 0xc2fedb69, 0x10001, 0x6ebb9209, 0xf743, 0xc9e3, 0xd07f, 0x6275, 0x1095, ], 42354, ), ]; for &(ref input_data, exp_result) in test_vectors.iter() { let rsa = get_rsa_from_vec(input_data).unwrap(); let rsa_pem = rsa.private_key_to_pem().unwrap(); println!("pkey:\n{}", String::from_utf8(rsa_pem).unwrap()); let key = KeyPair::from_rsa(rsa).unwrap(); let sig0key = key .to_sig0key_with_usage(Algorithm::RSASHA256, KeyUsage::Zone) .unwrap(); let signer = SigSigner::sig0(sig0key, key, Name::root()); let key_tag = signer.calculate_key_tag().unwrap(); assert_eq!(key_tag, exp_result); } } #[test] #[allow(deprecated)] fn test_calculate_key_tag_pem() { let x = "-----BEGIN RSA PRIVATE KEY----- MC0CAQACBQC+L6pNAgMBAAECBQCYj0ZNAgMA9CsCAwDHZwICeEUCAnE/AgMA3u0= -----END RSA PRIVATE KEY----- "; let rsa = Rsa::private_key_from_pem(x.as_bytes()).unwrap(); let rsa_pem = rsa.private_key_to_pem().unwrap(); println!("pkey:\n{}", String::from_utf8(rsa_pem).unwrap()); let key = KeyPair::from_rsa(rsa).unwrap(); let sig0key = key .to_sig0key_with_usage(Algorithm::RSASHA256, KeyUsage::Zone) .unwrap(); let signer = SigSigner::sig0(sig0key, key, Name::root()); let key_tag = signer.calculate_key_tag().unwrap(); assert_eq!(key_tag, 28551); } // TODO: these tests technically came from TBS in trust_dns_proto #[cfg(feature = "openssl")] #[allow(clippy::module_inception)] #[cfg(test)] mod tests { use openssl::rsa::Rsa; use crate::rr::dnssec::tbs::*; use crate::rr::dnssec::*; use crate::rr::rdata::{DNSSECRData, SIG}; use crate::rr::*; #[test] fn test_rrset_tbs() { let rsa = Rsa::generate(2048).unwrap(); let key = KeyPair::from_rsa(rsa).unwrap(); let sig0key = key.to_sig0key(Algorithm::RSASHA256).unwrap(); let signer = SigSigner::sig0(sig0key, key, Name::root()); let origin: Name = Name::parse("example.com.", None).unwrap(); let rrsig = Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::DNSSEC(DNSSECRData::SIG(SIG::new( RecordType::NS, Algorithm::RSASHA256, origin.num_labels(), 86400, 5, 0, signer.calculate_key_tag().unwrap(), origin.clone(), vec![], ))))) .clone(); let rrset = vec![ Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS( Name::parse("a.iana-servers.net.", None).unwrap(), ))) .clone(), Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS( Name::parse("b.iana-servers.net.", None).unwrap(), ))) .clone(), ]; let tbs = rrset_tbs_with_rrsig(&rrsig, &rrset).unwrap(); assert!(!tbs.as_ref().is_empty()); let rrset = vec![ Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::CNAME) .set_dns_class(DNSClass::IN) .set_data(Some(RData::CNAME( Name::parse("a.iana-servers.net.", None).unwrap(), ))) .clone(), // different type Record::new() .set_name(Name::parse("www.example.com.", None).unwrap()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS( Name::parse("a.iana-servers.net.", None).unwrap(), ))) .clone(), // different name Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::CH) .set_data(Some(RData::NS( Name::parse("a.iana-servers.net.", None).unwrap(), ))) .clone(), // different class Record::new() .set_name(origin.clone()) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS( Name::parse("a.iana-servers.net.", None).unwrap(), ))) .clone(), Record::new() .set_name(origin) .set_ttl(86400) .set_rr_type(RecordType::NS) .set_dns_class(DNSClass::IN) .set_data(Some(RData::NS( Name::parse("b.iana-servers.net.", None).unwrap(), ))) .clone(), ]; let filtered_tbs = rrset_tbs_with_rrsig(&rrsig, &rrset).unwrap(); assert!(!filtered_tbs.as_ref().is_empty()); assert_eq!(tbs.as_ref(), filtered_tbs.as_ref()); } } } trust-dns-client-0.22.0/src/rr/dnssec/tsig.rs000064400000000000000000000335741046102023000171450ustar 00000000000000// Copyright 2015-2019 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! tsigner is a structure for computing tsig messasignuthentication code for dns transactions use tracing::debug; use crate::proto::error::{ProtoError, ProtoResult}; use crate::proto::rr::dnssec::rdata::tsig::{ make_tsig_record, message_tbs, signed_bitmessage_to_buf, TsigAlgorithm, TSIG, }; use crate::proto::rr::dnssec::rdata::DNSSECRData; use std::ops::Range; use std::sync::Arc; use crate::op::{DnsResponse, Message, MessageFinalizer, MessageVerifier}; use crate::rr::{Name, RData, Record}; /// Struct to pass to a client for it to authenticate requests using TSIG. #[derive(Clone)] pub struct TSigner(Arc); struct TSignerInner { key: Vec, // TODO this might want to be some sort of auto-zeroing on drop buffer, as it's cryptographic matterial algorithm: TsigAlgorithm, signer_name: Name, fudge: u16, } impl TSigner { /// Create a new Tsigner from its parts /// /// # Arguments /// /// * `key` - cryptographic key used to authenticate exchanges /// * `algorithm` - algorithm used to authenticate exchanges /// * `signer_name` - name of the key. Must match the name known to the server /// * `fudge` - maximum difference between client and server time, in seconds, see [fudge](TSigner::fudge) for details pub fn new( key: Vec, algorithm: TsigAlgorithm, signer_name: Name, fudge: u16, ) -> ProtoResult { if algorithm.supported() { Ok(Self(Arc::new(TSignerInner { key, algorithm, signer_name, fudge, }))) } else { Err(ProtoError::from("unsupported mac algorithm")) } } /// Return the key used for message authentication pub fn key(&self) -> &[u8] { &self.0.key } /// Return the algorithm used for message authentication pub fn algorithm(&self) -> &TsigAlgorithm { &self.0.algorithm } /// Name of the key used by this signer pub fn signer_name(&self) -> &Name { &self.0.signer_name } /// Maximum time difference between client time when issuing a message, and server time when /// receiving it, in second. If time is out, the server will consider the request invalid. /// Longer values means more room for replay by an attacker. A few minutes are usually a good /// value. pub fn fudge(&self) -> u16 { self.0.fudge } /// Compute authentication tag for a buffer pub fn sign(&self, tbs: &[u8]) -> ProtoResult> { self.0.algorithm.mac_data(&self.0.key, tbs) } /// Compute authentication tag for a message pub fn sign_message(&self, message: &Message, pre_tsig: &TSIG) -> ProtoResult> { message_tbs(None, message, pre_tsig, &self.0.signer_name).and_then(|tbs| self.sign(&tbs)) } /// Verify hmac in constant time to prevent timing attacks pub fn verify(&self, tbv: &[u8], tag: &[u8]) -> ProtoResult<()> { self.0.algorithm.verify_mac(&self.0.key, tbv, tag) } /// Verify the message is correctly signed /// This does not perform time verification on its own, instead one should verify current time /// lie in returned Range /// /// # Arguments /// * `previous_hash` - Hash of the last message received before this one, or of the query for /// the first message /// * `message` - byte buffer containing current message /// * `first_message` - is this the first response message /// /// # Returns /// Return Ok(_) on valid signature. Inner tuple contain the following values, in order: /// * a byte buffer containing the hash of this message. Need to be passed back when /// authenticating next message /// * a Range of time that is acceptable /// * the time the signature was emited. It must be greater or equal to the time of previous /// messages, if any pub fn verify_message_byte( &self, previous_hash: Option<&[u8]>, message: &[u8], first_message: bool, ) -> ProtoResult<(Vec, Range, u64)> { let (tbv, record) = signed_bitmessage_to_buf(previous_hash, message, first_message)?; let tsig = if let Some(RData::DNSSEC(DNSSECRData::TSIG(tsig))) = record.data() { tsig } else { unreachable!("tsig::signed_message_to_buff always returns a TSIG record") }; // https://tools.ietf.org/html/rfc8945#section-5.2 // 1. Check key if record.name() != &self.0.signer_name || tsig.algorithm() != &self.0.algorithm { return Err(ProtoError::from("tsig validation error: wrong key")); } // 2. Check MAC // note: that this verification does not allow for truncation of the HMAC, which technically the RFC suggests. // this is to be pedantic about constant time HMAC validation (prevent timing attacks) as well as any security // concerns about MAC truncation and collisions. if tsig.mac().len() < tsig.algorithm().output_len()? { return Err(ProtoError::from("Please file an issue with https://github.com/bluejekyll/trust-dns to support truncated HMACs with TSIG")); } // verify the MAC let mac = tsig.mac(); self.verify(&tbv, mac) .map_err(|_e| ProtoError::from("tsig validation error: invalid signature"))?; // 3. Check time values // we don't actually have time here so we will let upper level decide // this is technically in violation of the RFC, in case both time and // truncation policy are bad, time should be reported and this code will report // truncation issue instead // 4. Check truncation policy // see not above in regards to not supporting verification of truncated HMACs. // if tsig.mac().len() < std::cmp::max(10, self.0.algorithm.output_len()? / 2) { // return Err(ProtoError::from( // "tsig validation error: truncated signature", // )); // } Ok(( tsig.mac().to_vec(), Range { start: tsig.time() - tsig.fudge() as u64, end: tsig.time() + tsig.fudge() as u64, }, tsig.time(), )) } } impl MessageFinalizer for TSigner { fn finalize_message( &self, message: &Message, current_time: u32, ) -> ProtoResult<(Vec, Option)> { debug!("signing message: {:?}", message); let current_time = current_time as u64; let pre_tsig = TSIG::new( self.0.algorithm.clone(), current_time as u64, self.0.fudge, Vec::new(), message.id(), 0, Vec::new(), ); let mut signature: Vec = self.sign_message(message, &pre_tsig)?; let tsig = make_tsig_record( self.0.signer_name.clone(), pre_tsig.set_mac(signature.clone()), ); let self2 = self.clone(); let mut remote_time = 0; let verifier = move |dns_response: &[u8]| { let (last_sig, range, rt) = self2.verify_message_byte( Some(signature.as_ref()), dns_response, remote_time == 0, )?; if rt >= remote_time && range.contains(¤t_time) // this assumes a no-latency answer { signature = last_sig; remote_time = rt; Message::from_vec(dns_response).map(DnsResponse::from) } else { Err(ProtoError::from("tsig validation error: outdated response")) } }; Ok((vec![tsig], Some(Box::new(verifier)))) } } #[cfg(test)] #[cfg(any(feature = "dnssec-ring", feature = "dnssec-openssl"))] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use crate::op::{Message, Query}; use crate::rr::Name; use crate::serialize::binary::BinEncodable; use super::*; fn assert_send_and_sync() {} #[test] fn test_send_and_sync() { assert_send_and_sync::(); } #[test] fn test_sign_and_verify_message_tsig() { let time_begin = 1609459200u64; let fudge = 300u64; let origin: Name = Name::parse("example.com.", None).unwrap(); let key_name: Name = Name::from_ascii("key_name").unwrap(); let mut question: Message = Message::new(); let mut query: Query = Query::new(); query.set_name(origin); question.add_query(query); let sig_key = b"some_key".to_vec(); let signer = TSigner::new(sig_key, TsigAlgorithm::HmacSha512, key_name, fudge as u16).unwrap(); assert!(question.signature().is_empty()); question .finalize(&signer, time_begin as u32) .expect("should have signed"); assert!(!question.signature().is_empty()); let (_, validity_range, _) = signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .unwrap(); assert!(validity_range.contains(&(time_begin + fudge / 2))); // slightly outdated, but still to be acceptable assert!(validity_range.contains(&(time_begin - fudge / 2))); // sooner than our time, but still acceptable assert!(!validity_range.contains(&(time_begin + fudge * 2))); // too late to be accepted assert!(!validity_range.contains(&(time_begin - fudge * 2))); // too soon to be accepted } // make rejection tests shorter by centralizing common setup code fn get_message_and_signer() -> (Message, TSigner) { let time_begin = 1609459200u64; let fudge = 300u64; let origin: Name = Name::parse("example.com.", None).unwrap(); let key_name: Name = Name::from_ascii("key_name").unwrap(); let mut question: Message = Message::new(); let mut query: Query = Query::new(); query.set_name(origin); question.add_query(query); let sig_key = b"some_key".to_vec(); let signer = TSigner::new(sig_key, TsigAlgorithm::HmacSha512, key_name, fudge as u16).unwrap(); assert!(question.signature().is_empty()); question .finalize(&signer, time_begin as u32) .expect("should have signed"); assert!(!question.signature().is_empty()); // this should be ok, it has not been tampered with assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_ok()); (question, signer) } #[test] fn test_sign_and_verify_message_tsig_reject_keyname() { let (mut question, signer) = get_message_and_signer(); let other_name: Name = Name::from_ascii("other_name").unwrap(); let mut signature = question.take_signature().remove(0); signature.set_name(other_name); question.add_tsig(signature); assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_err()); } #[test] fn test_sign_and_verify_message_tsig_reject_invalid_mac() { let (mut question, signer) = get_message_and_signer(); let mut query: Query = Query::new(); let origin: Name = Name::parse("example.net.", None).unwrap(); query.set_name(origin); question.add_query(query); assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_err()); } #[test] #[cfg(feature = "hmac_truncation")] // not currently supported for security reasons fn test_sign_and_verify_message_tsig_truncation() { let (mut question, signer) = get_message_and_signer(); { let mut signature = question.take_signature().remove(0); if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() { let mut mac = tsig.mac().to_vec(); mac.push(0); // make one longer than sha512 std::mem::swap(tsig, &mut tsig.clone().set_mac(mac)); } else { panic!("should have been a TSIG"); } question.add_tsig(signature); } // we are longer, there is a problem assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_err()); { let mut signature = question.take_signature().remove(0); if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() { // sha512 is 512 bits, half of that is 256 bits, /8 for byte let mac = tsig.mac()[..256 / 8].to_vec(); std::mem::swap(tsig, &mut tsig.clone().set_mac(mac)); } else { panic!("should have been a TSIG"); } question.add_tsig(signature); } // we are at half, it's allowed assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_ok()); { let mut signature = question.take_signature().remove(0); if let RData::DNSSEC(DNSSECRData::TSIG(ref mut tsig)) = signature.rdata_mut() { // less than half of sha512 let mac = tsig.mac()[..240 / 8].to_vec(); std::mem::swap(tsig, &mut tsig.clone().set_mac(mac)); } else { panic!("should have been a TSIG"); } question.add_tsig(signature); } assert!(signer .verify_message_byte(None, &question.to_bytes().unwrap(), true) .is_err()); } } trust-dns-client-0.22.0/src/rr/lower_name.rs000064400000000000000000000224371046102023000170440ustar 00000000000000// Copyright 2015-2019 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! domain name, aka labels, implementation use std::borrow::Borrow; use std::cmp::{Ordering, PartialEq}; use std::fmt; use std::hash::{Hash, Hasher}; use std::str::FromStr; use crate::proto::error::*; #[cfg(feature = "serde-config")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use crate::rr::Name; use crate::serialize::binary::*; /// them should be through references. As a workaround the Strings are all Rc as well as the array #[derive(Default, Debug, Eq, Clone)] pub struct LowerName(Name); impl LowerName { /// Create a new domain::LowerName, i.e. label pub fn new(name: &Name) -> Self { Self(name.to_lowercase()) } /// Returns true if there are no labels, i.e. it's empty. /// /// In DNS the root is represented by `.` /// /// # Examples /// /// ``` /// use trust_dns_client::rr::{LowerName, Name}; /// /// let root = LowerName::from(Name::root()); /// assert_eq!(&root.to_string(), "."); /// ``` pub fn is_root(&self) -> bool { self.0.is_root() } /// Returns true if the name is a fully qualified domain name. /// /// If this is true, it has effects like only querying for this single name, as opposed to building /// up a search list in resolvers. /// /// *warning: this interface is unstable and may change in the future* /// /// # Examples /// /// ``` /// use std::str::FromStr; /// use trust_dns_client::rr::{LowerName, Name}; /// /// let name = LowerName::from(Name::from_str("www").unwrap()); /// assert!(!name.is_fqdn()); /// /// let name = LowerName::from(Name::from_str("www.example.com").unwrap()); /// assert!(!name.is_fqdn()); /// /// let name = LowerName::from(Name::from_str("www.example.com.").unwrap()); /// assert!(name.is_fqdn()); /// ``` pub fn is_fqdn(&self) -> bool { self.0.is_fqdn() } /// Trims off the first part of the name, to help with searching for the domain piece /// /// # Examples /// /// ``` /// use std::str::FromStr; /// use trust_dns_client::rr::{LowerName, Name}; /// /// let example_com = LowerName::from(Name::from_str("example.com").unwrap()); /// assert_eq!(example_com.base_name(), LowerName::from(Name::from_str("com.").unwrap())); /// assert_eq!(LowerName::from(Name::from_str("com.").unwrap().base_name()), LowerName::from(Name::root())); /// assert_eq!(LowerName::from(Name::root().base_name()), LowerName::from(Name::root())); /// ``` pub fn base_name(&self) -> Self { Self(self.0.base_name()) } /// returns true if the name components of self are all present at the end of name /// /// # Example /// /// ```rust /// use std::str::FromStr; /// use trust_dns_client::rr::{LowerName, Name}; /// /// let name = LowerName::from(Name::from_str("www.example.com").unwrap()); /// let zone = LowerName::from(Name::from_str("example.com").unwrap()); /// let another = LowerName::from(Name::from_str("example.net").unwrap()); /// assert!(zone.zone_of(&name)); /// assert!(!another.zone_of(&name)); /// ``` pub fn zone_of(&self, name: &Self) -> bool { self.0.zone_of_case(&name.0) } /// Returns the number of labels in the name, discounting `*`. /// /// # Examples /// /// ``` /// use std::str::FromStr; /// use trust_dns_client::rr::{LowerName, Name}; /// /// let root = LowerName::from(Name::root()); /// assert_eq!(root.num_labels(), 0); /// /// let example_com = LowerName::from(Name::from_str("example.com").unwrap()); /// assert_eq!(example_com.num_labels(), 2); /// /// let star_example_com = LowerName::from(Name::from_str("*.example.com").unwrap()); /// assert_eq!(star_example_com.num_labels(), 2); /// ``` pub fn num_labels(&self) -> u8 { self.0.num_labels() } /// returns the length in bytes of the labels. '.' counts as 1 /// /// This can be used as an estimate, when serializing labels, they will often be compressed /// and/or escaped causing the exact length to be different. pub fn len(&self) -> usize { self.0.len() } /// Returns true if the name is empty pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Emits the canonical version of the name to the encoder. /// /// In canonical form, there will be no pointers written to the encoder (i.e. no compression). pub fn emit_as_canonical( &self, encoder: &mut BinEncoder<'_>, canonical: bool, ) -> ProtoResult<()> { self.0.emit_as_canonical(encoder, canonical) } /// Pass through for Name::is_wildcard pub fn is_wildcard(&self) -> bool { self.0.is_wildcard() } /// Replaces the first label with the wildcard character, "*" pub fn into_wildcard(self) -> Self { let name = self.0.into_wildcard(); Self(name) } } impl Hash for LowerName { fn hash(&self, state: &mut H) where H: Hasher, { for label in &self.0 { state.write(label); } } } impl PartialEq for LowerName { fn eq(&self, other: &Self) -> bool { self.0.eq_case(&other.0) } } impl BinEncodable for LowerName { fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { let is_canonical_names = encoder.is_canonical_names(); self.emit_as_canonical(encoder, is_canonical_names) } } impl fmt::Display for LowerName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl PartialOrd for LowerName { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for LowerName { /// Given two lower cased names, this performs a case sensitive comparison. /// /// ```text /// RFC 4034 DNSSEC Resource Records March 2005 /// /// 6.1. Canonical DNS LowerName Order /// /// For the purposes of DNS security, owner names are ordered by treating /// individual labels as unsigned left-justified octet strings. The /// absence of a octet sorts before a zero value octet, and uppercase /// US-ASCII letters are treated as if they were lowercase US-ASCII /// letters. /// /// To compute the canonical ordering of a set of DNS names, start by /// sorting the names according to their most significant (rightmost) /// labels. For names in which the most significant label is identical, /// continue sorting according to their next most significant label, and /// so forth. /// /// For example, the following names are sorted in canonical DNS name /// order. The most significant label is "example". At this level, /// "example" sorts first, followed by names ending in "a.example", then /// by names ending "z.example". The names within each level are sorted /// in the same way. /// /// example /// a.example /// yljkjljk.a.example /// Z.a.example /// zABC.a.EXAMPLE /// z.example /// \001.z.example /// *.z.example /// \200.z.example /// ``` fn cmp(&self, other: &Self) -> Ordering { self.0.cmp_case(&other.0) } } impl From for LowerName { fn from(name: Name) -> Self { Self::new(&name) } } impl<'a> From<&'a Name> for LowerName { fn from(name: &'a Name) -> Self { Self::new(name) } } impl From for Name { fn from(name: LowerName) -> Self { name.0 } } impl<'a> From<&'a LowerName> for Name { fn from(name: &'a LowerName) -> Self { name.0.clone() } } impl Borrow for LowerName { fn borrow(&self) -> &Name { &self.0 } } impl<'r> BinDecodable<'r> for LowerName { /// parses the chain of labels /// this has a max of 255 octets, with each label being less than 63. /// all names will be stored lowercase internally. /// This will consume the portions of the Vec which it is reading... fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult { let name = Name::read(decoder)?; Ok(Self(name.to_lowercase())) } } impl FromStr for LowerName { type Err = ProtoError; fn from_str(name: &str) -> Result { Name::from_str(name).map(Self::from) } } #[cfg(feature = "serde-config")] impl Serialize for LowerName { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&self.to_string()) } } #[cfg(feature = "serde-config")] impl<'de> Deserialize<'de> for LowerName { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; FromStr::from_str(&s).map_err(de::Error::custom) } } trust-dns-client-0.22.0/src/rr/mod.rs000064400000000000000000000031621046102023000154650ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! Resource record related components, e.g. `Name` aka label, `Record`, `RData`, ... #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub mod dnssec; mod lower_name; mod rr_key; pub mod zone; use crate::proto::rr; pub use crate::proto::rr::dns_class; pub use crate::proto::rr::domain; pub use crate::proto::rr::record_data; pub use crate::proto::rr::record_type; pub use crate::proto::rr::resource; pub use self::dns_class::DNSClass; pub use self::lower_name::LowerName; pub use self::record_data::RData; pub use self::record_type::RecordType; pub use self::resource::Record; pub use self::rr::domain::{IntoName, Label, Name}; #[allow(deprecated)] pub use self::rr::IntoRecordSet; pub use self::rr::RecordSet; pub use self::rr_key::RrKey; /// All record data structures and related serialization methods pub mod rdata { #[cfg(feature = "dnssec")] #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))] pub use crate::proto::rr::dnssec::rdata::*; pub use crate::proto::rr::rdata::*; } trust-dns-client-0.22.0/src/rr/rr_key.rs000064400000000000000000000031321046102023000161760ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::cmp::Ordering; use crate::rr::{LowerName, RecordType}; /// Accessor key for RRSets in the Authority. #[derive(Eq, PartialEq, Debug, Hash, Clone)] pub struct RrKey { /// Matches the name in the Record of this key pub name: LowerName, /// Matches the type of the Record of this key pub record_type: RecordType, } impl RrKey { /// Creates a new key to access the Authority. /// /// # Arguments /// /// * `name` - domain name to lookup. /// * `record_type` - the `RecordType` to lookup. /// /// # Return value /// /// A new key to access the Authorities. /// TODO: make all cloned params pass by value. pub fn new(name: LowerName, record_type: RecordType) -> Self { Self { name, record_type } } /// Returns the name of the key pub fn name(&self) -> &LowerName { &self.name } } impl PartialOrd for RrKey { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for RrKey { fn cmp(&self, other: &Self) -> Ordering { let order = self.name.cmp(&other.name); if order == Ordering::Equal { self.record_type.cmp(&other.record_type) } else { order } } } trust-dns-client-0.22.0/src/rr/zone.rs000064400000000000000000000426051046102023000156660ustar 00000000000000//! Reserved Zone and related information pub use crate::proto::rr::domain::usage::*; use crate::proto::rr::domain::{Label, Name}; use crate::proto::serialize::binary::BinEncodable; use lazy_static::lazy_static; use radix_trie::{Trie, TrieKey}; // Reserved reverse IPs // // [Special-Use Domain Names](https://tools.ietf.org/html/rfc6761), RFC 6761 February, 2013 // // ```text // 6.1. Domain Name Reservation Considerations for Private Addresses // // The private-address [RFC1918] reverse-mapping domains listed below, // and any names falling within those domains, are Special-Use Domain // Names: // // 10.in-addr.arpa. 21.172.in-addr.arpa. 26.172.in-addr.arpa. // 16.172.in-addr.arpa. 22.172.in-addr.arpa. 27.172.in-addr.arpa. // 17.172.in-addr.arpa. 30.172.in-addr.arpa. 28.172.in-addr.arpa. // 18.172.in-addr.arpa. 23.172.in-addr.arpa. 29.172.in-addr.arpa. // 19.172.in-addr.arpa. 24.172.in-addr.arpa. 31.172.in-addr.arpa. // 20.172.in-addr.arpa. 25.172.in-addr.arpa. 168.192.in-addr.arpa. // ``` lazy_static! { /// 10.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_10: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("10").unwrap().append_domain(&IN_ADDR_ARPA).unwrap()); static ref IN_ADDR_ARPA_172: Name = Name::from_ascii("172").unwrap().append_domain(&IN_ADDR_ARPA).unwrap(); /// 16.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_16: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("16").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 17.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_17: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("17").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 18.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_18: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("18").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 19.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_19: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("19").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 20.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_20: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("20").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 21.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_21: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("21").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 22.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_22: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("22").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 23.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_23: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("23").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 24.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_24: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("24").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 25.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_25: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("25").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 26.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_26: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("26").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 27.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_27: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("27").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 28.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_28: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("28").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 29.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_29: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("29").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 30.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_30: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("30").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 31.172.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_172_31: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("31").unwrap().append_domain(&IN_ADDR_ARPA_172).unwrap()); /// 168.192.in-addr.arpa. usage pub static ref IN_ADDR_ARPA_192_168: ZoneUsage = ZoneUsage::reverse(Name::from_ascii("168.192").unwrap().append_domain(&IN_ADDR_ARPA).unwrap()); } // example., example.com., example.net., and example.org. // // [Special-Use Domain Names](https://tools.ietf.org/html/rfc6761), RFC 6761 February, 2013 // // ```text // 6.5. Domain Name Reservation Considerations for Example Domains // // The domains "example.", "example.com.", "example.net.", // "example.org.", and any names falling within those domains, are // special in the following ways: // ``` lazy_static! { static ref COM: Label = Label::from_ascii("com").unwrap(); static ref NET: Label = Label::from_ascii("net").unwrap(); static ref ORG: Label = Label::from_ascii("org").unwrap(); static ref EXAMPLE_L: Label = Label::from_ascii("example").unwrap(); /// example. usage pub static ref EXAMPLE: ZoneUsage = ZoneUsage::example(Name::from_labels(vec![EXAMPLE_L.clone()]).unwrap()); /// example.com. usage pub static ref EXAMPLE_COM: ZoneUsage = ZoneUsage::example(Name::from_labels(vec![EXAMPLE_L.clone(), COM.clone()]).unwrap()); /// example.com. usage pub static ref EXAMPLE_NET: ZoneUsage = ZoneUsage::example(Name::from_labels(vec![EXAMPLE_L.clone(), NET.clone()]).unwrap()); /// example.com. usage pub static ref EXAMPLE_ORG: ZoneUsage = ZoneUsage::example(Name::from_labels(vec![EXAMPLE_L.clone(), ORG.clone()]).unwrap()); } // test. // // [Special-Use Domain Names](https://tools.ietf.org/html/rfc6761), RFC 6761 February, 2013 // // ```text // 6.2. Domain Name Reservation Considerations for "test." // // The domain "test.", and any names falling within ".test.", are // special in the following ways: // ``` lazy_static! { /// test. usage pub static ref TEST: ZoneUsage = ZoneUsage::test(Name::from_ascii("test.").unwrap()); } #[derive(Clone, Eq, PartialEq)] struct TrieName(Name); impl From for TrieName { fn from(n: Name) -> Self { Self(n) } } impl TrieKey for TrieName { /// Returns this name in byte form, reversed for searching from zone to local label /// /// # Panics /// /// This will panic on bad names fn encode_bytes(&self) -> Vec { let mut bytes = self.0.to_bytes().expect("bad name for trie"); bytes.reverse(); bytes } } #[derive(Clone, Eq, PartialEq)] struct TrieNameRef<'n>(&'n Name); impl<'n> From<&'n Name> for TrieNameRef<'n> { fn from(n: &'n Name) -> Self { TrieNameRef(n) } } impl<'n> TrieKey for TrieNameRef<'n> { /// Returns this name in byte form, reversed for searching from zone to local label /// /// # Panics /// /// This will panic on bad names fn encode_bytes(&self) -> Vec { let mut bytes = self.0.to_bytes().expect("bad name for trie"); bytes.reverse(); bytes } } /// A Trie of all reserved Zones pub struct UsageTrie(Trie); impl UsageTrie { #[allow(clippy::cognitive_complexity)] fn default() -> Self { let mut trie: Trie = Trie::new(); assert!(trie.insert(DEFAULT.clone().into(), &DEFAULT).is_none()); assert!(trie .insert(IN_ADDR_ARPA_10.clone().into(), &IN_ADDR_ARPA_10) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_16.clone().into(), &IN_ADDR_ARPA_172_16) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_17.clone().into(), &IN_ADDR_ARPA_172_17) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_18.clone().into(), &IN_ADDR_ARPA_172_18) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_19.clone().into(), &IN_ADDR_ARPA_172_19) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_20.clone().into(), &IN_ADDR_ARPA_172_20) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_21.clone().into(), &IN_ADDR_ARPA_172_21) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_22.clone().into(), &IN_ADDR_ARPA_172_22) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_23.clone().into(), &IN_ADDR_ARPA_172_23) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_24.clone().into(), &IN_ADDR_ARPA_172_24) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_25.clone().into(), &IN_ADDR_ARPA_172_25) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_26.clone().into(), &IN_ADDR_ARPA_172_26) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_27.clone().into(), &IN_ADDR_ARPA_172_27) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_28.clone().into(), &IN_ADDR_ARPA_172_28) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_29.clone().into(), &IN_ADDR_ARPA_172_29) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_30.clone().into(), &IN_ADDR_ARPA_172_30) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_172_31.clone().into(), &IN_ADDR_ARPA_172_31) .is_none()); assert!(trie .insert(IN_ADDR_ARPA_192_168.clone().into(), &IN_ADDR_ARPA_192_168) .is_none()); assert!(trie.insert(TEST.clone().into(), &TEST).is_none()); assert!(trie.insert(LOCALHOST.clone().into(), &LOCALHOST).is_none()); assert!(trie .insert(IN_ADDR_ARPA_127.clone().into(), &IN_ADDR_ARPA_127) .is_none()); assert!(trie .insert(IP6_ARPA_1.clone().into(), &IP6_ARPA_1) .is_none()); assert!(trie.insert(INVALID.clone().into(), &INVALID).is_none()); assert!(trie.insert(ONION.clone().into(), &ONION).is_none()); assert!(trie.insert(EXAMPLE.clone().into(), &EXAMPLE).is_none()); assert!(trie .insert(EXAMPLE_COM.clone().into(), &EXAMPLE_COM) .is_none()); assert!(trie .insert(EXAMPLE_NET.clone().into(), &EXAMPLE_NET) .is_none()); assert!(trie .insert(EXAMPLE_ORG.clone().into(), &EXAMPLE_ORG) .is_none()); Self(trie) } /// Fetches the ZoneUsage /// /// # Returns /// /// Matches the closest zone encapsulating `name`, at a minimum the default root zone usage will be returned pub fn get(&self, name: &Name) -> &'static ZoneUsage { self.0 .get_ancestor_value(&TrieName::from(name.clone())) .expect("DEFAULT root ZoneUsage should have been returned") } } lazy_static! { /// All default usage mappings pub static ref USAGE: UsageTrie = UsageTrie::default(); } #[cfg(test)] mod tests { use std::net::{Ipv4Addr, Ipv6Addr}; use super::*; #[test] fn test_root() { let name = Name::from_ascii("com.").unwrap(); let usage = USAGE.get(&name); assert!(usage.is_root()); } #[test] fn test_local_networks() { assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(9, 0, 0, 1))).name(), DEFAULT.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(10, 0, 0, 1))).name(), IN_ADDR_ARPA_10.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(11, 0, 0, 1))).name(), DEFAULT.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 16, 0, 0))).name(), IN_ADDR_ARPA_172_16.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 17, 0, 0))).name(), IN_ADDR_ARPA_172_17.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 18, 0, 0))).name(), IN_ADDR_ARPA_172_18.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 19, 0, 0))).name(), IN_ADDR_ARPA_172_19.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 20, 0, 0))).name(), IN_ADDR_ARPA_172_20.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 21, 0, 0))).name(), IN_ADDR_ARPA_172_21.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 22, 0, 0))).name(), IN_ADDR_ARPA_172_22.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 23, 0, 0))).name(), IN_ADDR_ARPA_172_23.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 24, 0, 0))).name(), IN_ADDR_ARPA_172_24.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 25, 0, 0))).name(), IN_ADDR_ARPA_172_25.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 26, 0, 0))).name(), IN_ADDR_ARPA_172_26.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 27, 0, 0))).name(), IN_ADDR_ARPA_172_27.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 28, 0, 0))).name(), IN_ADDR_ARPA_172_28.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 29, 0, 0))).name(), IN_ADDR_ARPA_172_29.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 30, 0, 0))).name(), IN_ADDR_ARPA_172_30.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 31, 0, 0))).name(), IN_ADDR_ARPA_172_31.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 15, 0, 0))).name(), DEFAULT.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(172, 32, 0, 0))).name(), DEFAULT.name() ); assert_eq!( USAGE .get(&Name::from(Ipv4Addr::new(192, 167, 255, 255))) .name(), DEFAULT.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(192, 168, 2, 3))).name(), IN_ADDR_ARPA_192_168.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(192, 169, 0, 0))).name(), DEFAULT.name() ); } #[test] fn test_example() { let name = Name::from_ascii("example.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), EXAMPLE.name()); let name = Name::from_ascii("example.com.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), EXAMPLE_COM.name()); let name = Name::from_ascii("example.net.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), EXAMPLE_NET.name()); let name = Name::from_ascii("example.org.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), EXAMPLE_ORG.name()); let name = Name::from_ascii("www.example.org.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), EXAMPLE_ORG.name()); } #[test] fn test_localhost() { let name = Name::from_ascii("localhost.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), LOCALHOST.name()); let name = Name::from_ascii("this.localhost.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), LOCALHOST.name()); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(127, 0, 0, 1))).name(), IN_ADDR_ARPA_127.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(127, 0, 0, 2))).name(), IN_ADDR_ARPA_127.name() ); assert_eq!( USAGE.get(&Name::from(Ipv4Addr::new(127, 255, 0, 0))).name(), IN_ADDR_ARPA_127.name() ); assert_eq!( USAGE .get(&Name::from(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))) .name(), IP6_ARPA_1.name() ); } #[test] fn test_invalid() { let name = Name::from_ascii("invalid.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), INVALID.name()); let name = Name::from_ascii("something.invalid.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), INVALID.name()); } #[test] fn test_onion() { let name = Name::from_ascii("onion.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), ONION.name()); let name = Name::from_ascii("2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion.") .unwrap(); // torproject.org onion let usage = USAGE.get(&name); assert_eq!(usage.name(), ONION.name()); } #[test] fn test_test() { let name = Name::from_ascii("test.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), TEST.name()); let name = Name::from_ascii("foo.bar.test.").unwrap(); let usage = USAGE.get(&name); assert_eq!(usage.name(), TEST.name()); } } trust-dns-client-0.22.0/src/serialize/binary/mod.rs000064400000000000000000000017631046102023000203220ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! Binary serialization types use crate::proto::serialize::binary; #[deprecated(note = "use [`trust_dns_client::serialize::binary::StreamHandle`] instead")] pub use self::binary::BinDecodable as BinSerializable; pub use self::binary::BinDecodable; pub use self::binary::BinDecoder; pub use self::binary::BinEncodable; pub use self::binary::BinEncoder; pub use self::binary::EncodeMode; trust-dns-client-0.22.0/src/serialize/mod.rs000064400000000000000000000013171046102023000170310ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! Contains serialization libraries for `binary` and text, `txt`. pub mod binary; pub mod txt; trust-dns-client-0.22.0/src/serialize/txt/mod.rs000064400000000000000000000015131046102023000176460ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! Text serialization types mod parse_rdata; mod rdata_parsers; mod zone; mod zone_lex; pub use self::parse_rdata::RDataParser; pub use self::zone::Parser; pub use self::zone_lex::Lexer; pub use self::zone_lex::Token; trust-dns-client-0.22.0/src/serialize/txt/parse_rdata.rs000064400000000000000000000243021046102023000213550ustar 00000000000000/* * Copyright (C) 2015-2019 Benjamin Fry * * 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. */ //! record data enum variants use crate::error::*; #[cfg(feature = "dnssec")] use crate::proto::rr::dnssec::rdata::DNSSECRData; use crate::rr::{Name, RData, RecordType}; use crate::serialize::txt::rdata_parsers::*; use crate::serialize::txt::zone_lex::Lexer; use super::Token; /// Extension on RData for text parsing pub trait RDataParser: Sized { /// Attempts to parse a streem of tokenized strs into the RData of the specified record type fn parse<'i, I: Iterator>( record_type: RecordType, tokens: I, origin: Option<&Name>, ) -> ParseResult; /// Parse RData from a string fn try_from_str(record_type: RecordType, s: &str) -> ParseResult { let mut lexer = Lexer::new(s); let mut rdata = Vec::new(); while let Some(token) = lexer.next_token()? { match token { Token::List(list) => rdata.extend(list), Token::CharData(s) => rdata.push(s), Token::EOL | Token::Blank => (), _ => { return Err(ParseError::from(format!( "unexpected token in record data: {:?}", token ))) } } } Self::parse(record_type, rdata.iter().map(AsRef::as_ref), None) } } #[warn(clippy::wildcard_enum_match_arm)] // make sure all cases are handled impl RDataParser for RData { /// Parse the RData from a set of Tokens fn parse<'i, I: Iterator>( record_type: RecordType, tokens: I, origin: Option<&Name>, ) -> ParseResult { let rdata = match record_type { RecordType::A => Self::A(a::parse(tokens)?), RecordType::AAAA => Self::AAAA(aaaa::parse(tokens)?), RecordType::ANAME => Self::ANAME(name::parse(tokens, origin)?), RecordType::ANY => return Err(ParseError::from("parsing ANY doesn't make sense")), RecordType::AXFR => return Err(ParseError::from("parsing AXFR doesn't make sense")), RecordType::CAA => caa::parse(tokens).map(Self::CAA)?, RecordType::CNAME => Self::CNAME(name::parse(tokens, origin)?), RecordType::CSYNC => csync::parse(tokens).map(Self::CSYNC)?, RecordType::HINFO => Self::HINFO(hinfo::parse(tokens)?), RecordType::HTTPS => svcb::parse(tokens).map(Self::SVCB)?, RecordType::IXFR => return Err(ParseError::from("parsing IXFR doesn't make sense")), RecordType::MX => Self::MX(mx::parse(tokens, origin)?), RecordType::NAPTR => Self::NAPTR(naptr::parse(tokens, origin)?), RecordType::NULL => Self::NULL(null::parse(tokens)?), RecordType::NS => Self::NS(name::parse(tokens, origin)?), RecordType::OPENPGPKEY => Self::OPENPGPKEY(openpgpkey::parse(tokens)?), RecordType::OPT => return Err(ParseError::from("parsing OPT doesn't make sense")), RecordType::PTR => Self::PTR(name::parse(tokens, origin)?), RecordType::SOA => Self::SOA(soa::parse(tokens, origin)?), RecordType::SRV => Self::SRV(srv::parse(tokens, origin)?), RecordType::SSHFP => Self::SSHFP(sshfp::parse(tokens)?), RecordType::SVCB => svcb::parse(tokens).map(Self::SVCB)?, RecordType::TLSA => Self::TLSA(tlsa::parse(tokens)?), RecordType::TXT => Self::TXT(txt::parse(tokens)?), RecordType::SIG => return Err(ParseError::from("parsing SIG doesn't make sense")), RecordType::DNSKEY => { return Err(ParseError::from("DNSKEY should be dynamically generated")) } RecordType::CDNSKEY => { return Err(ParseError::from("CDNSKEY should be dynamically generated")) } RecordType::KEY => return Err(ParseError::from("KEY should be dynamically generated")), #[cfg(feature = "dnssec")] RecordType::DS => Self::DNSSEC(DNSSECRData::DS(ds::parse(tokens)?)), #[cfg(not(feature = "dnssec"))] RecordType::DS => return Err(ParseError::from("DS should be dynamically generated")), RecordType::CDS => return Err(ParseError::from("CDS should be dynamically generated")), RecordType::NSEC => { return Err(ParseError::from("NSEC should be dynamically generated")) } RecordType::NSEC3 => { return Err(ParseError::from("NSEC3 should be dynamically generated")) } RecordType::NSEC3PARAM => { return Err(ParseError::from( "NSEC3PARAM should be dynamically generated", )) } RecordType::RRSIG => { return Err(ParseError::from("RRSIG should be dynamically generated")) } RecordType::TSIG => return Err(ParseError::from("TSIG is only used during AXFR")), #[allow(deprecated)] RecordType::ZERO => Self::ZERO, r @ RecordType::Unknown(..) | r => { // TODO: add a way to associate generic record types to the zone return Err(ParseError::from(ParseErrorKind::UnsupportedRecordType(r))); } }; Ok(rdata) } } #[cfg(test)] mod tests { #![allow(clippy::dbg_macro, clippy::print_stdout)] use super::*; use crate::rr::domain::Name; use crate::rr::rdata::*; use std::str::FromStr; #[test] fn test_a() { let tokens = vec!["192.168.0.1"]; let name = Name::from_str("example.com.").unwrap(); let record = RData::parse(RecordType::A, tokens.iter().map(AsRef::as_ref), Some(&name)).unwrap(); assert_eq!(record, RData::A("192.168.0.1".parse().unwrap())); } #[test] fn test_a_parse() { let data = "192.168.0.1"; let record = RData::try_from_str(RecordType::A, data).unwrap(); assert_eq!(record, RData::A("192.168.0.1".parse().unwrap())); } #[test] fn test_aaaa() { let tokens = vec!["::1"]; let name = Name::from_str("example.com.").unwrap(); let record = RData::parse( RecordType::AAAA, tokens.iter().map(AsRef::as_ref), Some(&name), ) .unwrap(); assert_eq!(record, RData::AAAA("::1".parse().unwrap())); } #[test] fn test_aaaa_parse() { let data = "::1"; let record = RData::try_from_str(RecordType::AAAA, data).unwrap(); assert_eq!(record, RData::AAAA("::1".parse().unwrap())); } #[test] fn test_ns_parse() { let data = "ns.example.com"; let record = RData::try_from_str(RecordType::NS, data).unwrap(); assert_eq!( record, RData::NS(Name::from_str("ns.example.com.").unwrap()) ); } #[test] fn test_csync() { let tokens = vec!["123", "1", "A", "NS"]; let name = Name::from_str("example.com.").unwrap(); let record = RData::parse( RecordType::CSYNC, tokens.iter().map(AsRef::as_ref), Some(&name), ) .unwrap(); assert_eq!( record, RData::CSYNC(CSYNC::new( 123, true, false, vec![RecordType::A, RecordType::NS] )) ); } #[test] fn test_csync_parse() { let data = "123 1 A NS"; let record = RData::try_from_str(RecordType::CSYNC, data).unwrap(); assert_eq!( record, RData::CSYNC(CSYNC::new( 123, true, false, vec![RecordType::A, RecordType::NS] )) ); } #[cfg(feature = "dnssec")] #[test] #[allow(deprecated)] fn test_ds() { let tokens = [ "60485", "5", "1", "2BB183AF5F22588179A53B0A", "98631FAD1A292118", ]; let name = Name::from_str("dskey.example.com.").unwrap(); let record = RData::parse( RecordType::DS, tokens.iter().map(AsRef::as_ref), Some(&name), ) .unwrap(); assert_eq!( record, RData::DNSSEC(DNSSECRData::DS(DS::new( 60485, crate::proto::rr::dnssec::Algorithm::RSASHA1, crate::proto::rr::dnssec::DigestType::SHA1, vec![ 0x2B, 0xB1, 0x83, 0xAF, 0x5F, 0x22, 0x58, 0x81, 0x79, 0xA5, 0x3B, 0x0A, 0x98, 0x63, 0x1F, 0xAD, 0x1A, 0x29, 0x21, 0x18 ] ))) ); } #[test] fn test_any() { let tokens = vec!["test"]; let name = Name::from_str("example.com.").unwrap(); let result = RData::parse( RecordType::ANY, tokens.iter().map(AsRef::as_ref), Some(&name), ); assert!(result.is_err()); } #[test] fn test_dynamically_generated() { let dynamically_generated = vec![ RecordType::DS, RecordType::CDS, RecordType::DNSKEY, RecordType::CDNSKEY, RecordType::KEY, RecordType::NSEC, RecordType::NSEC3, RecordType::NSEC3PARAM, RecordType::RRSIG, ]; let tokens = vec!["test"]; let name = Name::from_str("example.com.").unwrap(); for record_type in dynamically_generated { let result = RData::parse(record_type, tokens.iter().map(AsRef::as_ref), Some(&name)); assert!(result.is_err()); } } } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/a.rs000064400000000000000000000021031046102023000221350ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! Parser for A text form use std::net::Ipv4Addr; use std::str::FromStr; use crate::error::*; /// Parse the RData from a set of Tokens pub(crate) fn parse<'i, I: Iterator>(mut tokens: I) -> ParseResult { let address: Ipv4Addr = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("ipv4 address".to_string()))) .and_then(|s| Ipv4Addr::from_str(s).map_err(Into::into))?; Ok(address) } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/aaaa.rs000064400000000000000000000021061046102023000226030ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! Parser for AAAA text form use std::net::Ipv6Addr; use std::str::FromStr; use crate::error::*; /// Parse the RData from a set of Tokens pub(crate) fn parse<'i, I: Iterator>(mut tokens: I) -> ParseResult { let address: Ipv6Addr = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("ipv6 address".to_string()))) .and_then(|s| Ipv6Addr::from_str(s).map_err(Into::into))?; Ok(address) } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/caa.rs000064400000000000000000000066271046102023000224600ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! mail exchange, email, record use tracing::warn; use crate::proto::rr::rdata::caa; use crate::proto::rr::rdata::caa::{Property, Value}; use crate::error::*; use crate::rr::rdata::CAA; /// Parse the RData from a set of Tokens /// /// [RFC 6844, DNS Certification Authority Authorization, January 2013](https://tools.ietf.org/html/rfc6844#section-5.1) /// /// ```text /// 5.1.1. Canonical Presentation Format /// /// The canonical presentation format of the CAA record is: /// /// CAA /// /// Where: /// /// Flags: Is an unsigned integer between 0 and 255. /// /// Tag: Is a non-zero sequence of US-ASCII letters and numbers in lower /// case. /// /// Value: Is the encoding of the value field as /// specified in [RFC1035], Section 5.1. /// ``` pub(crate) fn parse<'i, I: Iterator>(mut tokens: I) -> ParseResult { let flags_str: &str = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::Message("caa flags not present")))?; let tag_str: &str = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::Message("caa tag not present")))?; let value_str: &str = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::Message("caa value not present")))?; // parse the flags let issuer_critical = { let flags = flags_str.parse::()?; if flags & 0b0111_1111 != 0 { warn!("unexpected flag values in caa (0 or 128): {}", flags); } flags & 0b1000_0000 != 0 }; // parse the tag let tag = { // unnecessary clone let tag = Property::from(tag_str.to_string()); if tag.is_unknown() { warn!("unknown tag found for caa: {:?}", tag); } tag }; // parse the value let value = { // TODO: this is a slight dup of the match logic in caa::read_value(..) match tag { Property::Issue | Property::IssueWild => { let value = caa::read_issuer(value_str.as_bytes())?; Value::Issuer(value.0, value.1) } Property::Iodef => { let url = caa::read_iodef(value_str.as_bytes())?; Value::Url(url) } Property::Unknown(_) => Value::Unknown(value_str.as_bytes().to_vec()), } }; // return the new CAA record Ok(CAA { issuer_critical, tag, value, }) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parsing() { //nocerts CAA 0 issue \";\" assert!(parse(vec!["0", "issue", ";"].into_iter()).is_ok()); // certs CAA 0 issuewild \"example.net\" assert!(parse(vec!["0", "issue", "example.net"].into_iter()).is_ok()); } } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/csync.rs000064400000000000000000000035541046102023000230470ustar 00000000000000// Copyright 2015-2021 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! CSYNC record for synchronizing information to the parent zone use std::str::FromStr; use crate::error::*; use crate::rr::rdata::CSYNC; use crate::rr::RecordType; /// Parse the RData from a set of Tokens /// /// ```text /// IN CSYNC 1 3 A NS AAAA /// IN CSYNC 66 0 MX /// ``` pub(crate) fn parse<'i, I: Iterator>(mut tokens: I) -> ParseResult { let soa_serial: u32 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("soa_serial".to_string()))) .and_then(|s| s.parse().map_err(Into::into))?; let flags: u16 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("flags".to_string()))) .and_then(|s| s.parse().map_err(Into::into))?; let immediate: bool = flags & 0b0000_0001 == 0b0000_0001; let soa_minimum: bool = flags & 0b0000_0010 == 0b0000_0010; let mut record_types: Vec = Vec::new(); for token in tokens { let record_type: RecordType = RecordType::from_str(token)?; record_types.push(record_type); } Ok(CSYNC::new(soa_serial, immediate, soa_minimum, record_types)) } #[test] fn test_parsing() { // IN CSYNC 123 3 NS assert_eq!( parse(vec!["123", "3", "NS"].into_iter()).expect("failed to parse CSYNC"), CSYNC::new(123, true, true, vec![RecordType::NS]), ); } #[test] fn test_parsing_fails() { // IN CSYNC NS assert!(parse(vec!["NS"].into_iter()).is_err()); assert!(parse(vec![].into_iter()).is_err()); } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/ds.rs000064400000000000000000000064331046102023000223350ustar 00000000000000//! Parser for DS text form use crate::error::*; use crate::proto::rr::dnssec::rdata::ds::DS; use crate::proto::rr::dnssec::{Algorithm, DigestType}; /// Parse the RData from a set of Tokens /// /// [RFC 4034, Resource Records for the DNS Security Extensions](https://datatracker.ietf.org/doc/html/rfc4034#section-5.3) /// ```text /// 5.3. The DS RR Presentation Format /// /// The presentation format of the RDATA portion is as follows: /// /// The Key Tag field MUST be represented as an unsigned decimal integer. /// /// The Algorithm field MUST be represented either as an unsigned decimal /// integer or as an algorithm mnemonic specified in Appendix A.1. /// /// The Digest Type field MUST be represented as an unsigned decimal /// integer. /// /// The Digest MUST be represented as a sequence of case-insensitive /// hexadecimal digits. Whitespace is allowed within the hexadecimal /// text. /// ``` #[allow(deprecated)] pub(crate) fn parse<'i, I: Iterator>(mut tokens: I) -> ParseResult { let tag_str: &str = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::Message("key tag not present")))?; let algorithm_str: &str = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::Message("algorithm not present")))?; let digest_type_str: &str = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::Message("digest type not present")))?; let tag: u16 = tag_str.parse()?; let algorithm = match algorithm_str { // Mnemonics from Appendix A.1. "RSAMD5" => Algorithm::Unknown(1), "DH" => Algorithm::Unknown(2), "DSA" => Algorithm::Unknown(3), "ECC" => Algorithm::Unknown(4), "RSASHA1" => Algorithm::RSASHA1, "INDIRECT" => Algorithm::Unknown(252), "PRIVATEDNS" => Algorithm::Unknown(253), "PRIVATEOID" => Algorithm::Unknown(254), _ => Algorithm::from_u8(algorithm_str.parse()?), }; let digest_type = DigestType::from_u8(digest_type_str.parse()?)?; let digest_str: String = tokens.collect(); if digest_str.is_empty() { return Err(ParseError::from(ParseErrorKind::Message( "digest not present", ))); } let mut digest = Vec::with_capacity(digest_str.len() / 2); let mut s = digest_str.as_str(); while s.len() >= 2 { if !s.is_char_boundary(2) { return Err(ParseError::from(ParseErrorKind::Message( "digest contains non hexadecimal text", ))); } let (byte_str, rest) = s.split_at(2); s = rest; let byte = u8::from_str_radix(byte_str, 16)?; digest.push(byte); } Ok(DS::new(tag, algorithm, digest_type, digest)) } #[cfg(test)] mod tests { use super::*; #[test] #[allow(deprecated)] fn test_parsing() { assert_eq!( parse("60485 5 1 2BB183AF5F22588179A53B0A 98631FAD1A292118".split(' ')).unwrap(), DS::new( 60485, Algorithm::RSASHA1, DigestType::SHA1, vec![ 0x2B, 0xB1, 0x83, 0xAF, 0x5F, 0x22, 0x58, 0x81, 0x79, 0xA5, 0x3B, 0x0A, 0x98, 0x63, 0x1F, 0xAD, 0x1A, 0x29, 0x21, 0x18 ] ) ); } } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/hinfo.rs000064400000000000000000000026401046102023000230260ustar 00000000000000// Copyright 2015-2021 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! HINFO record for storing host information use crate::error::*; use crate::rr::rdata::HINFO; /// Parse the RData from a set of Tokens /// /// ```text /// IN HINFO DEC-2060 TOPS20 /// IN HINFO VAX-11/780 UNIX /// ``` pub(crate) fn parse<'i, I: Iterator>(mut tokens: I) -> ParseResult { let cpu = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("cpu".to_string()))) .map(ToString::to_string)?; let os = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("os".to_string()))) .map(ToString::to_string)?; Ok(HINFO::new(cpu, os)) } #[test] fn test_parsing() { // IN HINFO DEC-2060 TOPS20 assert_eq!( parse(vec!["DEC-2060", "TOPS20"].into_iter()).expect("failed to parse NAPTR"), HINFO::new("DEC-2060".to_string(), "TOPS20".to_string()), ); } #[test] fn test_parsing_fails() { // IN HINFO DEC-2060 TOPS20 assert!(parse(vec!["DEC-2060"].into_iter()).is_err()); assert!(parse(vec![].into_iter()).is_err()); } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/mod.rs000064400000000000000000000024551046102023000225060ustar 00000000000000/* * Copyright (C) 2015-2019 Benjamin Fry * * 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. */ //! All record data structures and related serialization methods // TODO: these should each be it's own struct, it would make parsing and decoding a little cleaner // and also a little more ergonomic when accessing. // each of these module's has the parser for that rdata embedded, to keep the file sizes down... pub(crate) mod a; pub(crate) mod aaaa; pub(crate) mod caa; pub(crate) mod csync; #[cfg(feature = "dnssec")] pub(crate) mod ds; pub(crate) mod hinfo; pub(crate) mod mx; pub(crate) mod name; pub(crate) mod naptr; pub(crate) mod null; pub(crate) mod openpgpkey; pub(crate) mod soa; pub(crate) mod srv; pub(crate) mod sshfp; pub(crate) mod svcb; pub(crate) mod tlsa; pub(crate) mod txt; trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/mx.rs000064400000000000000000000025011046102023000223430ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! mail exchange, email, record use crate::error::*; use crate::rr::domain::Name; use crate::rr::rdata::MX; /// Parse the RData from a set of Tokens pub(crate) fn parse<'i, I: Iterator>( mut tokens: I, origin: Option<&Name>, ) -> ParseResult { let preference: u16 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("preference".to_string()))) .and_then(|s| s.parse().map_err(Into::into))?; let exchange: Name = tokens .next() .ok_or_else(|| ParseErrorKind::MissingToken("exchange".to_string()).into()) .and_then(|s| Name::parse(s, origin).map_err(ParseError::from))?; Ok(MX::new(preference, exchange)) } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/name.rs000064400000000000000000000020721046102023000226420ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! Parse for Name text form use crate::error::*; use crate::rr::domain::Name; /// Parse the RData from a set of Tokens pub(crate) fn parse<'i, I: Iterator>( mut tokens: I, origin: Option<&Name>, ) -> ParseResult { let name: Name = tokens .next() .ok_or_else(|| ParseErrorKind::MissingToken("name".to_string()).into()) .and_then(|s| Name::parse(s, origin).map_err(ParseError::from))?; Ok(name) } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/naptr.rs000064400000000000000000000072411046102023000230510ustar 00000000000000// Copyright 2015-2019 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! naptr records DDDS, RFC 3403 use std::str::FromStr; use crate::error::*; use crate::rr::rdata::naptr::{verify_flags, NAPTR}; use crate::rr::Name; /// Parse the RData from a set of Tokens /// /// ```text /// ;; order pref flags service regexp replacement /// IN NAPTR 100 50 "a" "z3950+N2L+N2C" "" cidserver.example.com. /// IN NAPTR 100 50 "a" "rcds+N2C" "" cidserver.example.com. /// IN NAPTR 100 50 "s" "http+N2L+N2C+N2R" "" www.example.com. /// ``` pub(crate) fn parse<'i, I: Iterator>( mut tokens: I, origin: Option<&Name>, ) -> ParseResult { let order: u16 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("order".to_string()))) .and_then(|s| u16::from_str(s).map_err(Into::into))?; let preference: u16 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("preference".to_string()))) .and_then(|s| u16::from_str(s).map_err(Into::into))?; let flags = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("flags".to_string()))) .map(ToString::to_string) .map(|s| s.into_bytes().into_boxed_slice())?; if !verify_flags(&flags) { return Err(ParseError::from("bad flags, must be in range [a-zA-Z0-9]")); } let service = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("service".to_string()))) .map(ToString::to_string) .map(|s| s.into_bytes().into_boxed_slice())?; let regexp = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("regexp".to_string()))) .map(ToString::to_string) .map(|s| s.into_bytes().into_boxed_slice())?; let replacement: Name = tokens .next() .ok_or_else(|| ParseErrorKind::MissingToken("replacement".to_string()).into()) .and_then(|s| Name::parse(s, origin).map_err(ParseError::from))?; Ok(NAPTR::new( order, preference, flags, service, regexp, replacement, )) } #[test] fn test_parsing() { // IN NAPTR 100 50 "a" "z3950+N2L+N2C" "" cidserver.example.com. // IN NAPTR 100 50 "a" "rcds+N2C" "" cidserver.example.com. // IN NAPTR 100 50 "s" "http+N2L+N2C+N2R" "" www.example.com. assert_eq!( parse( vec!["100", "50", "a", "z3950+N2L+N2C", "", "cidserver"].into_iter(), Some(&Name::from_str("example.com.").unwrap()) ) .expect("failed to parse NAPTR"), NAPTR::new( 100, 50, b"a".to_vec().into_boxed_slice(), b"z3950+N2L+N2C".to_vec().into_boxed_slice(), b"".to_vec().into_boxed_slice(), Name::from_str("cidserver.example.com.").unwrap() ), ); } #[test] fn test_parsing_fails() { // IN NAPTR 100 50 "a" "z3950+N2L+N2C" "" cidserver.example.com. // IN NAPTR 100 50 "a" "rcds+N2C" "" cidserver.example.com. // IN NAPTR 100 50 "s" "http+N2L+N2C+N2R" "" www.example.com. assert!(parse( vec!["100", "50", "-", "z3950+N2L+N2C", "", "cidserver"].into_iter(), Some(&Name::from_str("example.com.").unwrap()) ) .is_err()); } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/null.rs000064400000000000000000000020151046102023000226710ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! null record type, generally not used except as an internal tool for representing null data use crate::error::*; use crate::rr::rdata::NULL; /// Parse the RData from a set of Tokens #[allow(unused)] pub(crate) fn parse<'i, I: Iterator>(mut tokens: I) -> ParseResult { Err(ParseError::from(ParseErrorKind::Msg( "Parse is not implemented for NULL record".to_string(), ))) } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/openpgpkey.rs000064400000000000000000000036211046102023000241040ustar 00000000000000// Copyright 2019 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! OPENPGPKEY records for OpenPGP public keys use crate::error::*; use crate::rr::rdata::OPENPGPKEY; /// Parse the RData from a set of tokens. /// /// [RFC 7929](https://tools.ietf.org/html/rfc7929#section-2.3) /// /// ```text /// 2.3. The OPENPGPKEY RDATA Presentation Format /// /// The RDATA Presentation Format, as visible in Zone Files [RFC1035], /// consists of a single OpenPGP Transferable Public Key as defined in /// Section 11.1 of [RFC4880] encoded in base64 as defined in Section 4 /// of [RFC4648]. /// ``` pub(crate) fn parse<'i, I: Iterator>(mut tokens: I) -> ParseResult { let encoded_public_key = tokens.next().ok_or(ParseErrorKind::Message( "OPENPGPKEY public key field is missing", ))?; let public_key = data_encoding::BASE64.decode(encoded_public_key.as_bytes())?; Some(OPENPGPKEY::new(public_key)) .filter(|_| tokens.next().is_none()) .ok_or_else(|| ParseErrorKind::Message("too many fields for OPENPGPKEY").into()) } #[test] fn test_parsing() { assert!(parse(::std::iter::empty()).is_err()); assert!(parse(vec!["äöüäööüä"].into_iter()).is_err()); assert!(parse(vec!["ZmFpbGVk", "äöüäöüö"].into_iter()).is_err()); assert!(parse(vec!["dHJ1c3RfZG5zIGlzIGF3ZXNvbWU="].into_iter()) .map(|rd| rd == OPENPGPKEY::new(b"trust_dns is awesome".to_vec())) .unwrap_or(false)); assert!(parse(vec!["c2VsZi1wcmFpc2Ugc3Rpbmtz"].into_iter()) .map(|rd| rd == OPENPGPKEY::new(b"self-praise stinks".to_vec())) .unwrap_or(false)); } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/soa.rs000064400000000000000000000063021046102023000225040ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! Parser for SOA text form use std::convert::TryInto; use crate::{ error::*, rr::{domain::Name, rdata::SOA}, serialize::txt::zone, }; /// Parse the RData from a set of Tokens pub(crate) fn parse<'i, I: Iterator>( mut tokens: I, origin: Option<&Name>, ) -> ParseResult { let mname: Name = tokens .next() .ok_or_else(|| ParseErrorKind::MissingToken("mname".to_string()).into()) .and_then(|s| Name::parse(s, origin).map_err(ParseError::from))?; let rname: Name = tokens .next() .ok_or_else(|| ParseErrorKind::MissingToken("rname".to_string()).into()) .and_then(|s| Name::parse(s, origin).map_err(ParseError::from))?; let serial: u32 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("serial".to_string()))) .and_then(zone::Parser::parse_time)?; let refresh: i32 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("refresh".to_string()))) .and_then(zone::Parser::parse_time)? .try_into() .map_err(|_e| ParseError::from("refresh outside i32 range"))?; let retry: i32 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("retry".to_string()))) .and_then(zone::Parser::parse_time)? .try_into() .map_err(|_e| ParseError::from("retry outside i32 range"))?; let expire: i32 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("expire".to_string()))) .and_then(zone::Parser::parse_time)? .try_into() .map_err(|_e| ParseError::from("expire outside i32 range"))?; let minimum: u32 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("minimum".to_string()))) .and_then(zone::Parser::parse_time)?; Ok(SOA::new( mname, rname, serial, refresh, retry, expire, minimum, )) } #[test] fn test_parse() { use std::str::FromStr; let soa_tokens = vec![ "trust-dns.org.", "root.trust-dns.org.", "199609203", "8h", "120m", "7d", "24h", ]; let parsed_soa = parse( soa_tokens.into_iter(), Some(&Name::from_str("example.com.").unwrap()), ) .expect("failed to parse tokens"); let expected_soa = SOA::new( "trust-dns.org.".parse().unwrap(), "root.trust-dns.org.".parse().unwrap(), 199609203, 28800, 7200, 604800, 86400, ); assert_eq!(parsed_soa, expected_soa); } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/srv.rs000064400000000000000000000034531046102023000225400ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! service records for identify port mapping for specific services on a host use std::str::FromStr; use crate::error::*; use crate::rr::domain::Name; use crate::rr::rdata::SRV; /// Parse the RData from a set of Tokens pub(crate) fn parse<'i, I: Iterator>( mut tokens: I, origin: Option<&Name>, ) -> ParseResult { let priority: u16 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("priority".to_string()))) .and_then(|s| u16::from_str(s).map_err(Into::into))?; let weight: u16 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("weight".to_string()))) .and_then(|s| u16::from_str(s).map_err(Into::into))?; let port: u16 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("port".to_string()))) .and_then(|s| u16::from_str(s).map_err(Into::into))?; let target: Name = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("target".to_string()))) .and_then(|s| Name::parse(s, origin).map_err(ParseError::from))?; Ok(SRV::new(priority, weight, port, target)) } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/sshfp.rs000064400000000000000000000122301046102023000230420ustar 00000000000000// Copyright 2019 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! SSHFP records for SSH public key fingerprints use crate::error::*; use crate::rr::rdata::{sshfp, SSHFP}; /// Parse the RData from a set of Tokens /// /// [RFC 4255](https://tools.ietf.org/html/rfc4255#section-3.2) /// /// ```text /// 3.2. Presentation Format of the SSHFP RR /// /// The RDATA of the presentation format of the SSHFP resource record /// consists of two numbers (algorithm and fingerprint type) followed by /// the fingerprint itself, presented in hex, e.g.: /// /// host.example. SSHFP 2 1 123456789abcdef67890123456789abcdef67890 /// /// The use of mnemonics instead of numbers is not allowed. /// ``` pub(crate) fn parse<'i, I: Iterator>(mut tokens: I) -> ParseResult { fn missing_field>(field: &str) -> E { ParseErrorKind::Msg(format!("SSHFP {} field missing", field)).into() } let (algorithm, fingerprint_type) = { let mut parse_u8 = |field: &str| { tokens .next() .ok_or_else(|| missing_field(field)) .and_then(|t| t.parse::().map_err(ParseError::from)) }; ( parse_u8("algorithm")?.into(), parse_u8("fingerprint type")?.into(), ) }; let fingerprint = sshfp::HEX.decode( tokens .next() .filter(|fp| !fp.is_empty()) .ok_or_else(|| missing_field::("fingerprint"))? .as_bytes(), )?; Some(SSHFP::new(algorithm, fingerprint_type, fingerprint)) .filter(|_| tokens.next().is_none()) .ok_or_else(|| ParseErrorKind::Message("too many fields for SSHFP").into()) } #[test] fn test_parsing() { assert!(parse(::std::iter::empty()).is_err()); assert!(parse(vec!["51", "13"].into_iter()).is_err()); assert!(parse(vec!["1", "-1"].into_iter()).is_err()); assert!(parse(vec!["1", "1", "abcd", "foo"].into_iter()).is_err()); use crate::rr::rdata::sshfp::Algorithm::*; use crate::rr::rdata::sshfp::FingerprintType::*; use crate::rr::rdata::sshfp::{Algorithm, FingerprintType}; fn test_parsing(input: Vec<&str>, a: Algorithm, ft: FingerprintType, f: &[u8]) { assert!(parse(input.into_iter()) .map(|rd| rd == SSHFP::new(a, ft, f.to_vec())) .unwrap_or(false)); } test_parsing( vec!["1", "1", "dd465c09cfa51fb45020cc83316fff21b9ec74ac"], RSA, SHA1, &[ 221, 70, 92, 9, 207, 165, 31, 180, 80, 32, 204, 131, 49, 111, 255, 33, 185, 236, 116, 172, ], ); test_parsing( vec![ "1", "2", "b049f950d1397b8fee6a61e4d14a9acdc4721e084eff5460bbed80cfaa2ce2cb", ], RSA, SHA256, &[ 176, 73, 249, 80, 209, 57, 123, 143, 238, 106, 97, 228, 209, 74, 154, 205, 196, 114, 30, 8, 78, 255, 84, 96, 187, 237, 128, 207, 170, 44, 226, 203, ], ); test_parsing( vec!["2", "1", "3b6ba6110f5ffcd29469fc1ec2ee25d61718badd"], DSA, SHA1, &[ 59, 107, 166, 17, 15, 95, 252, 210, 148, 105, 252, 30, 194, 238, 37, 214, 23, 24, 186, 221, ], ); test_parsing( vec![ "2", "2", "f9b8a6a460639306f1b38910456a6ae1018a253c47ecec12db77d7a0878b4d83", ], DSA, SHA256, &[ 249, 184, 166, 164, 96, 99, 147, 6, 241, 179, 137, 16, 69, 106, 106, 225, 1, 138, 37, 60, 71, 236, 236, 18, 219, 119, 215, 160, 135, 139, 77, 131, ], ); test_parsing( vec!["3", "1", "c64607a28c5300fec1180b6e417b922943cffcdd"], ECDSA, SHA1, &[ 198, 70, 7, 162, 140, 83, 0, 254, 193, 24, 11, 110, 65, 123, 146, 41, 67, 207, 252, 221, ], ); test_parsing( vec![ "3", "2", "821eb6c1c98d9cc827ab7f456304c0f14785b7008d9e8646a8519de80849afc7", ], ECDSA, SHA256, &[ 130, 30, 182, 193, 201, 141, 156, 200, 39, 171, 127, 69, 99, 4, 192, 241, 71, 133, 183, 0, 141, 158, 134, 70, 168, 81, 157, 232, 8, 73, 175, 199, ], ); test_parsing( vec!["4", "1", "6b6f6165636874657266696e6765727072696e74"], Ed25519, SHA1, &[ 107, 111, 97, 101, 99, 104, 116, 101, 114, 102, 105, 110, 103, 101, 114, 112, 114, 105, 110, 116, ], ); test_parsing( vec![ "4", "2", "a87f1b687ac0e57d2a081a2f282672334d90ed316d2b818ca9580ea384d92401", ], Ed25519, SHA256, &[ 168, 127, 27, 104, 122, 192, 229, 125, 42, 8, 26, 47, 40, 38, 114, 51, 77, 144, 237, 49, 109, 43, 129, 140, 169, 88, 14, 163, 132, 217, 36, 1, ], ); } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/svcb.rs000064400000000000000000000404011046102023000226550ustar 00000000000000// Copyright 2015-2021 Benjamin Fry // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! SVCB records in presentation format use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::FromStr; use crate::error::*; use crate::rr::rdata::svcb::*; use crate::rr::Name; use crate::serialize::txt::{Lexer, Token}; /// [draft-ietf-dnsop-svcb-https-03 SVCB and HTTPS RRs for DNS, February 2021](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-2.2) /// /// ```text /// 2.1. Zone file presentation format /// /// The presentation format of the record is: /// /// Name TTL IN SVCB SvcPriority TargetName SvcParams /// /// The SVCB record is defined specifically within the Internet ("IN") /// Class ([RFC1035]). /// /// SvcPriority is a number in the range 0-65535, TargetName is a domain /// name, and the SvcParams are a whitespace-separated list, with each /// SvcParam consisting of a SvcParamKey=SvcParamValue pair or a /// standalone SvcParamKey. SvcParamKeys are subject to IANA control /// (Section 14.3). /// /// Each SvcParamKey SHALL appear at most once in the SvcParams. In /// presentation format, SvcParamKeys are lower-case alphanumeric /// strings. Key names should contain 1-63 characters from the ranges /// "a"-"z", "0"-"9", and "-". In ABNF [RFC5234], /// /// alpha-lc = %x61-7A ; a-z /// SvcParamKey = 1*63(alpha-lc / DIGIT / "-") /// SvcParam = SvcParamKey ["=" SvcParamValue] /// SvcParamValue = char-string /// value = *OCTET /// /// The SvcParamValue is parsed using the character-string decoding /// algorithm (Appendix A), producing a "value". The "value" is then /// validated and converted into wire-format in a manner specific to each /// key. /// /// When the "=" is omitted, the "value" is interpreted as empty. /// /// Unrecognized keys are represented in presentation format as /// "keyNNNNN" where NNNNN is the numeric value of the key type without /// leading zeros. A SvcParam in this form SHALL be parsed as specified /// above, and the decoded "value" SHALL be used as its wire format /// encoding. /// /// For some SvcParamKeys, the "value" corresponds to a list or set of /// items. Presentation formats for such keys SHOULD use a comma- /// separated list (Appendix A.1). /// /// SvcParams in presentation format MAY appear in any order, but keys /// MUST NOT be repeated. /// ``` pub(crate) fn parse<'i, I: Iterator>(mut tokens: I) -> ParseResult { // SvcPriority let svc_priority: u16 = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("SvcPriority".to_string()))) .and_then(|s| s.parse().map_err(Into::into))?; // svcb target let target_name: Name = tokens .next() .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("Target".to_string()))) .and_then(|s| Name::from_str(s).map_err(ParseError::from))?; // Loop over all of the let mut svc_params = Vec::new(); for token in tokens { // first need to split the key and (optional) value let mut key_value = token.splitn(2, '='); let key = key_value.next().ok_or_else(|| { ParseError::from(ParseErrorKind::MissingToken( "SVCB SvcbParams missing".to_string(), )) })?; // get the value, and remove any quotes let value = key_value.next(); svc_params.push(into_svc_param(key, value)?); } Ok(SVCB::new(svc_priority, target_name, svc_params)) } // first take the param and convert to fn into_svc_param( key: &str, value: Option<&str>, ) -> Result<(SvcParamKey, SvcParamValue), ParseError> { let key = SvcParamKey::from_str(key)?; let value = parse_value(key, value)?; Ok((key, value)) } fn parse_value(key: SvcParamKey, value: Option<&str>) -> Result { match key { SvcParamKey::Mandatory => parse_mandatory(value), SvcParamKey::Alpn => parse_alpn(value), SvcParamKey::NoDefaultAlpn => parse_no_default_alpn(value), SvcParamKey::Port => parse_port(value), SvcParamKey::Ipv4Hint => parse_ipv4_hint(value), SvcParamKey::EchConfig => parse_ech_config(value), SvcParamKey::Ipv6Hint => parse_ipv6_hint(value), SvcParamKey::Key(_) => parse_unknown(value), SvcParamKey::Key65535 | SvcParamKey::Unknown(_) => { Err(ParseError::from(ParseErrorKind::Message( "Bad Key type or unsupported, see generic key option, e.g. key1234", ))) } } } fn parse_char_data(value: &str) -> Result { let mut lex = Lexer::new(value); let ch_data = lex .next_token()? .ok_or_else(|| ParseError::from(ParseErrorKind::Message("expected character data")))?; match ch_data { Token::CharData(data) => Ok(data), _ => Err(ParseError::from(ParseErrorKind::Message( "expected character data", ))), } } /// [draft-ietf-dnsop-svcb-https-03 SVCB and HTTPS RRs for DNS, February 2021](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-7) /// ```text /// The presentation "value" SHALL be a comma-separated list /// (Appendix A.1) of one or more valid SvcParamKeys, either by their /// registered name or in the unknown-key format (Section 2.1). Keys MAY /// appear in any order, but MUST NOT appear more than once. For self- /// consistency (Section 2.4.3), listed keys MUST also appear in the /// SvcParams. /// /// To enable simpler parsing, this SvcParamValue MUST NOT contain escape /// sequences. /// /// For example, the following is a valid list of SvcParams: /// /// echconfig=... key65333=ex1 key65444=ex2 mandatory=key65444,echconfig /// ``` /// /// Currently this does not validate that the mandatory section matches the other keys fn parse_mandatory(value: Option<&str>) -> Result { let value = value.ok_or_else(|| { ParseError::from(ParseErrorKind::Message( "expected at least one Mandatory field", )) })?; let mandatories = parse_list::(value)?; Ok(SvcParamValue::Mandatory(Mandatory(mandatories))) } /// [draft-ietf-dnsop-svcb-https-03 SVCB and HTTPS RRs for DNS, February 2021](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-6.1) /// ```text /// ALPNs are identified by their registered "Identification Sequence" /// ("alpn-id"), which is a sequence of 1-255 octets. /// /// alpn-id = 1*255OCTET /// /// The presentation "value" SHALL be a comma-separated list /// (Appendix A.1) of one or more "alpn-id"s. /// ``` /// /// This does not currently check to see if the ALPN code is legitimate fn parse_alpn(value: Option<&str>) -> Result { let value = value.ok_or_else(|| { ParseError::from(ParseErrorKind::Message("expected at least one ALPN code")) })?; let alpns = parse_list::(value).expect("infallible"); Ok(SvcParamValue::Alpn(Alpn(alpns))) } /// [draft-ietf-dnsop-svcb-https-03 SVCB and HTTPS RRs for DNS, February 2021](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-6.1) /// ```text /// For "no-default-alpn", the presentation and wire format values MUST /// be empty. When "no-default-alpn" is specified in an RR, "alpn" must /// also be specified in order for the RR to be "self-consistent" /// (Section 2.4.3). /// ``` fn parse_no_default_alpn(value: Option<&str>) -> Result { if value.is_some() { return Err(ParseErrorKind::Message("no value expected for NoDefaultAlpn").into()); } Ok(SvcParamValue::NoDefaultAlpn) } /// [draft-ietf-dnsop-svcb-https-03 SVCB and HTTPS RRs for DNS, February 2021](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-6.2) /// ```text /// The presentation "value" of the SvcParamValue is a single decimal /// integer between 0 and 65535 in ASCII. Any other "value" (e.g. an /// empty value) is a syntax error. To enable simpler parsing, this /// SvcParam MUST NOT contain escape sequences. /// ``` fn parse_port(value: Option<&str>) -> Result { let value = value.ok_or_else(|| { ParseError::from(ParseErrorKind::Message("a port number for the port option")) })?; let value = parse_char_data(value)?; let port = u16::from_str(&value)?; Ok(SvcParamValue::Port(port)) } /// [draft-ietf-dnsop-svcb-https-03 SVCB and HTTPS RRs for DNS, February 2021](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-6.4) /// ```text /// The presentation "value" SHALL be a comma-separated list /// (Appendix A.1) of one or more IP addresses of the appropriate family /// in standard textual format [RFC5952]. To enable simpler parsing, /// this SvcParamValue MUST NOT contain escape sequences. /// ``` fn parse_ipv4_hint(value: Option<&str>) -> Result { let value = value.ok_or_else(|| { ParseError::from(ParseErrorKind::Message("expected at least one ipv4 hint")) })?; let hints = parse_list::(value)?; Ok(SvcParamValue::Ipv4Hint(IpHint(hints))) } /// As the documentation states, the presentation format (what this function reads) must be a BASE64 encoded string. /// trust-dns will decode the BASE64 during parsing and stores the internal data as the raw bytes. /// /// [draft-ietf-dnsop-svcb-https-03 SVCB and HTTPS RRs for DNS, February 2021](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-9) /// ```text /// In presentation format, the value is a /// single ECHConfigs encoded in Base64 [base64]. Base64 is used here to /// simplify integration with TLS server software. To enable simpler /// parsing, this SvcParam MUST NOT contain escape sequences. /// ``` fn parse_ech_config(value: Option<&str>) -> Result { let value = value.ok_or_else(|| { ParseError::from(ParseErrorKind::Message( "expected a base64 encoded string for EchConfig", )) })?; let value = parse_char_data(value)?; let ech_config_bytes = data_encoding::BASE64.decode(value.as_bytes())?; Ok(SvcParamValue::EchConfig(EchConfig(ech_config_bytes))) } /// [draft-ietf-dnsop-svcb-https-03 SVCB and HTTPS RRs for DNS, February 2021](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-6.4) /// ```text /// The presentation "value" SHALL be a comma-separated list /// (Appendix A.1) of one or more IP addresses of the appropriate family /// in standard textual format [RFC5952]. To enable simpler parsing, /// this SvcParamValue MUST NOT contain escape sequences. /// ``` fn parse_ipv6_hint(value: Option<&str>) -> Result { let value = value.ok_or_else(|| { ParseError::from(ParseErrorKind::Message("expected at least one ipv6 hint")) })?; let hints = parse_list::(value)?; Ok(SvcParamValue::Ipv6Hint(IpHint(hints))) } /// [draft-ietf-dnsop-svcb-https-03 SVCB and HTTPS RRs for DNS, February 2021](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-03#section-2.1) /// ```text /// Unrecognized keys are represented in presentation format as /// "keyNNNNN" where NNNNN is the numeric value of the key type without /// leading zeros. A SvcParam in this form SHALL be parsed as specified /// above, and the decoded "value" SHALL be used as its wire format /// encoding. /// /// For some SvcParamKeys, the "value" corresponds to a list or set of /// items. Presentation formats for such keys SHOULD use a comma- /// separated list (Appendix A.1). /// /// SvcParams in presentation format MAY appear in any order, but keys /// MUST NOT be repeated. /// ``` fn parse_unknown(value: Option<&str>) -> Result { let unknown: Vec = if let Some(value) = value { value.as_bytes().to_vec() } else { Vec::new() }; Ok(SvcParamValue::Unknown(Unknown(unknown))) } fn parse_list(value: &str) -> Result, ParseError> where T: FromStr, T::Err: Into, { let mut result = Vec::new(); let values = value.trim_end_matches(',').split(','); for value in values { let value = parse_char_data(value)?; let value = T::from_str(&value).map_err(|e| e.into())?; result.push(value); } Ok(result) } #[cfg(test)] mod tests { use trust_dns_proto::rr::RData; use crate::rr::DNSClass; use crate::serialize::txt::{Lexer, Parser}; use super::*; // this assumes that only a single record is parsed // TODO: make Parser return an iterator over all records in a stream. fn parse_record(txt: &str) -> SVCB { let lex = Lexer::new(txt); let mut parser = Parser::new(); let records = parser .parse(lex, Some(Name::root()), Some(DNSClass::IN)) .expect("failed to parse record") .1; let record_set = records.into_iter().next().expect("no record found").1; record_set .into_iter() .next() .unwrap() .data() .and_then(RData::as_svcb) .expect("Not an SVCB record") .clone() } #[test] fn test_parsing() { let svcb = parse_record("crypto.cloudflare.com. 299 IN SVCB 1 . alpn=h2, ipv4hint=162.159.135.79,162.159.136.79, echconfig=\"/gkAQwATY2xvdWRmbGFyZS1lc25pLmNvbQAgUbBtC3UeykwwE6C87TffqLJ/1CeaAvx3iESGyds85l8AIAAEAAEAAQAAAAA=\" ipv6hint=2606:4700:7::a29f:874f,2606:4700:7::a29f:884f,"); assert_eq!(svcb.svc_priority(), 1); assert_eq!(*svcb.target_name(), Name::root()); let mut params = svcb.svc_params().iter(); // alpn let param = params.next().expect("not alpn"); assert_eq!(param.0, SvcParamKey::Alpn); assert_eq!(param.1.as_alpn().expect("not alpn").0, &["h2"]); // ipv4 hint let param = params.next().expect("ipv4hint"); assert_eq!(SvcParamKey::Ipv4Hint, param.0); assert_eq!( param.1.as_ipv4_hint().expect("ipv4hint").0, &[ Ipv4Addr::from([162, 159, 135, 79]), Ipv4Addr::from([162, 159, 136, 79]) ] ); // echconfig let param = params.next().expect("echconfig"); assert_eq!(SvcParamKey::EchConfig, param.0); assert_eq!( param.1.as_ech_config().expect("echconfig").0, data_encoding::BASE64.decode("/gkAQwATY2xvdWRmbGFyZS1lc25pLmNvbQAgUbBtC3UeykwwE6C87TffqLJ/1CeaAvx3iESGyds85l8AIAAEAAEAAQAAAAA=".as_bytes()).unwrap() ); // ipv6 hint let param = params.next().expect("ipv6hint"); assert_eq!(SvcParamKey::Ipv6Hint, param.0); assert_eq!( param.1.as_ipv6_hint().expect("ipv6hint").0, &[ Ipv6Addr::from([0x2606, 0x4700, 0x7, 0, 0, 0, 0xa29f, 0x874f]), Ipv6Addr::from([0x2606, 0x4700, 0x7, 0, 0, 0, 0xa29f, 0x884f]) ] ); } #[test] fn test_parse_display() { let svcb = parse_record("crypto.cloudflare.com. 299 IN SVCB 1 . alpn=h2, ipv4hint=162.159.135.79,162.159.136.79, echconfig=\"/gkAQwATY2xvdWRmbGFyZS1lc25pLmNvbQAgUbBtC3UeykwwE6C87TffqLJ/1CeaAvx3iESGyds85l8AIAAEAAEAAQAAAAA=\" ipv6hint=2606:4700:7::a29f:874f,2606:4700:7::a29f:884f,"); let svcb_display = svcb.to_string(); // add back the name, etc... let svcb_display = format!("crypto.cloudflare.com. 299 IN SVCB {}", svcb_display); let svcb_display = parse_record(&svcb_display); assert_eq!(svcb, svcb_display); } /// sanity check for https #[test] fn test_parsing_https() { let svcb = parse_record("crypto.cloudflare.com. 299 IN HTTPS 1 . alpn=h2, ipv4hint=162.159.135.79,162.159.136.79, echconfig=\"/gkAQwATY2xvdWRmbGFyZS1lc25pLmNvbQAgUbBtC3UeykwwE6C87TffqLJ/1CeaAvx3iESGyds85l8AIAAEAAEAAQAAAAA=\" ipv6hint=2606:4700:7::a29f:874f,2606:4700:7::a29f:884f,"); assert_eq!(svcb.svc_priority(), 1); assert_eq!(*svcb.target_name(), Name::root()); } } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/tlsa.rs000064400000000000000000000064351046102023000226740ustar 00000000000000// Copyright 2015-2017 Benjamin Fry // Copyright 2017 Google LLC. // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. //! tlsa records for storing TLS authentication records use crate::error::*; use crate::rr::rdata::tlsa::CertUsage; use crate::rr::rdata::{sshfp, TLSA}; fn to_u8(data: &str) -> ParseResult { data.parse().map_err(ParseError::from) } /// Parse the RData from a set of Tokens /// /// [RFC 6698, DNS-Based Authentication for TLS](https://tools.ietf.org/html/rfc6698#section-2.2) /// /// ```text /// 2.2. TLSA RR Presentation Format /// /// The presentation format of the RDATA portion (as defined in /// [RFC1035]) is as follows: /// /// o The certificate usage field MUST be represented as an 8-bit /// unsigned integer. /// /// o The selector field MUST be represented as an 8-bit unsigned /// integer. /// /// o The matching type field MUST be represented as an 8-bit unsigned /// integer. /// /// o The certificate association data field MUST be represented as a /// string of hexadecimal characters. Whitespace is allowed within /// the string of hexadecimal characters, as described in [RFC1035]. /// ``` pub(crate) fn parse<'i, I: Iterator>(tokens: I) -> ParseResult { let mut iter = tokens; let token: &str = iter .next() .ok_or_else(|| ParseError::from(ParseErrorKind::Message("TLSA usage field missing")))?; let usage = CertUsage::from(to_u8(token)?); let token = iter .next() .ok_or(ParseErrorKind::Message("TLSA selector field missing"))?; let selector = to_u8(token)?.into(); let token = iter .next() .ok_or(ParseErrorKind::Message("TLSA matching field missing"))?; let matching = to_u8(token)?.into(); // these are all in hex: "a string of hexadecimal characters" // aside: personally I find it funny that the other fields are decimal, while this is hex encoded... let cert_data = iter.fold(String::new(), |mut cert_data, data| { cert_data.push_str(data); cert_data }); let cert_data = sshfp::HEX.decode(cert_data.as_bytes())?; if !cert_data.is_empty() { Ok(TLSA::new(usage, selector, matching, cert_data)) } else { Err(ParseErrorKind::Message("TLSA data field missing").into()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parsing() { assert!(parse( vec![ "0", "0", "1", "d2abde240d7cd3ee6b4b28c54df034b9", "7983a1d16e8a410e4561cb106618e971", ] .into_iter() ) .is_ok()); assert!(parse( vec![ "1", "1", "2", "92003ba34942dc74152e2f2c408d29ec", "a5a520e7f2e06bb944f4dca346baf63c", "1b177615d466f6c4b71c216a50292bd5", "8c9ebdd2f74e38fe51ffd48c43326cbc", ] .into_iter() ) .is_ok()); } } trust-dns-client-0.22.0/src/serialize/txt/rdata_parsers/txt.rs000064400000000000000000000017271046102023000225470ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ //! text records for storing arbitrary data use crate::error::*; use crate::rr::rdata::TXT; /// Parse the RData from a set of Tokens #[allow(clippy::unnecessary_wraps)] pub(crate) fn parse<'i, I: Iterator>(tokens: I) -> ParseResult { let txt_data: Vec = tokens.map(ToString::to_string).collect(); Ok(TXT::new(txt_data)) } trust-dns-client-0.22.0/src/serialize/txt/zone.rs000064400000000000000000000473201046102023000200500ustar 00000000000000/* * Copyright (C) 2015 Benjamin Fry * * 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. */ use std::collections::BTreeMap; use std::str::FromStr; use crate::error::*; use crate::rr::{DNSClass, LowerName, Name, RData, Record, RecordSet, RecordType, RrKey}; use crate::serialize::txt::parse_rdata::RDataParser; use crate::serialize::txt::zone_lex::{Lexer, Token}; /// ```text /// 5. ZONE FILES /// /// Zone files are text files that contain RRs in text form. Since the /// contents of a zone can be expressed in the form of a list of RRs a /// Zone File is most often used to define a zone, though it can be used /// to list a cache's contents. Hence, this section first discusses the /// format of RRs in a Zone File, and then the special considerations when /// a Zone File is used to create a zone in some name server. /// /// 5.1. Format /// /// The format of these files is a sequence of entries. Entries are /// predominantly line-oriented, though parentheses can be used to continue /// a list of items across a line boundary, and text literals can contain /// CRLF within the text. Any combination of tabs and spaces act as a /// delimiter between the separate items that make up an entry. The end of /// any line in the Zone File can end with a comment. The comment starts /// with a ";" (semicolon). /// /// The following entries are defined: /// /// [] /// /// $ORIGIN [] /// /// $INCLUDE [] [] /// /// [] /// /// [] /// /// Blank lines, with or without comments, are allowed anywhere in the file. /// /// Two control entries are defined: $ORIGIN and $INCLUDE. $ORIGIN is /// followed by a domain name, and resets the current origin for relative /// domain names to the stated name. $INCLUDE inserts the named file into /// the current file, and may optionally specify a domain name that sets the /// relative domain name origin for the included file. $INCLUDE may also /// have a comment. Note that a $INCLUDE entry never changes the relative /// origin of the parent file, regardless of changes to the relative origin /// made within the included file. /// /// The last two forms represent RRs. If an entry for an RR begins with a /// blank, then the RR is assumed to be owned by the last stated owner. If /// an RR entry begins with a , then the owner name is reset. /// /// contents take one of the following forms: /// /// [] [] /// /// [] [] /// /// The RR begins with optional TTL and class fields, followed by a type and /// RDATA field appropriate to the type and class. Class and type use the /// standard mnemonics, TTL is a decimal integer. Omitted class and TTL /// values are default to the last explicitly stated values. Since type and /// class mnemonics are disjoint, the parse is unique. (Note that this /// order is different from the order used in examples and the order used in /// the actual RRs; the given order allows easier parsing and defaulting.) /// /// s make up a large share of the data in the Zone File. /// The labels in the domain name are expressed as character strings and /// separated by dots. Quoting conventions allow arbitrary characters to be /// stored in domain names. Domain names that end in a dot are called /// absolute, and are taken as complete. Domain names which do not end in a /// dot are called relative; the actual domain name is the concatenation of /// the relative part with an origin specified in a $ORIGIN, $INCLUDE, or as /// an argument to the Zone File loading routine. A relative name is an /// error when no origin is available. /// /// is expressed in one or two ways: as a contiguous set /// of characters without interior spaces, or as a string beginning with a " /// and ending with a ". Inside a " delimited string any character can /// occur, except for a " itself, which must be quoted using \ (back slash). /// /// Because these files are text files several special encodings are /// necessary to allow arbitrary data to be loaded. In particular: /// /// of the root. /// /// @ A free standing @ is used to denote the current origin. /// /// \X where X is any character other than a digit (0-9), is /// used to quote that character so that its special meaning /// does not apply. For example, "\." can be used to place /// a dot character in a label. /// /// \DDD where each D is a digit is the octet corresponding to /// the decimal number described by DDD. The resulting /// octet is assumed to be text and is not checked for /// special meaning. /// /// ( ) Parentheses are used to group data that crosses a line /// boundary. In effect, line terminations are not /// recognized within parentheses. /// /// ; Semicolon is used to start a comment; the remainder of /// the line is ignored. /// ``` #[derive(Clone, Copy, Default)] pub struct Parser; impl Parser { /// Returns a new Zone file parser pub fn new() -> Self { Parser } /// Parse a file from the Lexer /// /// # Return /// /// A pair of the Zone origin name and a map of all Keys to RecordSets pub fn parse( &mut self, lexer: Lexer<'_>, origin: Option, class: Option, ) -> ParseResult<(Name, BTreeMap)> { let mut lexer = lexer; let mut records: BTreeMap = BTreeMap::new(); let mut origin: Option = origin; let mut class: Option = class; let mut current_name: Option = None; let mut rtype: Option = None; let mut ttl: Option = None; let mut state = State::StartLine; while let Some(t) = lexer.next_token()? { state = match state { State::StartLine => { // current_name is not reset on the next line b/c it might be needed from the previous rtype = None; match t { // if Dollar, then $INCLUDE or $ORIGIN Token::Include => { return Err(ParseError::from(ParseErrorKind::Message("The parser does not support $INCLUDE. Consider inlining file before parsing"))) }, Token::Origin => State::Origin, Token::Ttl => State::Ttl, // if CharData, then Name then ttl_class_type Token::CharData(data) => { current_name = Some(Name::parse(&data, origin.as_ref())?); State::TtlClassType } // @ is a placeholder for specifying the current origin Token::At => { current_name = origin.clone(); // TODO a COW or RC would reduce copies... State::TtlClassType } // if blank, then nothing or ttl_class_type Token::Blank => State::TtlClassType, Token::EOL => State::StartLine, // probably a comment _ => return Err(ParseErrorKind::UnexpectedToken(t).into()), } } State::Ttl => match t { Token::CharData(data) => { ttl = Some(Self::parse_time(&data)?); State::StartLine } _ => return Err(ParseErrorKind::UnexpectedToken(t).into()), }, State::Origin => { match t { Token::CharData(data) => { // TODO an origin was specified, should this be legal? definitely confusing... origin = Some(Name::parse(&data, None)?); State::StartLine } _ => return Err(ParseErrorKind::UnexpectedToken(t).into()), } } State::Include => return Err(ParseError::from(ParseErrorKind::Message( "The parser does not support $INCLUDE. Consider inlining file before parsing", ))), State::TtlClassType => { match t { // if number, TTL // Token::Number(ref num) => ttl = Some(*num), // One of Class or Type (these cannot be overlapping!) Token::CharData(mut data) => { // if it's a number it's a ttl let result: ParseResult = Self::parse_time(&data); if result.is_ok() { ttl = result.ok(); State::TtlClassType // hm, should this go to just ClassType? } else { // if can parse DNSClass, then class data.make_ascii_uppercase(); let result = DNSClass::from_str(&data); if result.is_ok() { class = result.ok(); State::TtlClassType } else { // if can parse RecordType, then RecordType rtype = Some(RecordType::from_str(&data)?); State::Record(vec![]) } } } // could be nothing if started with blank and is a comment, i.e. EOL Token::EOL => { State::StartLine // next line } _ => return Err(ParseErrorKind::UnexpectedToken(t).into()), } } State::Record(record_parts) => { // b/c of ownership rules, perhaps, just collect all the RData components as a list of // tokens to pass into the processor match t { Token::EOL => { Self::flush_record( record_parts, &origin, ¤t_name, rtype, &mut ttl, class, &mut records, )?; State::StartLine } Token::CharData(part) => { let mut record_parts = record_parts; record_parts.push(part); State::Record(record_parts) } // TODO: we should not tokenize the list... Token::List(list) => { let mut record_parts = record_parts; record_parts.extend(list); State::Record(record_parts) } _ => return Err(ParseErrorKind::UnexpectedToken(t).into()), } } } } //Extra flush at the end for the case of missing endline if let State::Record(record_parts) = state { Self::flush_record( record_parts, &origin, ¤t_name, rtype, &mut ttl, class, &mut records, )?; } // // build the Authority and return. let origin = origin.ok_or_else(|| { ParseError::from(ParseErrorKind::Message("$ORIGIN was not specified")) })?; Ok((origin, records)) } fn flush_record( record_parts: Vec, origin: &Option, current_name: &Option, rtype: Option, ttl: &mut Option, class: Option, records: &mut BTreeMap, ) -> ParseResult<()> { // call out to parsers for difference record types // all tokens as part of the Record should be chardata... let rtype = rtype.ok_or_else(|| { ParseError::from(ParseErrorKind::Message("record type not specified")) })?; let rdata = RData::parse( rtype, record_parts.iter().map(AsRef::as_ref), origin.as_ref(), )?; // verify that we have everything we need for the record let mut record = Record::new(); // TODO COW or RC would reduce mem usage, perhaps Name should have an intern()... // might want to wait until RC.weak() stabilizes, as that would be needed for global // memory where you want record.set_name(current_name.clone().ok_or_else(|| { ParseError::from(ParseErrorKind::Message("record name not specified")) })?); record.set_rr_type(rtype); record.set_dns_class(class.ok_or_else(|| { ParseError::from(ParseErrorKind::Message("record class not specified")) })?); // slightly annoying, need to grab the TTL, then move rdata into the record, // then check the Type again and have custom add logic. match rtype { RecordType::SOA => { // TTL for the SOA is set internally... // expire is for the SOA, minimum is default for records if let RData::SOA(ref soa) = rdata { // TODO, this looks wrong, get_expire() should be get_minimum(), right? record.set_ttl(soa.expire() as u32); // the spec seems a little inaccurate with u32 and i32 if ttl.is_none() { *ttl = Some(soa.minimum()); } // TODO: should this only set it if it's not set? } else { let msg = format!("Invalid RData here, expected SOA: {:?}", rdata); return ParseResult::Err(ParseError::from(ParseErrorKind::Msg(msg))); } } _ => { record.set_ttl(ttl.ok_or_else(|| { ParseError::from(ParseErrorKind::Message("record ttl not specified")) })?); } } // TODO: validate record, e.g. the name of SRV record allows _ but others do not. // move the rdata into record... record.set_data(Some(rdata)); // add to the map let key = RrKey::new(LowerName::new(record.name()), record.rr_type()); match rtype { RecordType::SOA => { let set = record.into(); if records.insert(key, set).is_some() { return Err(ParseErrorKind::Message("SOA is already specified").into()); } } _ => { // add a Vec if it's not there, then add the record to the list let set = records .entry(key) .or_insert_with(|| RecordSet::new(record.name(), record.rr_type(), 0)); set.insert(record, 0); } } Ok(()) } /// parses the string following the rules from: /// (NXCaching RFC) and /// /// /// default is seconds /// #s = seconds = # x 1 seconds (really!) /// #m = minutes = # x 60 seconds /// #h = hours = # x 3600 seconds /// #d = day = # x 86400 seconds /// #w = week = # x 604800 seconds /// /// returns the result of the parsing or and error /// /// # Example /// ``` /// use trust_dns_client::serialize::txt::Parser; /// /// assert_eq!(Parser::parse_time("0").unwrap(), 0); /// assert!(Parser::parse_time("s").is_err()); /// assert_eq!(Parser::parse_time("0s").unwrap(), 0); /// assert_eq!(Parser::parse_time("1").unwrap(), 1); /// assert_eq!(Parser::parse_time("1S").unwrap(), 1); /// assert_eq!(Parser::parse_time("1s").unwrap(), 1); /// assert_eq!(Parser::parse_time("1M").unwrap(), 60); /// assert_eq!(Parser::parse_time("1m").unwrap(), 60); /// assert_eq!(Parser::parse_time("1H").unwrap(), 3600); /// assert_eq!(Parser::parse_time("1h").unwrap(), 3600); /// assert_eq!(Parser::parse_time("1D").unwrap(), 86400); /// assert_eq!(Parser::parse_time("1d").unwrap(), 86400); /// assert_eq!(Parser::parse_time("1W").unwrap(), 604800); /// assert_eq!(Parser::parse_time("1w").unwrap(), 604800); /// assert_eq!(Parser::parse_time("1s2d3w4h2m").unwrap(), 1+2*86400+3*604800+4*3600+2*60); /// assert_eq!(Parser::parse_time("3w3w").unwrap(), 3*604800+3*604800); /// ``` pub fn parse_time(ttl_str: &str) -> ParseResult { let mut value: u32 = 0; let mut collect: Option = None; for c in ttl_str.chars() { match c { // TODO, should these all be checked operations? '0'..='9' => { let digit = c.to_digit(10).ok_or(ParseErrorKind::CharToInt(c))?; collect = Some(collect.unwrap_or(0) * 10 + digit); } 'S' | 's' => { value += collect .take() .ok_or_else(|| ParseErrorKind::ParseTime(ttl_str.to_string()))? } 'M' | 'm' => { value += 60 * collect .take() .ok_or_else(|| ParseErrorKind::ParseTime(ttl_str.to_string()))? } 'H' | 'h' => { value += 3_600 * collect .take() .ok_or_else(|| ParseErrorKind::ParseTime(ttl_str.to_string()))? } 'D' | 'd' => { value += 86_400 * collect .take() .ok_or_else(|| ParseErrorKind::ParseTime(ttl_str.to_string()))? } 'W' | 'w' => { value += 604_800 * collect .take() .ok_or_else(|| ParseErrorKind::ParseTime(ttl_str.to_string()))? } _ => return Err(ParseErrorKind::ParseTime(ttl_str.to_string()).into()), } } Ok(value + collect.unwrap_or(0)) // collects the initial num, or 0 if it was already collected } } #[allow(unused)] enum State { StartLine, // start of line, @, $, Name, Blank TtlClassType, // [] [] , Ttl, // $TTL