feature: add proxy config
This commit is contained in:
parent
e0a9115941
commit
5bf3909d46
14 changed files with 225 additions and 28 deletions
|
@ -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"]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
28
examples/proxy.rs
Normal 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(())
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
@ -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> {
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
"password": "",
|
||||
"server": "irc.test.net",
|
||||
"port": 6667,
|
||||
"use_ssl": false,
|
||||
"encoding": "UTF-8",
|
||||
"channels": [
|
||||
"#test",
|
||||
|
|
|
@ -5,7 +5,6 @@ realname = "test"
|
|||
server = "irc.test.net"
|
||||
port = 6667
|
||||
password = ""
|
||||
use_ssl = false
|
||||
encoding = "UTF-8"
|
||||
channels = ["#test", "#test2"]
|
||||
umodes = "+BR"
|
||||
|
|
|
@ -7,7 +7,6 @@ realname: test
|
|||
server: irc.test.net
|
||||
port: 6667
|
||||
password: ""
|
||||
use_ssl: false
|
||||
encoding: UTF-8
|
||||
channels:
|
||||
- "#test"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
33
src/client/data/proxy.rs
Normal 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,
|
||||
}
|
12
src/error.rs
12
src/error.rs
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue