feature: add TLS as feature and support multiples TLS backends (currently native-tls and rustls)

This commit is contained in:
Théo Gaillard 2020-03-07 13:17:35 +08:00
parent 751c56e85b
commit 67e61e0606
No known key found for this signature in database
GPG key ID: 9C6AAAF893B070FC
16 changed files with 355 additions and 179 deletions

View file

@ -36,6 +36,9 @@ jobs:
# nochanlists on w/toml
- <<: *test
env: FEATURES=nochanlists toml_config
# tls-rust
- <<: *test
env: FEATURES=tls-rust
# Beta
- <<: *test

View file

@ -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"]

View file

@ -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"

View file

@ -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) => {

View file

@ -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..],
)?;

View file

@ -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(())
}

View file

@ -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") {
match message.command {
Command::PRIVMSG(ref target, ref msg) => {
if msg.contains(client.current_nickname()) {
sender.send_privmsg(target, "Hi!")?;
}
}
_ => (),
}
}
Ok(())
}

View file

@ -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(())

View file

@ -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()
};

View file

@ -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()
};

View file

@ -247,11 +247,9 @@ impl FromStr for Message {
args.push(suffix);
}
Message::with_tags(tags, prefix, command, args).map_err(|e| {
ProtocolError::InvalidMessage {
Message::with_tags(tags, prefix, command, args).map_err(|e| ProtocolError::InvalidMessage {
string: s.to_owned(),
cause: e,
}
})
}
}

View file

@ -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,31 +85,81 @@ 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(),
log::info!("Connecting via mock to {}.", config.server()?);
return Ok(Connection::Mock(Logged::wrap(
Self::new_mocked_transport(config, tx).await?,
)));
}
})?;
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(),
})?;
#[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?,
));
}
}
let stream = crate::client::mock::MockStream::new(&initial);
log::info!("Connecting to {}.", config.server()?);
Ok(Connection::Unsecured(
Self::new_unsecured_transport(config, tx).await?,
))
}
#[cfg(not(feature = "proxy"))]
async fn new_stream(config: &Config) -> error::Result<TcpStream> {
Ok(TcpStream::connect((config.server()?, config.port())).await?)
}
#[cfg(feature = "proxy")]
async fn new_stream(config: &Config) -> error::Result<TcpStream> {
let server = config.server()?;
let port = config.port();
let address = (server, port);
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 = (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,
address,
proxy_username,
proxy_password,
)
.await?
.into_inner());
}
Ok(Socks5Stream::connect(proxy, address).await?.into_inner())
}
}
}
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);
let transport = Transport::new(&config, framed, tx);
return Ok(Connection::Mock(Logged::wrap(transport)));
Ok(Transport::new(&config, framed, tx))
}
if config.use_ssl() {
log::info!("Building SSL connection.");
#[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() {
@ -107,75 +183,88 @@ impl Connection {
client_cert_path
);
}
let connector: tokio_tls::TlsConnector = builder.build()?.into();
let domain = config.server()?;
let socket = Self::new_conn(config).await?;
let stream = connector.connect(config.server()?, socket).await?;
let stream = Self::new_stream(config).await?;
let stream = connector.connect(domain, stream).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?;
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);
let transport = Transport::new(&config, framed, tx);
Ok(Connection::Unsecured(transport))
}
Ok(Transport::new(&config, framed, tx))
}
#[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);
async fn new_mocked_transport(
config: &Config,
tx: UnboundedSender<Message>,
) -> error::Result<Transport<MockStream>> {
use encoding::{label::encoding_from_whatwg_label, EncoderTrap};
log::info!(
"Connecting to {:?} using SSL: {}",
address,
config.use_ssl()
);
Ok(TcpStream::connect(address).await?)
let encoding = encoding_from_whatwg_label(config.encoding()).ok_or_else(|| {
error::Error::UnknownCodec {
codec: config.encoding().to_owned(),
}
})?;
#[cfg(feature = "proxy")]
async fn new_conn(config: &Config) -> error::Result<TcpStream> {
let server = config.server()?;
let port = config.port();
let address = (server, port);
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(),
})?;
log::info!(
"Connecting to {:?} using SSL: {}",
address,
config.use_ssl()
);
let stream = MockStream::new(&initial);
let framed = IrcCodec::new(config.encoding())?.framed(stream);
match config.proxy_type() {
ProxyType::None => Ok(TcpStream::connect(address).await?),
_ => {
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);
if !proxy_username.is_empty() || !proxy_password.is_empty() {
return Ok(Socks5Stream::connect_with_password(
proxy,
address,
proxy_username,
proxy_password,
)
.await?
.into_inner());
}
Ok(Socks5Stream::connect(proxy, address).await?.into_inner())
}
}
Ok(Transport::new(&config, framed, tx))
}
/// Gets a view of the internal logging if and only if this connection is using a mock stream.
@ -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),
}

View file

@ -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,17 +440,22 @@ 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() {
self.port.as_ref().cloned().unwrap_or(match self.use_tls() {
true => 6697,
false => 6667
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.
/// This defaults to an empty string when not specified.
pub fn password(&self) -> &str {
@ -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 {

View file

@ -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(),
]))
}
}

View file

@ -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::{

View file

@ -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)