feature: add TLS as feature and support multiples TLS backends (currently native-tls and rustls)
This commit is contained in:
parent
751c56e85b
commit
67e61e0606
16 changed files with 355 additions and 179 deletions
|
@ -36,6 +36,9 @@ jobs:
|
|||
# nochanlists on w/toml
|
||||
- <<: *test
|
||||
env: FEATURES=nochanlists toml_config
|
||||
# tls-rust
|
||||
- <<: *test
|
||||
env: FEATURES=tls-rust
|
||||
|
||||
# Beta
|
||||
- <<: *test
|
||||
|
|
89
Cargo.toml
89
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"]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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::<u8>() {
|
||||
for _ in 0..count {
|
||||
client.send_privmsg(
|
||||
sender.send_privmsg(
|
||||
message.response_target().unwrap_or(target),
|
||||
&msg[n..],
|
||||
)?;
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TcpStream>),
|
||||
#[doc(hidden)]
|
||||
#[cfg(any(feature = "tls-native", feature = "tls-rust"))]
|
||||
Secured(Transport<TlsStream<TcpStream>>),
|
||||
#[doc(hidden)]
|
||||
Mock(Logged<crate::client::mock::MockStream>),
|
||||
Mock(Logged<MockStream>),
|
||||
}
|
||||
|
||||
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<Message>,
|
||||
) -> error::Result<Connection> {
|
||||
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<TcpStream> {
|
||||
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<TcpStream> {
|
||||
Ok(TcpStream::connect((config.server()?, config.port())).await?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "proxy")]
|
||||
async fn new_conn(config: &Config) -> error::Result<TcpStream> {
|
||||
async fn new_stream(config: &Config) -> error::Result<TcpStream> {
|
||||
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<Message>,
|
||||
) -> error::Result<Transport<TcpStream>> {
|
||||
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<Message>,
|
||||
) -> error::Result<Transport<TlsStream<TcpStream>>> {
|
||||
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<Message>,
|
||||
) -> error::Result<Transport<TlsStream<TcpStream>>> {
|
||||
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<Message>,
|
||||
) -> error::Result<Transport<MockStream>> {
|
||||
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<LogView> {
|
||||
|
@ -194,6 +283,7 @@ impl Stream for Connection {
|
|||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
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<Message> for Connection {
|
|||
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
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<Message> 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<Message> for Connection {
|
|||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
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<Message> for Connection {
|
|||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
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),
|
||||
}
|
||||
|
|
|
@ -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<String>,
|
||||
/// 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<bool>,
|
||||
/// The path to the SSL certificate for this server in DER format.
|
||||
pub use_tls: Option<bool>,
|
||||
/// 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<String>,
|
||||
/// 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<String>,
|
||||
/// 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<String>,
|
||||
/// 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 {
|
||||
|
|
|
@ -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(),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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::{
|
||||
|
|
17
src/error.rs
17
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<tokio_socks::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls-native")]
|
||||
impl From<native_tls::Error> for Error {
|
||||
fn from(e: native_tls::Error) -> Error {
|
||||
Error::Tls(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls-rust")]
|
||||
impl From<InvalidDNSNameError> for Error {
|
||||
fn from(e: InvalidDNSNameError) -> Error {
|
||||
Error::Dns(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RecvError> for Error {
|
||||
fn from(e: RecvError) -> Error {
|
||||
Error::SyncChannelClosed(e)
|
||||
|
|
Loading…
Reference in a new issue