Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ uudoc = []

feat_common_core = [
"hostname",
"domainname"
]

[workspace.dependencies]
Expand Down Expand Up @@ -65,6 +66,7 @@ textwrap = { workspace = true }

#
hostname = { optional = true, version = "0.0.1", package = "uu_hostname", path = "src/uu/hostname" }
domainname = { optional = true, version = "0.0.1", package = "uu_domainname", path = "src/uu/domainname" }

[dev-dependencies]
pretty_assertions = "1.4.0"
Expand All @@ -91,6 +93,11 @@ phf_codegen = { workspace = true }
name = "hostname"
path = "src/bin/hostname.rs"

[[bin]]
name = "domainname"
path = "src/bin/domainname.rs"


[[bin]]
name = "uudoc"
path = "src/bin/uudoc.rs"
Expand Down
13 changes: 13 additions & 0 deletions src/bin/domainname.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use std::env;
use std::ffi::OsString;

fn main() {
let args: Vec<OsString> = env::args_os().collect();
let exit_code = domainname::uumain(args.into_iter());
std::process::exit(exit_code);
}
30 changes: 30 additions & 0 deletions src/uu/domainname/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "uu_domainname"
version = "0.0.1"
edition = "2024"
authors = ["uutils developers"]
license = "MIT"
description = "domainname ~ (uutils) show or set the system's NIS/YP domain name"

homepage = "https://github.com/uutils/hostname"
repository = "https://github.com/uutils/hostname/tree/main/src/uu/hostname"
keywords = ["acl", "uutils", "cross-platform", "cli", "utility"]
categories = ["command-line-utilities"]

[dependencies]
uucore = { workspace = true }
clap = { workspace = true }

[target.'cfg(target_os = "windows")'.dependencies]
windows-sys = { workspace = true }

[target.'cfg(not(target_os = "windows"))'.dependencies]
errno = { workspace = true }
libc = { workspace = true }

[lib]
path = "src/domainname.rs"

[[bin]]
name = "domainname"
path = "src/main.rs"
7 changes: 7 additions & 0 deletions src/uu/domainname/domainname.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# domainname

```
domainname [nisdomain] [-F file]
```

show or set the system's NIS/YP domain name
14 changes: 14 additions & 0 deletions src/uu/domainname/src/change.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

#[cfg(not(target_family = "windows"))]
pub(crate) mod unix;
#[cfg(target_family = "windows")]
pub(crate) mod windows;

#[cfg(not(target_family = "windows"))]
pub(crate) use unix::{from_argument, from_file};
#[cfg(target_family = "windows")]
pub(crate) use windows::{from_argument, from_file};
84 changes: 84 additions & 0 deletions src/uu/domainname/src/change/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use std::borrow::Cow;
use std::ffi::{CString, OsStr};
use std::path::Path;

use uucore::error::UResult;

use crate::errors::DomainNameError;
use crate::net::set_domain_name;
use crate::utils::parse_domain_name_file;

pub(crate) fn from_file(path: &Path) -> UResult<()> {
parse_domain_name_file(path).map(Cow::Owned).and_then(run)
}

pub(crate) fn from_argument(domain_name: &OsStr) -> UResult<()> {
#[cfg(target_family = "unix")]
let domain_name = {
use std::os::unix::ffi::OsStrExt;
Cow::Borrowed(domain_name.as_bytes())
};

#[cfg(target_family = "wasm")]
let domain_name = {
use std::os::wasm::ffi::OsStrExt;
Cow::Borrowed(domain_name.as_bytes())
};

run(domain_name)
}

fn run(mut domain_name: Cow<[u8]>) -> UResult<()> {
// Trim white space.
match &mut domain_name {
Cow::Borrowed(name) => *name = name.trim_ascii(),

Cow::Owned(name) => {
while name.first().is_some_and(u8::is_ascii_whitespace) {
name.remove(0);
}

while name.last().is_some_and(u8::is_ascii_whitespace) {
name.pop();
}
}
};

let domain_name = validate_domain_name(domain_name)?;

set_domain_name(&domain_name)
}

fn validate_domain_name(domain_name: Cow<[u8]>) -> Result<CString, DomainNameError> {
// Rules:
// - The only allowed prefix and suffix characters are alphanumeric.
// - The only allowed characters inside are alphanumeric, '-' and '.'.
// - The following sequences are disallowed: "..", ".-" and "-.".
//
// Reference: RFC 1035: Domain Names - Implementation And Specification,
// section 2.3.1. Preferred name syntax.

let (Some(first_byte), Some(last_byte)) = (domain_name.first(), domain_name.last()) else {
return Err(DomainNameError::InvalidDomainName); // Empty name.
};

let is_disallowed_byte = move |b: &u8| !b.is_ascii_alphanumeric() && *b != b'-' && *b != b'.';
let is_disallowed_seq = move |seq: &[u8]| seq == b".." || seq == b".-" || seq == b"-.";

if !first_byte.is_ascii_alphanumeric()
|| !last_byte.is_ascii_alphanumeric()
|| domain_name.iter().any(is_disallowed_byte)
|| domain_name.windows(2).any(is_disallowed_seq)
{
return Err(DomainNameError::InvalidDomainName);
}

let mut domain_name = domain_name.into_owned();
domain_name.push(0_u8);
Ok(unsafe { CString::from_vec_with_nul_unchecked(domain_name) })
}
82 changes: 82 additions & 0 deletions src/uu/domainname/src/change/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;

use uucore::error::UResult;

use crate::errors::DomainNameError;
use crate::net::set_domain_name;
use crate::utils::parse_domain_name_file;

pub(crate) fn from_file(path: &Path) -> UResult<()> {
let domain_name = parse_domain_name_file(path)?;
let domain_name =
std::str::from_utf8(&domain_name).map_err(|_r| DomainNameError::InvalidDomainName)?;
run(domain_name.encode_utf16().collect())
}

pub(crate) fn from_argument(domain_name: &OsStr) -> UResult<()> {
run(domain_name.encode_wide().collect())
}

fn run(mut domain_name: Vec<u16>) -> UResult<()> {
// Trim white space.
while domain_name.first().is_some_and(u16_is_ascii_whitespace) {
domain_name.remove(0);
}

while domain_name.last().is_some_and(u16_is_ascii_whitespace) {
domain_name.pop();
}

validate_domain_name(&domain_name)?;

domain_name.push(0); // Null-terminate.
set_domain_name(&domain_name)
}

fn u16_is_ascii_whitespace(ch: &u16) -> bool {
u8::try_from(*ch).is_ok_and(|b| b.is_ascii_whitespace())
}

fn u16_is_ascii_alphanumeric(ch: &u16) -> bool {
u8::try_from(*ch).is_ok_and(|b| b.is_ascii_alphanumeric())
}

fn validate_domain_name(domain_name: &[u16]) -> Result<(), DomainNameError> {
// Rules:
// - The only allowed prefix and suffix characters are alphanumeric.
// - The only allowed characters inside are alphanumeric, '-' and '.'.
// - The following sequences are disallowed: "..", ".-" and "-.".
//
// Reference: RFC 1035: Domain Names - Implementation And Specification,
// section 2.3.1. Preferred name syntax.

const DOT_DOT: [u16; 2] = [b'.' as u16, b'.' as u16];
const DOT_DASH: [u16; 2] = [b'.' as u16, b'-' as u16];
const DASH_DOT: [u16; 2] = [b'-' as u16, b'.' as u16];

let (Some(first_byte), Some(last_byte)) = (domain_name.first(), domain_name.last()) else {
return Err(DomainNameError::InvalidDomainName); // Empty name.
};

let is_disallowed_byte = move |ch: &u16| {
!u16_is_ascii_alphanumeric(ch) && *ch != (b'-' as u16) && *ch != (b'.' as u16)
};
let is_disallowed_seq = move |seq: &[u16]| seq == DOT_DOT || seq == DOT_DASH || seq == DASH_DOT;

if !u16_is_ascii_alphanumeric(first_byte)
|| !u16_is_ascii_alphanumeric(last_byte)
|| domain_name.iter().any(is_disallowed_byte)
|| domain_name.windows(2).any(is_disallowed_seq)
{
Err(DomainNameError::InvalidDomainName)
} else {
Ok(())
}
}
80 changes: 80 additions & 0 deletions src/uu/domainname/src/domainname.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

mod change;
mod errors;
mod net;
mod print;
mod utils;

use std::ffi::OsString;
use std::path::PathBuf;

use clap::{Arg, ArgAction, ArgGroup, Command, crate_version, value_parser};
use uucore::{error::UResult, format_usage, help_about, help_usage};

const ABOUT: &str = help_about!("domainname.md");
const USAGE: &str = help_usage!("domainname.md");

pub mod options {
pub static FILE: &str = "file";
pub static FILENAME: &str = "filename";
pub static DOMAINNAME: &str = "domainname";
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = uu_app().try_get_matches_from(args)?;

if args.contains_id("set-group") {
if let Some(path) = args.get_one::<PathBuf>(options::FILE) {
change::from_file(path)
} else {
let domain_name = args
.get_one::<OsString>(options::DOMAINNAME)
.expect("domainname must be specified");

change::from_argument(domain_name)
}
} else {
let mut stdout = std::io::stdout();
print::print_domain_name(&mut stdout)
}
}

#[must_use]
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
.arg(
Arg::new(options::FILE)
.short('F')
.long(options::FILE)
.value_name(options::FILENAME)
.value_parser(value_parser!(PathBuf))
.action(ArgAction::Set)
.conflicts_with(options::DOMAINNAME)
.help("read domain name from given file"),
)
.arg(
Arg::new(options::DOMAINNAME)
.value_parser(value_parser!(OsString))
.conflicts_with(options::FILE),
)
.group(
ArgGroup::new("set-group")
.args([options::FILE, options::DOMAINNAME])
.multiple(true)
.requires("source-group"),
)
.group(
ArgGroup::new("source-group")
.args([options::FILE, options::DOMAINNAME])
.multiple(false),
)
}
Loading