feature: add proxy config

This commit is contained in:
Théo Gaillard 2020-03-04 14:38:01 +08:00
parent e0a9115941
commit 5bf3909d46
No known key found for this signature in database
GPG key ID: 9C6AAAF893B070FC
14 changed files with 225 additions and 28 deletions

View file

@ -26,6 +26,7 @@ nochanlists = []
json_config = ["serde", "serde_derive", "serde_json"]
toml_config = ["serde", "serde_derive", "toml"]
yaml_config = ["serde", "serde_derive", "serde_yaml"]
proxy = ["tokio-socks"]
# Temporary transitionary features
json = ["json_config"]
@ -44,6 +45,7 @@ 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", "stream"] }
tokio-util = { version = "0.2.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 }
@ -59,3 +61,8 @@ anyhow = "1.0.13"
args = "2.0"
getopts = "0.2"
env_logger = "0.7"
[[example]]
name = "proxy"
path = "examples/proxy.rs"
required-features = ["proxy"]

View file

@ -104,6 +104,11 @@ realname = "Test User"
server = "chat.freenode.net"
port = 6697
password = ""
proxy_type = "None"
proxy_server = "127.0.0.1"
proxy_port = "1080"
proxy_username = ""
proxy_password = ""
use_ssl = true
cert_path = "cert.der"
client_cert_path = "client.der"

View file

@ -17,7 +17,6 @@ async fn main() -> irc::error::Result<()> {
nickname: Some("irc-crate-ci".to_owned()),
server: Some("irc.pdgn.co".to_owned()),
alt_nicks: vec!["[irc-crate-ci]".to_owned()],
use_ssl: true,
..Default::default()
};

28
examples/proxy.rs Normal file
View file

@ -0,0 +1,28 @@
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()),
channels: vec!["#rust-spam".to_owned()],
proxy_type: Some(ProxyType::Socks5),
proxy_server: Some("127.0.0.1".to_owned()),
proxy_port: Some(9050),
..Default::default()
};
let mut client = Client::from_config(config).await?;
client.identify()?;
let mut stream = client.stream()?;
while let Some(message) = stream.next().await.transpose()? {
print!("{}", message);
}
Ok(())
}

View file

@ -7,7 +7,6 @@ async fn main() -> irc::error::Result<()> {
nickname: Some("repeater".to_owned()),
alt_nicks: vec!["blaster".to_owned(), "smg".to_owned()],
server: Some("irc.mozilla.org".to_owned()),
use_ssl: true,
channels: vec!["#rust-spam".to_owned()],
burst_window_length: Some(4),
max_messages_in_burst: Some(4),

View file

@ -7,7 +7,7 @@ async fn main() -> irc::error::Result<()> {
nickname: Some("pickles".to_owned()),
server: Some("irc.mozilla.org".to_owned()),
channels: vec!["#rust-spam".to_owned()],
use_ssl: true,
use_ssl: Some(false),
..Default::default()
};

View file

@ -13,6 +13,12 @@ use tokio::net::TcpStream;
use tokio_tls::{self, TlsStream};
use tokio_util::codec::Decoder;
#[cfg(feature = "proxy")]
use tokio_socks::tcp::Socks5Stream;
#[cfg(feature = "proxy")]
use crate::client::data::ProxyType;
use crate::{
client::{
data::Config,
@ -76,8 +82,8 @@ impl Connection {
}
if config.use_ssl() {
let domain = format!("{}", config.server()?);
log::info!("Connecting via SSL to {}.", domain);
log::info!("Building SSL connection.");
let mut builder = TlsConnector::builder();
if let Some(cert_path) = config.cert_path() {
@ -103,15 +109,14 @@ impl Connection {
}
let connector: tokio_tls::TlsConnector = builder.build()?.into();
let socket = TcpStream::connect(config.to_socket_addrs()?).await?;
let stream = connector.connect(&domain, socket).await?;
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 {
log::info!("Connecting to {}.", config.server()?);
let stream = TcpStream::connect(config.to_socket_addrs()?).await?;
let stream = Self::new_conn(config).await?;
let framed = IrcCodec::new(config.encoding())?.framed(stream);
let transport = Transport::new(&config, framed, tx);
@ -119,6 +124,60 @@ impl Connection {
}
}
#[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?)
}
#[cfg(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()
);
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())
}
}
}
/// 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> {

View file

@ -8,7 +8,6 @@
"password": "",
"server": "irc.test.net",
"port": 6667,
"use_ssl": false,
"encoding": "UTF-8",
"channels": [
"#test",

View file

@ -5,7 +5,6 @@ realname = "test"
server = "irc.test.net"
port = 6667
password = ""
use_ssl = false
encoding = "UTF-8"
channels = ["#test", "#test2"]
umodes = "+BR"

View file

@ -7,7 +7,6 @@ realname: test
server: irc.test.net
port: 6667
password: ""
use_ssl: false
encoding: UTF-8
channels:
- "#test"

View file

@ -7,7 +7,6 @@ use std::{
io::prelude::*,
path::{Path, PathBuf},
};
use tokio::net::ToSocketAddrs;
#[cfg(feature = "json_config")]
use serde_json;
@ -16,6 +15,9 @@ use serde_yaml;
#[cfg(feature = "toml_config")]
use toml;
#[cfg(feature = "proxy")]
use crate::client::data::proxy::ProxyType;
use crate::error::Error::InvalidConfig;
#[cfg(feature = "toml_config")]
use crate::error::TomlError;
@ -98,11 +100,30 @@ pub struct Config {
/// The password to connect to the server.
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub password: Option<String>,
/// The proxy type to connect to.
#[cfg(feature = "proxy")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub proxy_type: Option<ProxyType>,
/// The proxy server to connect to.
#[cfg(feature = "proxy")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub proxy_server: Option<String>,
/// The proxy port to connect on.
#[cfg(feature = "proxy")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub proxy_port: Option<u16>,
/// The username to connect to the proxy server.
#[cfg(feature = "proxy")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub proxy_username: Option<String>,
/// The password to connect to the proxy server.
#[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.
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))]
#[cfg_attr(feature = "serde", serde(default))]
pub use_ssl: bool,
#[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.
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub cert_path: Option<String>,
@ -415,31 +436,64 @@ impl Config {
}
/// Gets the port of the server specified in the configuration.
/// This defaults to 6667 (or 6697 if use_ssl is specified as true) when not specified.
/// This defaults to 6697 (or 6667 if use_ssl is specified as false) when not specified.
pub fn port(&self) -> u16 {
self.port
.as_ref()
.cloned()
.unwrap_or(if self.use_ssl() { 6697 } else { 6667 })
}
/// Return something that can be converted into a socket address by tokio.
pub(crate) fn to_socket_addrs(&self) -> Result<impl ToSocketAddrs + '_> {
let server = self.server()?;
let port = self.port();
Ok((server, port))
.unwrap_or(match self.use_ssl() {
true => 6697,
false => 6667
})
}
/// Gets the server password specified in the configuration.
/// This defaults to a blank string when not specified.
/// This defaults to an empty string when not specified.
pub fn password(&self) -> &str {
self.password.as_ref().map_or("", String::as_str)
}
/// Gets the type of the proxy specified in the configuration.
/// This defaults to a None ProxyType when not specified.
#[cfg(feature = "proxy")]
pub fn proxy_type(&self) -> ProxyType {
self.proxy_type.as_ref().cloned().unwrap_or(ProxyType::None)
}
/// Gets the address of the proxy specified in the configuration.
/// This defaults to "localhost" string when not specified.
#[cfg(feature = "proxy")]
pub fn proxy_server(&self) -> &str {
self.proxy_server
.as_ref()
.map_or("localhost", String::as_str)
}
/// Gets the port of the proxy specified in the configuration.
/// This defaults to 1080 when not specified.
#[cfg(feature = "proxy")]
pub fn proxy_port(&self) -> u16 {
self.proxy_port.as_ref().cloned().unwrap_or(1080)
}
/// Gets the username of the proxy specified in the configuration.
/// This defaults to an empty string when not specified.
#[cfg(feature = "proxy")]
pub fn proxy_username(&self) -> &str {
self.proxy_username.as_ref().map_or("", String::as_str)
}
/// Gets the password of the proxy specified in the configuration.
/// This defaults to an empty string when not specified.
#[cfg(feature = "proxy")]
pub fn proxy_password(&self) -> &str {
self.proxy_password.as_ref().map_or("", String::as_str)
}
/// Gets whether or not to use SSL with this connection.
/// This defaults to false when not specified.
/// This defaults to true when not specified.
pub fn use_ssl(&self) -> bool {
self.use_ssl
self.use_ssl.as_ref().cloned().map_or(true, |s| s)
}
/// Gets the path to the SSL certificate in DER format if specified.

View file

@ -1,7 +1,11 @@
//! Data related to IRC functionality.
pub use crate::client::data::config::Config;
#[cfg(feature = "proxy")]
pub use crate::client::data::proxy::ProxyType;
pub use crate::client::data::user::{AccessLevel, User};
pub mod config;
#[cfg(feature = "proxy")]
pub mod proxy;
pub mod user;

33
src/client/data/proxy.rs Normal file
View file

@ -0,0 +1,33 @@
//! A feature which allow us to connect to IRC via a proxy.
//!
//! ```
//! use irc::client::prelude::Config;
//! use irc::client::data::ProxyType;
//!
//! # fn main() {
//! let config = Config {
//! nickname: Some("test".to_owned()),
//! server: Some("irc.example.com".to_owned()),
//! proxy_type: Some(ProxyType::Socks5),
//! proxy_server: Some("127.0.0.1".to_owned()),
//! proxy_port: Some(9050),
//! ..Config::default()
//! };
//! # }
//! ```
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// An enum which defines which type of proxy should be in use.
#[cfg(feature = "proxy")]
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ProxyType {
/// Does not use any proxy.
None,
/// Use a SOCKS5 proxy.
/// DNS queries are also sent via the proxy.
Socks5,
}

View file

@ -21,6 +21,11 @@ pub enum Error {
#[error("an io error occurred")]
Io(#[source] IoError),
/// An internal proxy error.
#[cfg(feature = "proxy")]
#[error("a proxy error occurred")]
Proxy(tokio_socks::Error),
/// An internal TLS error.
#[error("a TLS error occurred")]
Tls(#[source] native_tls::Error),
@ -164,6 +169,13 @@ impl From<IoError> for Error {
}
}
#[cfg(feature = "proxy")]
impl From<tokio_socks::Error> for Error {
fn from(e: tokio_socks::Error) -> Error {
Error::Proxy(e)
}
}
impl From<native_tls::Error> for Error {
fn from(e: native_tls::Error) -> Error {
Error::Tls(e)