Compare commits

...

10 Commits

Author SHA1 Message Date
xfnw 3a60ee1b28 flake: create 2024-04-09 13:53:54 -04:00
xfnw 9a8be92c1b release v1.0.0 2024-03-16 11:33:01 -04:00
xfnw 3fdd64772a cargo: add smol profile
like half the size of normal release profile
2024-03-09 14:20:22 -05:00
xfnw 23eb92dae3 use blocking stdout
awaiting on stdout allows new lines to be processed when we are
already done with and about to print a line, which causes lines
arriving quickly (like the MOTD text) to appear out of order.
2024-03-09 12:15:33 -05:00
xfnw 02de2ce640 readme: create 2024-03-09 11:05:28 -05:00
xfnw 4a99ba9b7c support client certificates 2024-03-09 11:05:10 -05:00
xfnw ecee487233 flush more 2024-03-08 23:21:10 -05:00
xfnw 666d3b8f69 support disabling certificate verification 2024-03-08 23:12:48 -05:00
xfnw ae5c9462b6 rust-pki-types crate not needed 2024-03-08 22:27:15 -05:00
xfnw 44e559b8cc have a smarter default port choice 2024-03-08 21:39:40 -05:00
7 changed files with 266 additions and 39 deletions

26
Cargo.lock generated
View File

@ -64,9 +64,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.2"
version = "4.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651"
checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
dependencies = [
"clap_builder",
"clap_derive",
@ -84,9 +84,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.0"
version = "4.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f"
dependencies = [
"heck",
"proc-macro2",
@ -119,9 +119,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "heck"
version = "0.4.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
@ -131,11 +131,10 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "irccrab"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"clap",
"rustls-pemfile",
"rustls-pki-types",
"tokio",
"tokio-rustls",
]
@ -146,12 +145,6 @@ version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.7.1"
@ -205,9 +198,9 @@ checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "proc-macro2"
version = "1.0.78"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
dependencies = [
"unicode-ident",
]
@ -248,7 +241,6 @@ version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41"
dependencies = [
"log",
"ring",
"rustls-pki-types",
"rustls-webpki",

View File

@ -1,15 +1,21 @@
[package]
name = "irccrab"
description = "irc ping/ponger similar to ircdog"
version = "0.1.0"
version = "1.0.0"
edition = "2021"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.5.2", default-features = false, features = ["derive", "std", "env", "help", "usage"] }
clap = { version = "4.5.2", default-features = false, features = ["derive", "std", "help", "usage"] }
rustls-pemfile = "2.1.1"
rustls-pki-types = "1.3.1"
tokio = { version = "1.36.0", features = ["rt-multi-thread", "tokio-macros", "macros", "net", "io-util", "io-std"] }
tokio-rustls = "0.25.0"
tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros", "net", "io-util", "io-std"]}
tokio-rustls = { version = "0.25.0", default-features = false, features = ["ring", "tls12"] }
[profile.smol]
inherits = "release"
opt-level = "z"
panic = "abort"
strip = true
lto = true

10
README.md Normal file
View File

@ -0,0 +1,10 @@
# irccrab 🦀
rust alternative to [ircdog](https://github.com/ergochat/ircdog)
because i did not feel like installing go
- automatically responds to irc `PING`s
- connects via tls (rustls) or plaintext
- can use an alternative cafile or disable tls verification entirely
- supports client certificate authentication

94
flake.lock Normal file
View File

@ -0,0 +1,94 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1698420672,
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
"owner": "nix-community",
"repo": "naersk",
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1712666087,
"narHash": "sha256-WwjUkWsjlU8iUImbivlYxNyMB1L5YVqE8QotQdL9jWc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a76c4553d7e741e17f289224eda135423de0491d",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1712666087,
"narHash": "sha256-WwjUkWsjlU8iUImbivlYxNyMB1L5YVqE8QotQdL9jWc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a76c4553d7e741e17f289224eda135423de0491d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"naersk": "naersk",
"nixpkgs": "nixpkgs_2"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

22
flake.nix Normal file
View File

@ -0,0 +1,22 @@
{
inputs = {
flake-utils.url = "github:numtide/flake-utils";
naersk.url = "github:nix-community/naersk";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs = { self, flake-utils, naersk, nixpkgs }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = (import nixpkgs) { inherit system; };
naersk' = pkgs.callPackage naersk { };
in rec {
packages.irccrab = naersk'.buildPackage { src = ./.; };
defaultPackage = packages.irccrab;
devShell = pkgs.mkShell {
nativeBuildInputs = with pkgs; [ rustc cargo clippy ];
};
});
}

66
src/danger.rs Normal file
View File

@ -0,0 +1,66 @@
use std::sync::Arc;
use tokio_rustls::rustls::{
self,
client::danger::HandshakeSignatureValid,
crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider},
pki_types::{CertificateDer, ServerName, UnixTime},
DigitallySignedStruct,
};
/// mostly borrowed from rustls/examples/src/bin/tlsclient-mio.rs,
/// rustls seems quite insistent on making the process of disabling
/// certificate verification as obnoxious as possible...
#[derive(Debug)]
pub(crate) struct PhonyVerify(CryptoProvider);
impl PhonyVerify {
pub fn new(provider: CryptoProvider) -> Arc<Self> {
Arc::new(Self(provider))
}
}
impl rustls::client::danger::ServerCertVerifier for PhonyVerify {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp: &[u8],
_now: UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
verify_tls12_signature(
message,
cert,
dss,
&self.0.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
verify_tls13_signature(
message,
cert,
dss,
&self.0.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self.0.signature_verification_algorithms.supported_schemes()
}
}

View File

@ -1,11 +1,15 @@
use clap::Parser;
use std::path::PathBuf;
use std::sync::Arc;
use std::{io::Write, path::PathBuf, sync::Arc};
use tokio::{
io::{self, AsyncBufReadExt, AsyncWriteExt, BufReader},
net::TcpStream,
};
use tokio_rustls::{rustls, TlsConnector};
use tokio_rustls::{
rustls::{self, pki_types},
TlsConnector,
};
mod danger;
/// irc ping/ponger similar to ircdog
#[derive(Debug, Parser)]
@ -28,8 +32,8 @@ struct Opt {
#[arg(required = true)]
host: String,
#[arg(default_value = "6667")]
port: u16,
/// port defaults to 6667 or 6697 for tls
port: Option<u16>,
}
fn trim_mut(buf: &mut Vec<u8>) {
@ -71,24 +75,34 @@ async fn send_pong(
// https://github.com/tokio-rs/tokio/issues/3679
//write.write_all_vectored(&[b"PONG ", pong].map(IoSlice::new)).await?;
write.write_all(b"PONG ").await?;
write.write_all(pong).await
write.write_all(pong).await?;
write.flush().await
}
#[tokio::main]
async fn main() {
let opt = Opt::parse();
let stream = TcpStream::connect((opt.host.as_ref(), opt.port))
let port = if let Some(port) = opt.port {
port
} else if opt.tls {
6697
} else {
6667
};
let stream = TcpStream::connect((opt.host.as_ref(), port))
.await
.expect("failed to connect");
if opt.tls {
let config = rustls::ClientConfig::builder();
let config = if opt.insecure {
// rustls seems to make doing this intentionally annoying
// https://docs.rs/rustls/latest/rustls/struct.ConfigBuilder.html
// https://users.rust-lang.org/t/rustls-connecting-without-certificate-in-local-network/83822
todo!();
config
.dangerous()
.with_custom_certificate_verifier(danger::PhonyVerify::new(
rustls::crypto::ring::default_provider(),
))
} else {
let mut root_cert_store = rustls::RootCertStore::empty();
let mut pem = std::io::BufReader::new(
@ -99,17 +113,39 @@ async fn main() {
}
config.with_root_certificates(root_cert_store)
};
let config = if let Some(_cert) = opt.cert {
// with_client_auth_cert
todo!();
let config = if let Some(cert) = opt.cert {
use rustls_pemfile::Item;
let mut pem = std::io::BufReader::new(
std::fs::File::open(cert).expect("cannot open client cert"),
);
let mut certs = Vec::new();
let mut keys: Vec<pki_types::PrivateKeyDer> = Vec::new();
for c in rustls_pemfile::read_all(&mut pem) {
match c.unwrap() {
Item::X509Certificate(crt) => certs.push(crt),
Item::Pkcs1Key(key) => keys.push(key.into()),
Item::Pkcs8Key(key) => keys.push(key.into()),
Item::Sec1Key(key) => keys.push(key.into()),
e => eprintln!("unknown item in pem: {:?}", e),
}
}
config
.with_client_auth_cert(certs, keys.pop().expect("no key found"))
.expect("could not load client cert")
} else {
config.with_no_client_auth()
};
let connector = TlsConnector::from(Arc::new(config));
let domain = rustls_pki_types::ServerName::try_from(opt.host)
let domain = pki_types::ServerName::try_from(opt.host)
.expect("invalid server name")
.to_owned();
let tlsstream = connector.connect(domain, stream).await.expect("failed to negotiate tls");
let tlsstream = connector
.connect(domain, stream)
.await
.expect("failed to negotiate tls");
handle_irc(tlsstream).await;
} else {
handle_irc(stream).await;
@ -134,6 +170,7 @@ async fn handle_irc(stream: impl io::AsyncReadExt + io::AsyncWriteExt) {
stdbuf.push(b'\r');
stdbuf.push(b'\n');
write.write_all(&stdbuf).await.expect("cannot send");
write.flush().await.expect("cannot send");
stdbuf.clear();
}
@ -148,7 +185,7 @@ async fn handle_irc(stream: impl io::AsyncReadExt + io::AsyncWriteExt) {
trim_mut(&mut ircbuf);
ircbuf.push(b'\n');
io::stdout().write_all(&ircbuf).await.expect("broken pipe");
std::io::stdout().write_all(&ircbuf).expect("broken pipe");
ircbuf.clear();
}