]> git.proxmox.com Git - pve-installer.git/commitdiff
common: fqdn: do not allow overlong FQDNs as per Debian spec
authorChristoph Heiss <c.heiss@proxmox.com>
Thu, 15 Feb 2024 12:39:34 +0000 (13:39 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Fri, 23 Feb 2024 15:38:45 +0000 (16:38 +0100)
Debian limits labels to 63 characters each and the total length to 253
characters [0].

While at it, reference all the RFCs that apply when parsing FQDNs.

[0] https://manpages.debian.org/stable/manpages/hostname.7.en.html

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
proxmox-installer-common/src/utils.rs

index c038524abbed72ef8985438e535b808e33e2cb1f..067e0259696c0b8f3c079e22f3183de78b786664 100644 (file)
@@ -117,6 +117,7 @@ pub enum FqdnParseError {
     MissingHostname,
     NumericHostname,
     InvalidPart(String),
+    TooLong(usize),
 }
 
 impl fmt::Display for FqdnParseError {
@@ -129,17 +130,46 @@ impl fmt::Display for FqdnParseError {
                 f,
                 "FQDN must only consist of alphanumeric characters and dashes. Invalid part: '{part}'",
             ),
+            TooLong(len) => write!(f, "FQDN too long: {len} > {}", Fqdn::MAX_LENGTH),
         }
     }
 }
 
+/// A type for safely representing fully-qualified domain names (FQDNs).
+///
+/// It considers following RFCs:
+/// https://www.ietf.org/rfc/rfc952.txt (sec. "ASSUMPTIONS", 1.)
+/// https://www.ietf.org/rfc/rfc1035.txt (sec. 2.3. "Conventions")
+/// https://www.ietf.org/rfc/rfc1123.txt (sec. 2.1. "Host Names and Numbers")
+/// https://www.ietf.org/rfc/rfc3492.txt
+/// https://www.ietf.org/rfc/rfc4343.txt
+///
+/// .. and applies some restriction given by Debian, e.g. 253 instead of 255
+/// maximum total length and maximum 63 characters per label.
+/// https://manpages.debian.org/stable/manpages/hostname.7.en.html
+///
+/// Additionally:
+/// - It enforces the restriction as per Bugzilla #1054, in that
+///   purely numeric hostnames are not allowed - against RFC1123 sec. 2.1.
+///
+/// Some terminology:
+/// - "label" - a single part of a FQDN, e.g. <label>.<label>.<tld>
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct Fqdn {
     parts: Vec<String>,
 }
 
 impl Fqdn {
+    /// Maximum length of a single label of the FQDN
+    const MAX_LABEL_LENGTH: usize = 63;
+    /// Maximum total length of the FQDN
+    const MAX_LENGTH: usize = 253;
+
     pub fn from(fqdn: &str) -> Result<Self, FqdnParseError> {
+        if fqdn.len() > Self::MAX_LENGTH {
+            return Err(FqdnParseError::TooLong(fqdn.len()));
+        }
+
         let parts = fqdn
             .split('.')
             .map(ToOwned::to_owned)
@@ -154,7 +184,8 @@ impl Fqdn {
         if parts.len() < 2 {
             Err(FqdnParseError::MissingHostname)
         } else if parts[0].chars().all(|c| c.is_ascii_digit()) {
-            // Not allowed/supported on Debian systems.
+            // Do not allow a purely numeric hostname, see:
+            // https://bugzilla.proxmox.com/show_bug.cgi?id=1054
             Err(FqdnParseError::NumericHostname)
         } else {
             Ok(Self { parts })
@@ -182,6 +213,7 @@ impl Fqdn {
 
     fn validate_single(s: &String) -> bool {
         !s.is_empty()
+            && s.len() <= Self::MAX_LABEL_LENGTH
             // First character must be alphanumeric
             && s.chars()
                 .next()
@@ -243,9 +275,20 @@ mod tests {
         assert_eq!(Fqdn::from("foo.com-"), Err(InvalidPart("com-".to_owned())));
         assert_eq!(Fqdn::from("-o-.com"), Err(InvalidPart("-o-".to_owned())));
 
+        // https://bugzilla.proxmox.com/show_bug.cgi?id=1054
         assert_eq!(Fqdn::from("123.com"), Err(NumericHostname));
         assert!(Fqdn::from("foo123.com").is_ok());
         assert!(Fqdn::from("123foo.com").is_ok());
+
+        assert!(Fqdn::from(&format!("{}.com", "a".repeat(63))).is_ok());
+        assert_eq!(
+            Fqdn::from(&format!("{}.com", "a".repeat(250))),
+            Err(TooLong(254)),
+        );
+        assert_eq!(
+            Fqdn::from(&format!("{}.com", "a".repeat(64))),
+            Err(InvalidPart("a".repeat(64))),
+        );
     }
 
     #[test]