Skip to content

Commit

Permalink
Implement mysql_clear_password plugin support
Browse files Browse the repository at this point in the history
  • Loading branch information
blackbeam committed Feb 25, 2023
1 parent 45ae006 commit 77e732a
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 17 deletions.
69 changes: 52 additions & 17 deletions src/conn/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use mysql_common::{
constants::UTF8MB4_GENERAL_CI,
crypto,
io::{ParseBuf, ReadMysqlExt},
misc::raw::Either,
misc::raw::{bytes::NullBytes, Either, RawBytes},
named_params::parse_named_params,
packets::{
binlog_request::BinlogRequest, AuthPlugin, AuthSwitchRequest, Column, ComStmtClose,
Expand All @@ -20,8 +20,6 @@ use mysql_common::{
OldAuthSwitchRequest, OldEofPacket, ResultSetTerminator, SessionStateInfo,
},
proto::{codec::Compression, sync_framed::MySyncFramed, MySerialize},
row::{Row, RowDeserializer},
value::ServerSide,
};

use mysql_common::{
Expand Down Expand Up @@ -536,12 +534,42 @@ impl Conn {
}
}

if matches!(
auth_switch_request.auth_plugin(),
AuthPlugin::Other(Cow::Borrowed(b"mysql_clear_password"))
) {
if !self.0.opts.get_enable_cleartext_plugin() {
return Err(DriverError(UnknownAuthPlugin(
"mysql_clear_password".into(),
)));
}
}

let nonce = auth_switch_request.plugin_data();
let plugin_data = auth_switch_request
.auth_plugin()
.gen_data(self.0.opts.get_pass(), nonce)
.map(Either::Left)
.unwrap_or_else(|| Either::Right([]));
let plugin_data = match auth_switch_request.auth_plugin() {
x @ AuthPlugin::MysqlOldPassword => {
x.gen_data(self.0.opts.get_pass(), nonce).map(Either::Left)
}
x @ AuthPlugin::MysqlNativePassword => {
x.gen_data(self.0.opts.get_pass(), nonce).map(Either::Left)
}
x @ AuthPlugin::CachingSha2Password => {
x.gen_data(self.0.opts.get_pass(), nonce).map(Either::Left)
}
AuthPlugin::Other(ref name) => {
if name.as_ref() == b"mysql_clear_password" {
Some(Either::Right(Either::Left(
RawBytes::<NullBytes>::new(
self.0.opts.get_pass().unwrap_or_default().as_bytes(),
)
.into_owned(),
)))
} else {
Some(Either::Right(Either::Right([])))
}
}
}
.unwrap_or_else(|| Either::Right(Either::Right([])));
self.write_struct(&plugin_data)?;
self.continue_auth(&auth_switch_request.auth_plugin(), nonce, true)
}
Expand Down Expand Up @@ -586,13 +614,13 @@ impl Conn {
nonce
};

let auth_plugin = handshake
.auth_plugin()
.unwrap_or(AuthPlugin::MysqlNativePassword);
if let AuthPlugin::Other(ref name) = auth_plugin {
let plugin_name = String::from_utf8_lossy(name).into();
return Err(DriverError(UnknownAuthPlugin(plugin_name)));
}
// Allow only CachingSha2Password and MysqlNativePassword here
// because sha256_password is deprecated and other plugins won't
// appear here.
let auth_plugin = match handshake.auth_plugin() {
Some(x @ AuthPlugin::CachingSha2Password) => x,
_ => AuthPlugin::MysqlNativePassword,
};

let auth_data = auth_plugin.gen_data(self.0.opts.get_pass(), &*nonce);
self.write_handshake_response(&auth_plugin, auth_data.as_deref())?;
Expand Down Expand Up @@ -714,8 +742,15 @@ impl Conn {
Ok(())
}
AuthPlugin::Other(ref name) => {
let plugin_name = String::from_utf8_lossy(name).into();
Err(DriverError(UnknownAuthPlugin(plugin_name)))
if name.as_ref() == b"mysql_clear_password"
&& self.0.opts.get_enable_cleartext_plugin()
{
self.continue_mysql_native_password_auth(nonce, auth_switched)?;
Ok(())
} else {
let plugin_name = String::from_utf8_lossy(name).into();
Err(DriverError(UnknownAuthPlugin(plugin_name)))
}
}
}
}
Expand Down
69 changes: 69 additions & 0 deletions src/conn/opts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,17 @@ pub(crate) struct InnerOpts {
/// Available via `secure_auth` connection url parameter.
secure_auth: bool,

/// Enables Client-Side Cleartext Pluggable Authentication (defaults to `false`).
///
/// Enables client to send passwords to the server as cleartext, without hashing or encryption
/// (consult MySql documentation for more info).
///
/// # Security Notes
///
/// Sending passwords as cleartext may be a security problem in some configurations. Please
/// consider using TLS or encrypted tunnels for server connection.
enable_cleartext_plugin: bool,

/// For tests only
#[cfg(test)]
pub injected_socket: Option<String>,
Expand Down Expand Up @@ -249,6 +260,7 @@ impl Default for InnerOpts {
additional_capabilities: CapabilityFlags::empty(),
connect_attrs: HashMap::new(),
secure_auth: true,
enable_cleartext_plugin: false,
#[cfg(test)]
injected_socket: None,
}
Expand Down Expand Up @@ -467,6 +479,31 @@ impl Opts {
pub fn get_secure_auth(&self) -> bool {
self.0.secure_auth
}

/// Returns `true` if `mysql_clear_password` plugin support is enabled (defaults to `false`).
///
/// `mysql_clear_password` enables client to send passwords to the server as cleartext, without
/// hashing or encryption (consult MySql documentation for more info).
///
/// # Security Notes
///
/// Sending passwords as cleartext may be a security problem in some configurations. Please
/// consider using TLS or encrypted tunnels for server connection.
///
/// # Connection URL
///
/// Use `enable_cleartext_plugin` URL parameter to set this value. E.g.
///
/// ```
/// # use mysql::*;
/// # fn main() -> Result<()> {
/// let opts = Opts::from_url("mysql://localhost/db?enable_cleartext_plugin=true")?;
/// assert!(opts.get_enable_cleartext_plugin());
/// # Ok(()) }
/// ```
pub fn get_enable_cleartext_plugin(&self) -> bool {
self.0.enable_cleartext_plugin
}
}

/// Provides a way to build [`Opts`](struct.Opts.html).
Expand Down Expand Up @@ -564,6 +601,12 @@ impl OptsBuilder {
}
}
}
"enable_cleartext_plugin" => match value.parse::<bool>() {
Ok(parsed) => self.opts.0.enable_cleartext_plugin = parsed,
Err(_) => {
return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
}
},
"secure_auth" => match value.parse::<bool>() {
Ok(parsed) => self.opts.0.secure_auth = parsed,
Err(_) => {
Expand Down Expand Up @@ -928,6 +971,32 @@ impl OptsBuilder {
self.opts.0.secure_auth = secure_auth;
self
}

/// Enables Client-Side Cleartext Pluggable Authentication (defaults to `false`).
///
/// Enables client to send passwords to the server as cleartext, without hashing or encryption
/// (consult MySql documentation for more info).
///
/// # Security Notes
///
/// Sending passwords as cleartext may be a security problem in some configurations. Please
/// consider using TLS or encrypted tunnels for server connection.
///
/// # Connection URL
///
/// Use `enable_cleartext_plugin` URL parameter to set this value. E.g.
///
/// ```
/// # use mysql::*;
/// # fn main() -> Result<()> {
/// let opts = Opts::from_url("mysql://localhost/db?enable_cleartext_plugin=true")?;
/// assert!(opts.get_enable_cleartext_plugin());
/// # Ok(()) }
/// ```
pub fn enable_cleartext_plugin(mut self, enable_cleartext_plugin: bool) -> Self {
self.opts.0.enable_cleartext_plugin = enable_cleartext_plugin;
self
}
}

impl From<OptsBuilder> for Opts {
Expand Down

0 comments on commit 77e732a

Please sign in to comment.