diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6b60260 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +name: CI + +permissions: read-all + +on: + push: + branches: + - main + pull_request: + merge_group: + branches: + - main + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust from apt + run: | + sudo apt update + sudo apt install -y cargo rustc + + - name: Build + run: cargo build --workspace --all-targets + + - name: Run tests + run: cargo test --workspace --all-targets + + format: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust from apt + run: | + sudo apt update + sudo apt install -y cargo rustc rustfmt + + - name: Check formatting + run: cargo fmt --all -- --check + + clippy: + needs: format + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust from apt + run: | + sudo apt update + sudo apt install -y cargo rustc rust-clippy + + - name: Run clippy + run: cargo clippy --workspace --all-targets -- --deny warnings + + docs: + needs: clippy + runs-on: ubuntu-latest + env: + RUSTDOCFLAGS: "-D warnings" + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust from apt + run: | + sudo apt update + sudo apt install -y cargo rustc + + - name: Build docs + run: cargo doc --no-deps --document-private-items + + audit: + needs: clippy + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust from apt + run: | + sudo apt update + sudo apt install -y cargo rustc + + - name: Install cargo-audit + run: cargo install cargo-audit --locked + + - name: Run audit + run: cargo audit diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 9fd45e0..0000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Rust - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..b583649 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,23 @@ +Copyright (C) 2025 Rust Swifties Team + +This project is a Rust rewrite of net-tools, based on the original +net-tools project available at: https://sourceforge.net/projects/net-tools/ + +Original net-tools copyright holders: +Copyright (C) 1988-1994 MicroWalt Corporation +Copyright (C) 1995-1996 Bernd Eckenfels +Copyright (C) 1997-2000 Andi Kleen +Copyright (C) 1997-2000 Donald Becker + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, see . \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b8c6164 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,326 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clap" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "net-tools-rs" +version = "0.1.0" +dependencies = [ + "clap", + "libc", + "nix", + "thiserror", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" diff --git a/Cargo.toml b/Cargo.toml index ac998b4..b2372c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,20 @@ authors = [ "Nadzeya Hutsko ", "Lena Voytek ", ] -license = "MIT" +license = "GPL-2.0-or-later" [dependencies] +clap = { version = "4.5", features = ["derive"] } +libc = "0.2" +nix = { version = "0.30", features = ["hostname", "net"] } +thiserror = "2.0" + +[[bin]] +name = "hostname" +path = "bin/hostname.rs" + +[lints.rust] +unsafe_op_in_unsafe_fn = { level = "deny" } + +[lints.clippy] +undocumented_unsafe_blocks = "warn" diff --git a/LICENSE b/LICENSE index e77b357..d159169 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,339 @@ -MIT License - -Copyright (c) 2025 Rust Swifties Team - -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. + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/bin/hostname.rs b/bin/hostname.rs new file mode 100644 index 0000000..9875848 --- /dev/null +++ b/bin/hostname.rs @@ -0,0 +1,3 @@ +fn main() { + net_tools_rs::hostname_main(); +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..8125a8e --- /dev/null +++ b/src/error.rs @@ -0,0 +1,31 @@ +use std::io; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum NetToolsError { + #[error("IO error: {0}")] + Io(#[from] io::Error), + + #[error("System error: {0}")] + Nix(#[from] nix::errno::Errno), + + #[error("Invalid argument: {0}")] + InvalidArgument(String), + + #[error("Permission denied: {0}")] + PermissionDenied(String), + + #[error("Not found: {0}")] + NotFound(String), + + #[error("Name too long: {0}")] + NameTooLong(String), + + #[error("Protocol family not supported")] + ProtocolNotSupported, + + #[error("{0}")] + Other(String), +} + +pub type Result = std::result::Result; diff --git a/src/hostname/mod.rs b/src/hostname/mod.rs new file mode 100644 index 0000000..60deaa7 --- /dev/null +++ b/src/hostname/mod.rs @@ -0,0 +1,407 @@ +//! Rust implementation of the hostname command from net-tools + +use crate::{NetToolsError, RELEASE, Result}; +use clap::Parser; +use nix::ifaddrs::getifaddrs; +use nix::sys::socket::SockaddrLike; +use nix::unistd; +use std::fs; +use std::net::{IpAddr, ToSocketAddrs}; + +#[derive(Parser, Debug)] +#[command( + name = "hostname", + version = RELEASE, + about = "Show or set system host name", + long_about = "Rust implementation of the hostname command.\n\n\ + This command can get or set the host name or the NIS domain name. You can\n\ + also get the DNS domain or the FQDN (fully qualified domain name).\n\ + Unless you are using bind or NIS for host lookups you can change the\n\ + FQDN (Fully Qualified Domain Name) and the DNS domain name (which is\n\ + part of the FQDN) in the /etc/hosts file." +)] +struct Args { + /// new hostname to set + hostname: Option, + + /// DNS domain name + #[arg(short, long)] + domain: bool, + + /// read host name or NIS domain name from given file + #[arg(short = 'F', long = "file")] + file: Option, + + /// addresses for the host name + #[arg(short = 'i', long = "ip-address")] + ip_address: bool, + + /// all addresses for the host + #[arg(short = 'I', long = "all-ip-addresses")] + all_ip_addresses: bool, + + /// short host name + #[arg(short, long)] + short: bool, + + /// NIS/YP domain name + #[arg(short = 'y', long = "yp", visible_alias = "nis")] + yp: bool, + + /// verbose output + #[arg(short, long)] + verbose: bool, +} + +pub fn main() { + let args = Args::parse(); + + let result = if args.yp { + handle_domainname(&args) + } else if args.domain { + handle_dns_domain(&args) + } else { + handle_hostname(&args) + }; + + if let Err(e) = result { + eprintln!("hostname: {}", e); + std::process::exit(1); + } +} + +/// Handle hostname operations (get/set) +fn handle_hostname(args: &Args) -> Result<()> { + if let Some(file) = &args.file { + let name = read_name_from_file(file, args.verbose)?; + return set_hostname(&name, args.verbose); + } + + if let Some(name) = &args.hostname { + return set_hostname(name, args.verbose); + } + + let hostname = get_hostname()?; + + if args.verbose { + eprintln!("gethostname()=`{hostname}'"); + } + + if args.short { + let short = hostname.split('.').next().unwrap_or(&hostname); + println!("{short}"); + } else if args.ip_address { + show_ip_addresses(&hostname, args.verbose)?; + } else if args.all_ip_addresses { + show_all_ip_addresses()?; + } else { + println!("{hostname}"); + } + + Ok(()) +} + +/// Handle NIS domainname operations +fn handle_domainname(args: &Args) -> Result<()> { + if let Some(file) = &args.file { + let name = read_name_from_file(file, args.verbose)?; + return set_domainname(&name, args.verbose); + } + + if let Some(name) = &args.hostname { + return set_domainname(name, args.verbose); + } + + let domainname = get_domainname()?; + + if args.verbose { + eprintln!("getdomainname()=`{}'", domainname); + } + + println!("{}", domainname); + Ok(()) +} + +/// Handle DNS domain name display +fn handle_dns_domain(args: &Args) -> Result<()> { + if args.file.is_some() || args.hostname.is_some() { + return Err(NetToolsError::Other( + "You can't change the DNS domain name with this command\n\ + \nUnless you are using bind or NIS for host lookups you can change the DNS\n\ + domain name (which is part of the FQDN) in the /etc/hosts file." + .to_string(), + )); + } + + let hostname = get_hostname()?; + show_dns_domain(&hostname)?; + + Ok(()) +} + +/// Get the current host name +fn get_hostname() -> Result { + let name = unistd::gethostname()?; + Ok(name.to_string_lossy().to_string()) +} + +/// Set the host name +fn set_hostname(name: &str, verbose: bool) -> Result<()> { + if verbose { + eprintln!("Setting hostname to `{name}'"); + } + + unistd::sethostname(name).map_err(|e| match e { + nix::errno::Errno::EPERM => NetToolsError::PermissionDenied( + "you don't have permission to set the host name".to_string(), + ), + nix::errno::Errno::EINVAL => NetToolsError::NameTooLong("hostname too long".to_string()), + _ => NetToolsError::Nix(e), + })?; + + Ok(()) +} + +/// Get the current NIS domain name +fn get_domainname() -> Result { + let mut buf = [0u8; 256]; + // SAFETY: getdomainname is passed a valid buffer with correct size + let ret = unsafe { libc::getdomainname(buf.as_mut_ptr() as *mut i8, buf.len()) }; + if ret != 0 { + return Err(NetToolsError::Nix(nix::errno::Errno::last())); + } + + let len = buf.iter().position(|&x| x == 0).unwrap_or(buf.len()); + let name = std::str::from_utf8(&buf[..len]) + .map_err(|_| NetToolsError::Other("Invalid UTF-8 in domainname".to_string()))?; + + Ok(name.to_string()) +} + +/// Set the NIS domain name +fn set_domainname(name: &str, verbose: bool) -> Result<()> { + if verbose { + eprintln!("Setting domainname to `{name}'"); + } + + // SAFETY: setdomainname is passed a valid string pointer and length + let ret = unsafe { libc::setdomainname(name.as_ptr() as *const i8, name.len()) }; + + if ret != 0 { + let errno = nix::errno::Errno::last(); + return Err(match errno { + nix::errno::Errno::EPERM => NetToolsError::PermissionDenied( + "you don't have permission to set the domain name".to_string(), + ), + nix::errno::Errno::EINVAL => { + NetToolsError::NameTooLong("domainname too long".to_string()) + } + _ => NetToolsError::Nix(errno), + }); + } + + Ok(()) +} + +/// Read hostname/domainname from file +fn read_name_from_file(file: &str, verbose: bool) -> Result { + let contents = + fs::read_to_string(file).map_err(|_| NetToolsError::Other(format!("can't open {file}")))?; + + for line in contents.lines() { + let line = line.trim(); + + if line.is_empty() || line.starts_with('#') { + continue; + } + + if verbose { + eprintln!(">> {line}"); + } + + return Ok(line.to_string()); + } + + Err(NetToolsError::Other(format!( + "No valid hostname found in `{file}`" + ))) +} + +/// Show DNS domain name (part after first dot) +fn show_dns_domain(hostname: &str) -> Result<()> { + if let Some(pos) = hostname.find('.') { + println!("{}", &hostname[pos + 1..]); + } else { + println!(); + } + Ok(()) +} + +/// Show IP addresses for hostname (DNS lookup) +fn show_ip_addresses(hostname: &str, verbose: bool) -> Result<()> { + if verbose { + eprintln!("Resolving `{}' ...", hostname); + } + + let addrs = format!("{}:0", hostname) + .to_socket_addrs() + .map_err(|_| NetToolsError::Other(format!("Cannot resolve hostname: {}", hostname)))?; + + let ips: Vec = addrs.map(|addr| addr.ip()).collect(); + + if verbose { + for ip in &ips { + eprintln!("Result: h_addr_list=`{}'", ip); + } + } + + for (i, ip) in ips.iter().enumerate() { + if i == 0 { + print!("{}", ip); + } else { + print!(" {}", ip); + } + } + println!(); + + Ok(()) +} + +/// Show all IP addresses for all network interfaces (excluding loopback) +fn show_all_ip_addresses() -> Result<()> { + let ifaddrs = getifaddrs()?; + + let mut ips = Vec::new(); + + for ifaddr in ifaddrs { + if let Some(address) = ifaddr.address { + let ip = match address.family() { + Some(nix::sys::socket::AddressFamily::Inet) => { + if let Some(sockaddr) = address.as_sockaddr_in() { + let ip = sockaddr.ip(); + if !ip.is_loopback() { + Some(IpAddr::V4(ip)) + } else { + None + } + } else { + None + } + } + Some(nix::sys::socket::AddressFamily::Inet6) => { + if let Some(sockaddr) = address.as_sockaddr_in6() { + let ip = sockaddr.ip(); + if !ip.is_loopback() && !is_link_local_ipv6(&ip) { + Some(IpAddr::V6(ip)) + } else { + None + } + } else { + None + } + } + _ => None, + }; + + if let Some(ip) = ip { + ips.push(ip); + } + } + } + + for (i, ip) in ips.iter().enumerate() { + if i == 0 { + print!("{}", ip); + } else { + print!(" {}", ip); + } + } + println!(); + + Ok(()) +} + +/// Check if an IPv6 address is link-local (fe80::/10) +fn is_link_local_ipv6(ip: &std::net::Ipv6Addr) -> bool { + let octets = ip.octets(); + octets[0] == 0xfe && (octets[1] & 0xc0) == 0x80 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_link_local_ipv6() { + let link_local = "fe80::1".parse().unwrap(); + assert!(is_link_local_ipv6(&link_local)); + + let link_local2 = "fe80:0000:0000:0000:0000:0000:0000:0001".parse().unwrap(); + assert!(is_link_local_ipv6(&link_local2)); + + let global = "2001:db8::1".parse().unwrap(); + assert!(!is_link_local_ipv6(&global)); + + let loopback = "::1".parse().unwrap(); + assert!(!is_link_local_ipv6(&loopback)); + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_get_hostname() { + let hostname = get_hostname().unwrap(); + assert!(!hostname.is_empty()); + } + + #[test] + fn test_show_dns_domain() { + let hostname = "host.example.com"; + let result = show_dns_domain(hostname); + assert!(result.is_ok()); + + let hostname = "localhost"; + let result = show_dns_domain(hostname); + assert!(result.is_ok()); + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_read_name_from_file_invalid() { + let result = read_name_from_file("/tmp/nonexistent_file_12345", false); + assert!(result.is_err()); + } + + #[test] + fn test_getdomainname() { + let result = get_domainname(); + + match result { + Ok(domain) => { + assert!(domain.len() <= 256); + + if !domain.is_empty() && domain != "(none)" { + assert!( + domain + .chars() + .all(|c| c.is_alphanumeric() || c == '.' || c == '-' || c == '_') + ); + } + } + Err(e) => { + eprintln!("getdomainname failed (expected on some systems): {}", e); + } + } + } + + #[test] + fn test_args_with_file() { + let args = Args::try_parse_from(["hostname", "-F", "/etc/hostname"]).unwrap(); + assert_eq!(args.file.as_deref(), Some("/etc/hostname")); + } + + #[test] + fn test_args_with_hostname_to_set() { + let args = Args::try_parse_from(["hostname", "newhostname"]).unwrap(); + assert_eq!(args.hostname.as_deref(), Some("newhostname")); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..211c0e0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +pub mod error; +pub mod hostname; + +pub use error::{NetToolsError, Result}; +pub use hostname::main as hostname_main; + +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const RELEASE: &str = concat!("net-tools-rs ", env!("CARGO_PKG_VERSION")); diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -}