From 67e61e06060e2fde1435c724114af0087555c3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Gaillard?= Date: Sat, 7 Mar 2020 13:17:35 +0800 Subject: [PATCH] feature: add TLS as feature and support multiples TLS backends (currently native-tls and rustls) --- .travis.yml | 3 + Cargo.toml | 89 ++++++--- README.md | 2 +- examples/multiserver.rs | 12 +- examples/repeater.rs | 8 +- examples/simple.rs | 19 +- examples/simple_plaintext.rs | 21 +- examples/{proxy.rs => simple_proxy.rs} | 16 +- examples/tooter.rs | 2 +- examples/tweeter.rs | 2 +- irc-proto/src/message.rs | 8 +- src/client/conn.rs | 266 +++++++++++++++++-------- src/client/data/config.rs | 56 ++++-- src/client/mod.rs | 10 +- src/client/prelude.rs | 3 + src/error.rs | 17 ++ 16 files changed, 355 insertions(+), 179 deletions(-) rename examples/{proxy.rs => simple_proxy.rs} (59%) diff --git a/.travis.yml b/.travis.yml index 1ae447e..114e73f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,6 +36,9 @@ jobs: # nochanlists on w/toml - <<: *test env: FEATURES=nochanlists toml_config + # tls-rust + - <<: *test + env: FEATURES=tls-rust # Beta - <<: *test diff --git a/Cargo.toml b/Cargo.toml index e6b2b87..b112708 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,58 +11,81 @@ repository = "https://github.com/aatxe/irc" readme = "README.md" edition = "2018" + [badges] travis-ci = { repository = "aatxe/irc" } is-it-maintained-issue-resolution = { repository = "aatxe/irc" } is-it-maintained-open-issues = { repository = "aatxe/irc" } + [workspace] -members = [ "./", "irc-proto" ] +members = [ "./", "irc-proto/" ] + [features] -default = ["ctcp", "toml_config"] +default = ["ctcp", "tls-native", "toml_config"] ctcp = [] nochanlists = [] -json_config = ["serde", "serde_derive", "serde_json"] -toml_config = ["serde", "serde_derive", "toml"] -yaml_config = ["serde", "serde_derive", "serde_yaml"] -proxy = ["tokio-socks"] +json_config = ["serde", "serde/derive", "serde_derive", "serde_json"] +toml_config = ["serde", "serde/derive", "serde_derive", "toml"] +yaml_config = ["serde", "serde/derive", "serde_derive", "serde_yaml"] # Temporary transitionary features json = ["json_config"] yaml = ["yaml_config"] +proxy = ["tokio-socks"] + +tls-native = ["native-tls", "tokio-tls"] +tls-rust = ["tokio-rustls", "webpki-roots"] + + [dependencies] -thiserror = "1.0.2" -bufstream = "0.1" -bytes = "0.5" -chrono = "0.4" -encoding = "0.2" -irc-proto = { version = "*", path = "irc-proto" } -log = "0.4" -native-tls = "0.2" -serde = { version = "1.0", features = ["derive"], optional = true } -serde_derive = { version = "1.0", optional = true } -tokio = { version = "0.2.4", features = ["time", "net", "stream", "macros"] } -tokio-util = { version = "0.3.0", features = ["codec"] } -tokio-socks = { version = "0.2.0", optional = true } -tokio-tls = "0.3.0" -serde_json = { version = "1.0", optional = true } -serde_yaml = { version = "0.8", optional = true } -toml = { version = "0.5", optional = true } -pin-utils = "0.1.0-alpha.4" +bufstream = "0.1.0" +bytes = "0.5.0" +chrono = "0.4.0" +encoding = "0.2.0" +futures-channel = "0.3.0" +futures-util = { version = "0.3.0", features = ["sink"] } +irc-proto = { path = "irc-proto" } +log = "0.4.0" parking_lot = "0.10.0" -futures-channel = "0.3.1" -futures-util = { version = "0.3.1", features = ["sink"] } +pin-utils = "0.1.0-alpha.4" +thiserror = "1.0.0" +tokio = { version = "0.2.0", features = ["macros", "net", "stream", "time"] } +tokio-util = { version = "0.3.0", features = ["codec"] } + +# Feature - Config +serde = { version = "1.0.0", optional = true } +serde_derive = { version = "1.0.0", optional = true } +serde_json = { version = "1.0.0", optional = true } +serde_yaml = { version = "0.8.0", optional = true } +toml = { version = "0.5.0", optional = true } + +# Feature - Proxy +tokio-socks = { version = "0.2.0", optional = true } + +# Feature - TLS +native-tls = { version = "0.2.0", optional = true } +tokio-rustls = { version = "0.13.0", optional = true } +tokio-tls = { version = "0.3.0", optional = true } +webpki-roots = { version = "0.19.0", optional = true } + [dev-dependencies] -futures = "0.3.1" -anyhow = "1.0.13" -args = "2.0" -getopts = "0.2" -env_logger = "0.7" +anyhow = "1.0.0" +args = "2.0.0" +env_logger = "0.7.0" +futures = "0.3.0" +getopts = "0.2.0" + [[example]] -name = "proxy" -path = "examples/proxy.rs" +name = "simple_proxy" +path = "examples/simple_proxy.rs" required-features = ["proxy"] + +[[example]] +name = "simple_plaintext" +path = "examples/simple_plaintext.rs" +required-features = ["tls-native"] diff --git a/README.md b/README.md index aee773d..cd74f32 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ proxy_server = "127.0.0.1" proxy_port = "1080" proxy_username = "" proxy_password = "" -use_ssl = true +use_tls = true cert_path = "cert.der" client_cert_path = "client.der" client_cert_pass = "password" diff --git a/examples/multiserver.rs b/examples/multiserver.rs index 02efb39..e6d0ae3 100644 --- a/examples/multiserver.rs +++ b/examples/multiserver.rs @@ -1,5 +1,3 @@ -extern crate irc; - use futures::prelude::*; use irc::{client::prelude::*, error}; @@ -9,21 +7,21 @@ async fn main() -> irc::error::Result<()> { let cfg1 = Config { nickname: Some("pickles".to_owned()), - server: Some("irc.mozilla.org".to_owned()), + server: Some("irc.pdgn.co".to_owned()), channels: vec!["#rust-spam".to_owned()], ..Default::default() }; let cfg2 = Config { nickname: Some("bananas".to_owned()), - server: Some("irc.mozilla.org".to_owned()), + server: Some("irc.pdgn.co".to_owned()), channels: vec!["#rust-spam".to_owned()], ..Default::default() }; let configs = vec![cfg1, cfg2]; - let mut senders = Vec::new(); let mut streams = Vec::new(); + let mut senders = Vec::new(); for config in configs { // Immediate errors like failure to resolve the server's domain or to establish any connection will @@ -31,8 +29,8 @@ async fn main() -> irc::error::Result<()> { let mut client = Client::from_config(config).await?; client.identify()?; - senders.push(client.sender()); streams.push(client.stream()?); + senders.push(client.sender()); } loop { @@ -45,7 +43,7 @@ async fn main() -> irc::error::Result<()> { } fn process_msg(sender: &Sender, message: Message) -> error::Result<()> { - // print!("{}", message); + print!("{}", message); match message.command { Command::PRIVMSG(ref target, ref msg) => { diff --git a/examples/repeater.rs b/examples/repeater.rs index 134d577..27e29eb 100644 --- a/examples/repeater.rs +++ b/examples/repeater.rs @@ -4,9 +4,8 @@ use irc::client::prelude::*; #[tokio::main] async fn main() -> irc::error::Result<()> { let config = Config { - nickname: Some("repeater".to_owned()), - alt_nicks: vec!["blaster".to_owned(), "smg".to_owned()], - server: Some("irc.mozilla.org".to_owned()), + nickname: Some("pickles".to_owned()), + server: Some("irc.pdgn.co".to_owned()), channels: vec!["#rust-spam".to_owned()], burst_window_length: Some(4), max_messages_in_burst: Some(4), @@ -17,6 +16,7 @@ async fn main() -> irc::error::Result<()> { client.identify()?; let mut stream = client.stream()?; + let sender = client.sender(); loop { let message = stream.select_next_some().await?; @@ -28,7 +28,7 @@ async fn main() -> irc::error::Result<()> { let n = tokens[0].len() + tokens[1].len() + 2; if let Ok(count) = tokens[1].parse::() { for _ in 0..count { - client.send_privmsg( + sender.send_privmsg( message.response_target().unwrap_or(target), &msg[n..], )?; diff --git a/examples/simple.rs b/examples/simple.rs index d0e1e9f..061b8ba 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,8 +5,7 @@ use irc::client::prelude::*; async fn main() -> irc::error::Result<()> { let config = Config { nickname: Some("pickles".to_owned()), - alt_nicks: vec!["bananas".to_owned(), "apples".to_owned()], - server: Some("irc.mozilla.org".to_owned()), + server: Some("irc.pdgn.co".to_owned()), channels: vec!["#rust-spam".to_owned()], ..Default::default() }; @@ -15,14 +14,20 @@ async fn main() -> irc::error::Result<()> { client.identify()?; let mut stream = client.stream()?; + let sender = client.sender(); - loop { - let message = stream.select_next_some().await?; + while let Some(message) = stream.next().await.transpose()? { + print!("{}", message); - if let Command::PRIVMSG(ref target, ref msg) = message.command { - if msg.contains("pickles") { - client.send_privmsg(target, "Hi!").unwrap(); + match message.command { + Command::PRIVMSG(ref target, ref msg) => { + if msg.contains(client.current_nickname()) { + sender.send_privmsg(target, "Hi!")?; + } } + _ => (), } } + + Ok(()) } diff --git a/examples/simple_plaintext.rs b/examples/simple_plaintext.rs index 826e8bc..15726e7 100644 --- a/examples/simple_plaintext.rs +++ b/examples/simple_plaintext.rs @@ -5,23 +5,30 @@ use irc::client::prelude::*; async fn main() -> irc::error::Result<()> { let config = Config { nickname: Some("pickles".to_owned()), - server: Some("irc.mozilla.org".to_owned()), + server: Some("irc.pdgn.co".to_owned()), channels: vec!["#rust-spam".to_owned()], - use_ssl: Some(false), + use_tls: Some(false), ..Default::default() }; let mut client = Client::from_config(config).await?; + client.identify()?; + let mut stream = client.stream()?; let sender = client.sender(); - loop { - let message = stream.select_next_some().await?; + while let Some(message) = stream.next().await.transpose()? { + print!("{}", message); - if let Command::PRIVMSG(ref target, ref msg) = message.command { - if msg.contains("pickles") { - sender.send_privmsg(target, "Hi!")?; + match message.command { + Command::PRIVMSG(ref target, ref msg) => { + if msg.contains(client.current_nickname()) { + sender.send_privmsg(target, "Hi!")?; + } } + _ => (), } } + + Ok(()) } diff --git a/examples/proxy.rs b/examples/simple_proxy.rs similarity index 59% rename from examples/proxy.rs rename to examples/simple_proxy.rs index b65cbf4..e2c8451 100644 --- a/examples/proxy.rs +++ b/examples/simple_proxy.rs @@ -1,13 +1,11 @@ use futures::prelude::*; -use irc::client::data::ProxyType; use irc::client::prelude::*; #[tokio::main] async fn main() -> irc::error::Result<()> { let config = Config { - nickname: Some("rust-irc-bot".to_owned()), - alt_nicks: vec!["bananas".to_owned(), "apples".to_owned()], - server: Some("irc.oftc.net".to_owned()), + nickname: Some("pickles".to_owned()), + server: Some("irc.pdgn.co".to_owned()), channels: vec!["#rust-spam".to_owned()], proxy_type: Some(ProxyType::Socks5), proxy_server: Some("127.0.0.1".to_owned()), @@ -19,9 +17,19 @@ async fn main() -> irc::error::Result<()> { client.identify()?; let mut stream = client.stream()?; + let sender = client.sender(); while let Some(message) = stream.next().await.transpose()? { print!("{}", message); + + match message.command { + Command::PRIVMSG(ref target, ref msg) => { + if msg.contains(client.current_nickname()) { + sender.send_privmsg(target, "Hi!")?; + } + } + _ => (), + } } Ok(()) diff --git a/examples/tooter.rs b/examples/tooter.rs index 921257b..0c99637 100644 --- a/examples/tooter.rs +++ b/examples/tooter.rs @@ -7,7 +7,7 @@ use std::time::Duration; async fn main() -> irc::error::Result<()> { let config = Config { nickname: Some("mastodon".to_owned()), - server: Some("irc.mozilla.org".to_owned()), + server: Some("irc.pdgn.co".to_owned()), channels: vec!["#rust-spam".to_owned()], ..Default::default() }; diff --git a/examples/tweeter.rs b/examples/tweeter.rs index 6c6ad4e..22db491 100644 --- a/examples/tweeter.rs +++ b/examples/tweeter.rs @@ -7,7 +7,7 @@ use std::time::Duration; async fn main() -> irc::error::Result<()> { let config = Config { nickname: Some("pickles".to_owned()), - server: Some("irc.mozilla.org".to_owned()), + server: Some("irc.pdgn.co".to_owned()), channels: vec!["#rust-spam".to_owned()], ..Default::default() }; diff --git a/irc-proto/src/message.rs b/irc-proto/src/message.rs index 468a864..82ab7cc 100644 --- a/irc-proto/src/message.rs +++ b/irc-proto/src/message.rs @@ -247,11 +247,9 @@ impl FromStr for Message { args.push(suffix); } - Message::with_tags(tags, prefix, command, args).map_err(|e| { - ProtocolError::InvalidMessage { - string: s.to_owned(), - cause: e, - } + Message::with_tags(tags, prefix, command, args).map_err(|e| ProtocolError::InvalidMessage { + string: s.to_owned(), + cause: e, }) } } diff --git a/src/client/conn.rs b/src/client/conn.rs index 87727e6..716b8f8 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -1,16 +1,12 @@ //! A module providing IRC connections for use by `IrcServer`s. use futures_channel::mpsc::UnboundedSender; use futures_util::{sink::Sink, stream::Stream}; -use native_tls::{Certificate, Identity, TlsConnector}; use std::{ fmt, - fs::File, - io::Read, pin::Pin, task::{Context, Poll}, }; use tokio::net::TcpStream; -use tokio_tls::{self, TlsStream}; use tokio_util::codec::Decoder; #[cfg(feature = "proxy")] @@ -19,9 +15,37 @@ use tokio_socks::tcp::Socks5Stream; #[cfg(feature = "proxy")] use crate::client::data::ProxyType; +#[cfg(feature = "tls-native")] +use std::{fs::File, io::Read}; + +#[cfg(feature = "tls-native")] +use native_tls::{Certificate, Identity, TlsConnector}; + +#[cfg(feature = "tls-native")] +use tokio_tls::{self, TlsStream}; + +#[cfg(feature = "tls-rust")] +use std::{ + fs::File, + io::{BufReader, Error, ErrorKind}, + sync::Arc, +}; + +#[cfg(feature = "tls-rust")] +use webpki_roots::TLS_SERVER_ROOTS; + +#[cfg(feature = "tls-rust")] +use tokio_rustls::{ + client::TlsStream, + rustls::{internal::pemfile::certs, ClientConfig, PrivateKey}, + webpki::DNSNameRef, + TlsConnector, +}; + use crate::{ client::{ data::Config, + mock::MockStream, transport::{LogView, Logged, Transport}, }, error, @@ -33,9 +57,10 @@ pub enum Connection { #[doc(hidden)] Unsecured(Transport), #[doc(hidden)] + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] Secured(Transport>), #[doc(hidden)] - Mock(Logged), + Mock(Logged), } impl fmt::Debug for Connection { @@ -45,6 +70,7 @@ impl fmt::Debug for Connection { "{}", match *self { Connection::Unsecured(_) => "Connection::Unsecured(...)", + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] Connection::Secured(_) => "Connection::Secured(...)", Connection::Mock(_) => "Connection::Mock(...)", } @@ -59,109 +85,50 @@ impl Connection { tx: UnboundedSender, ) -> error::Result { if config.use_mock_connection() { - use encoding::{label::encoding_from_whatwg_label, EncoderTrap}; - - let encoding = encoding_from_whatwg_label(config.encoding()).ok_or_else(|| { - error::Error::UnknownCodec { - codec: config.encoding().to_owned(), - } - })?; - - let init_str = config.mock_initial_value(); - let initial = encoding - .encode(init_str, EncoderTrap::Replace) - .map_err(|data| error::Error::CodecFailed { - codec: encoding.name(), - data: data.into_owned(), - })?; - - let stream = crate::client::mock::MockStream::new(&initial); - let framed = IrcCodec::new(config.encoding())?.framed(stream); - let transport = Transport::new(&config, framed, tx); - return Ok(Connection::Mock(Logged::wrap(transport))); + log::info!("Connecting via mock to {}.", config.server()?); + return Ok(Connection::Mock(Logged::wrap( + Self::new_mocked_transport(config, tx).await?, + ))); } - if config.use_ssl() { - log::info!("Building SSL connection."); - - let mut builder = TlsConnector::builder(); - - if let Some(cert_path) = config.cert_path() { - let mut file = File::open(cert_path)?; - let mut cert_data = vec![]; - file.read_to_end(&mut cert_data)?; - let cert = Certificate::from_der(&cert_data)?; - builder.add_root_certificate(cert); - log::info!("Added {} to trusted certificates.", cert_path); + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] + { + if config.use_tls() { + log::info!("Connecting via TLS to {}.", config.server()?); + return Ok(Connection::Secured( + Self::new_secured_transport(config, tx).await?, + )); } - - if let Some(client_cert_path) = config.client_cert_path() { - let client_cert_pass = config.client_cert_pass(); - let mut file = File::open(client_cert_path)?; - let mut client_cert_data = vec![]; - file.read_to_end(&mut client_cert_data)?; - let pkcs12_archive = Identity::from_pkcs12(&client_cert_data, &client_cert_pass)?; - builder.identity(pkcs12_archive); - log::info!( - "Using {} for client certificate authentication.", - client_cert_path - ); - } - let connector: tokio_tls::TlsConnector = builder.build()?.into(); - - let socket = Self::new_conn(config).await?; - let stream = connector.connect(config.server()?, socket).await?; - let framed = IrcCodec::new(config.encoding())?.framed(stream); - let transport = Transport::new(&config, framed, tx); - - Ok(Connection::Secured(transport)) - } else { - let stream = Self::new_conn(config).await?; - let framed = IrcCodec::new(config.encoding())?.framed(stream); - let transport = Transport::new(&config, framed, tx); - - Ok(Connection::Unsecured(transport)) } + + log::info!("Connecting to {}.", config.server()?); + Ok(Connection::Unsecured( + Self::new_unsecured_transport(config, tx).await?, + )) } #[cfg(not(feature = "proxy"))] - async fn new_conn(config: &Config) -> error::Result { - let server = config.server()?; - let port = config.port(); - let address = (server, port); - - log::info!( - "Connecting to {:?} using SSL: {}", - address, - config.use_ssl() - ); - - Ok(TcpStream::connect(address).await?) + async fn new_stream(config: &Config) -> error::Result { + Ok(TcpStream::connect((config.server()?, config.port())).await?) } #[cfg(feature = "proxy")] - async fn new_conn(config: &Config) -> error::Result { + async fn new_stream(config: &Config) -> error::Result { let server = config.server()?; let port = config.port(); let address = (server, port); - log::info!( - "Connecting to {:?} using SSL: {}", - address, - config.use_ssl() - ); - match config.proxy_type() { ProxyType::None => Ok(TcpStream::connect(address).await?), - _ => { + ProxyType::Socks5 => { let proxy_server = config.proxy_server(); let proxy_port = config.proxy_port(); - let proxy_username = config.proxy_username(); - let proxy_password = config.proxy_password(); let proxy = (proxy_server, proxy_port); log::info!("Setup proxy {:?}.", proxy); + let proxy_username = config.proxy_username(); + let proxy_password = config.proxy_password(); if !proxy_username.is_empty() || !proxy_password.is_empty() { return Ok(Socks5Stream::connect_with_password( proxy, @@ -178,6 +145,128 @@ impl Connection { } } + async fn new_unsecured_transport( + config: &Config, + tx: UnboundedSender, + ) -> error::Result> { + let stream = Self::new_stream(config).await?; + let framed = IrcCodec::new(config.encoding())?.framed(stream); + + Ok(Transport::new(&config, framed, tx)) + } + + #[cfg(feature = "tls-native")] + async fn new_secured_transport( + config: &Config, + tx: UnboundedSender, + ) -> error::Result>> { + let mut builder = TlsConnector::builder(); + + if let Some(cert_path) = config.cert_path() { + let mut file = File::open(cert_path)?; + let mut cert_data = vec![]; + file.read_to_end(&mut cert_data)?; + let cert = Certificate::from_der(&cert_data)?; + builder.add_root_certificate(cert); + log::info!("Added {} to trusted certificates.", cert_path); + } + + if let Some(client_cert_path) = config.client_cert_path() { + let client_cert_pass = config.client_cert_pass(); + let mut file = File::open(client_cert_path)?; + let mut client_cert_data = vec![]; + file.read_to_end(&mut client_cert_data)?; + let pkcs12_archive = Identity::from_pkcs12(&client_cert_data, &client_cert_pass)?; + builder.identity(pkcs12_archive); + log::info!( + "Using {} for client certificate authentication.", + client_cert_path + ); + } + + let connector: tokio_tls::TlsConnector = builder.build()?.into(); + let domain = config.server()?; + + let stream = Self::new_stream(config).await?; + let stream = connector.connect(domain, stream).await?; + let framed = IrcCodec::new(config.encoding())?.framed(stream); + + Ok(Transport::new(&config, framed, tx)) + } + + #[cfg(feature = "tls-rust")] + async fn new_secured_transport( + config: &Config, + tx: UnboundedSender, + ) -> error::Result>> { + let mut builder = ClientConfig::default(); + builder + .root_store + .add_server_trust_anchors(&TLS_SERVER_ROOTS); + + if let Some(cert_path) = config.cert_path() { + let file = File::open(cert_path)?; + let mut cert_data = BufReader::new(file); + builder + .root_store + .add_pem_file(&mut cert_data) + .map_err(|_| { + error::Error::Io(Error::new(ErrorKind::InvalidInput, "invalid cert")) + })?; + log::info!("Added {} to trusted certificates.", cert_path); + } + + if let Some(client_cert_path) = config.client_cert_path() { + let client_cert_pass = PrivateKey(Vec::from(config.client_cert_pass())); + let file = File::open(client_cert_path)?; + let client_cert_data = certs(&mut BufReader::new(file)).map_err(|_| { + error::Error::Io(Error::new(ErrorKind::InvalidInput, "invalid cert")) + })?; + builder + .set_single_client_cert(client_cert_data, client_cert_pass) + .map_err(|err| error::Error::Io(Error::new(ErrorKind::InvalidInput, err)))?; + log::info!( + "Using {} for client certificate authentication.", + client_cert_path + ); + } + + let connector = TlsConnector::from(Arc::new(builder)); + let domain = DNSNameRef::try_from_ascii_str(config.server()?)?; + + let stream = Self::new_stream(config).await?; + let stream = connector.connect(domain, stream).await?; + let framed = IrcCodec::new(config.encoding())?.framed(stream); + + Ok(Transport::new(&config, framed, tx)) + } + + async fn new_mocked_transport( + config: &Config, + tx: UnboundedSender, + ) -> error::Result> { + use encoding::{label::encoding_from_whatwg_label, EncoderTrap}; + + let encoding = encoding_from_whatwg_label(config.encoding()).ok_or_else(|| { + error::Error::UnknownCodec { + codec: config.encoding().to_owned(), + } + })?; + + let init_str = config.mock_initial_value(); + let initial = encoding + .encode(init_str, EncoderTrap::Replace) + .map_err(|data| error::Error::CodecFailed { + codec: encoding.name(), + data: data.into_owned(), + })?; + + let stream = MockStream::new(&initial); + let framed = IrcCodec::new(config.encoding())?.framed(stream); + + Ok(Transport::new(&config, framed, tx)) + } + /// Gets a view of the internal logging if and only if this connection is using a mock stream. /// Otherwise, this will always return `None`. This is used for unit testing. pub fn log_view(&self) -> Option { @@ -194,6 +283,7 @@ impl Stream for Connection { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match &mut *self { Connection::Unsecured(inner) => Pin::new(inner).poll_next(cx), + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] Connection::Secured(inner) => Pin::new(inner).poll_next(cx), Connection::Mock(inner) => Pin::new(inner).poll_next(cx), } @@ -206,6 +296,7 @@ impl Sink for Connection { fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match &mut *self { Connection::Unsecured(inner) => Pin::new(inner).poll_ready(cx), + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] Connection::Secured(inner) => Pin::new(inner).poll_ready(cx), Connection::Mock(inner) => Pin::new(inner).poll_ready(cx), } @@ -214,6 +305,7 @@ impl Sink for Connection { fn start_send(mut self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> { match &mut *self { Connection::Unsecured(inner) => Pin::new(inner).start_send(item), + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] Connection::Secured(inner) => Pin::new(inner).start_send(item), Connection::Mock(inner) => Pin::new(inner).start_send(item), } @@ -222,6 +314,7 @@ impl Sink for Connection { fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match &mut *self { Connection::Unsecured(inner) => Pin::new(inner).poll_flush(cx), + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] Connection::Secured(inner) => Pin::new(inner).poll_flush(cx), Connection::Mock(inner) => Pin::new(inner).poll_flush(cx), } @@ -230,6 +323,7 @@ impl Sink for Connection { fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match &mut *self { Connection::Unsecured(inner) => Pin::new(inner).poll_close(cx), + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] Connection::Secured(inner) => Pin::new(inner).poll_close(cx), Connection::Mock(inner) => Pin::new(inner).poll_close(cx), } diff --git a/src/client/data/config.rs b/src/client/data/config.rs index 931762b..9bfeb50 100644 --- a/src/client/data/config.rs +++ b/src/client/data/config.rs @@ -120,17 +120,21 @@ pub struct Config { #[cfg(feature = "proxy")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub proxy_password: Option, - /// Whether or not to use SSL. - /// Clients will automatically panic if this is enabled without SSL support. + /// Whether or not to use TLS. + /// Clients will automatically panic if this is enabled without TLS support. + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub use_ssl: Option, - /// The path to the SSL certificate for this server in DER format. + pub use_tls: Option, + /// The path to the TLS certificate for this server in DER format. + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub cert_path: Option, - /// The path to a SSL certificate to use for CertFP client authentication in DER format. + /// The path to a TLS certificate to use for CertFP client authentication in DER format. + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub client_cert_path: Option, /// The password for the certificate to use in CertFP authentication. + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub client_cert_pass: Option, /// The encoding type used for this connection. @@ -436,15 +440,20 @@ impl Config { } /// Gets the port of the server specified in the configuration. - /// This defaults to 6697 (or 6667 if use_ssl is specified as false) when not specified. + /// This defaults to 6697 (or 6667 if use_tls is specified as false) when not specified. + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] pub fn port(&self) -> u16 { - self.port - .as_ref() - .cloned() - .unwrap_or(match self.use_ssl() { - true => 6697, - false => 6667 - }) + self.port.as_ref().cloned().unwrap_or(match self.use_tls() { + true => 6697, + false => 6667, + }) + } + + /// Gets the port of the server specified in the configuration. + /// This defaults to 6667 when not specified. + #[cfg(not(any(feature = "tls-native", feature = "tls-rust")))] + pub fn port(&self) -> u16 { + self.port.as_ref().cloned().unwrap_or(6667) } /// Gets the server password specified in the configuration. @@ -490,23 +499,27 @@ impl Config { self.proxy_password.as_ref().map_or("", String::as_str) } - /// Gets whether or not to use SSL with this connection. + /// Gets whether or not to use TLS with this connection. /// This defaults to true when not specified. - pub fn use_ssl(&self) -> bool { - self.use_ssl.as_ref().cloned().map_or(true, |s| s) + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] + pub fn use_tls(&self) -> bool { + self.use_tls.as_ref().cloned().map_or(true, |s| s) } - /// Gets the path to the SSL certificate in DER format if specified. + /// Gets the path to the TLS certificate in DER format if specified. + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] pub fn cert_path(&self) -> Option<&str> { self.cert_path.as_ref().map(String::as_str) } /// Gets the path to the client authentication certificate in DER format if specified. + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] pub fn client_cert_path(&self) -> Option<&str> { self.client_cert_path.as_ref().map(String::as_str) } /// Gets the password to the client authentication certificate. + #[cfg(any(feature = "tls-native", feature = "tls-rust"))] pub fn client_cert_pass(&self) -> &str { self.client_cert_pass.as_ref().map_or("", String::as_str) } @@ -618,9 +631,16 @@ impl Config { #[cfg(test)] mod test { - use super::{Config, Result}; + use super::Config; use std::collections::HashMap; + #[cfg(any( + feature = "json_config", + feature = "toml_config", + feature = "yaml_config" + ))] + use super::Result; + #[allow(unused)] fn test_config() -> Config { Config { diff --git a/src/client/mod.rs b/src/client/mod.rs index ab4120a..0210f88 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -621,20 +621,20 @@ impl ClientState { }; for s in seq { - self.send(NICKSERV(vec!( + self.send(NICKSERV(vec![ s.to_string(), self.config().nickname()?.to_string(), self.config().nick_password().to_string(), - )))?; + ]))?; } *index = 0; self.send(NICK(self.config().nickname()?.to_owned()))? } - self.send(NICKSERV(vec!( + self.send(NICKSERV(vec![ "IDENTIFY".to_string(), - self.config().nick_password().to_string() - ))) + self.config().nick_password().to_string(), + ])) } } diff --git a/src/client/prelude.rs b/src/client/prelude.rs index 6818526..bb0d7f0 100644 --- a/src/client/prelude.rs +++ b/src/client/prelude.rs @@ -21,6 +21,9 @@ //! dealing with IRC channel and user modes. They appear in methods for sending mode commands, //! as well as in the parsed form of received mode commands. +#[cfg(feature = "proxy")] +pub use crate::client::data::ProxyType; + pub use crate::{ client::{data::Config, Client, Sender}, proto::{ diff --git a/src/error.rs b/src/error.rs index 4ac68fd..e848849 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,6 +9,9 @@ use futures_channel::{ }; use thiserror::Error; +#[cfg(feature = "tls-rust")] +use tokio_rustls::webpki::InvalidDNSNameError; + use crate::proto::error::{MessageParseError, ProtocolError}; /// A specialized `Result` type for the `irc` crate. @@ -27,9 +30,15 @@ pub enum Error { Proxy(tokio_socks::Error), /// An internal TLS error. + #[cfg(feature = "tls-native")] #[error("a TLS error occurred")] Tls(#[source] native_tls::Error), + /// An internal DNS error. + #[cfg(feature = "tls-rust")] + #[error("a DNS error occurred")] + Dns(#[source] InvalidDNSNameError), + /// An internal synchronous channel closed. #[error("a sync channel closed")] SyncChannelClosed(#[source] RecvError), @@ -176,12 +185,20 @@ impl From for Error { } } +#[cfg(feature = "tls-native")] impl From for Error { fn from(e: native_tls::Error) -> Error { Error::Tls(e) } } +#[cfg(feature = "tls-rust")] +impl From for Error { + fn from(e: InvalidDNSNameError) -> Error { + Error::Dns(e) + } +} + impl From for Error { fn from(e: RecvError) -> Error { Error::SyncChannelClosed(e)