Skip to content

Commit 74473a9

Browse files
authored
Merge pull request jonhoo#6 from datafuse-extras/auth
add mysql_native_password auth support
2 parents eed337c + bb7ba7b commit 74473a9

File tree

6 files changed

+351
-27
lines changed

6 files changed

+351
-27
lines changed

examples/serve_auth.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//! After running this, you should be able to run:
2+
//!
3+
//! ```console
4+
//! $ echo "SELECT * FROM foo" | mysql -h 127.0.0.1 --table
5+
//! $
6+
//! ```
7+
8+
extern crate msql_srv;
9+
extern crate mysql;
10+
extern crate mysql_common as myc;
11+
12+
use msql_srv::*;
13+
use std::io;
14+
use std::net;
15+
use std::thread;
16+
17+
struct Backend;
18+
impl<W: io::Write> MysqlShim<W> for Backend {
19+
type Error = io::Error;
20+
21+
fn on_prepare(&mut self, _: &str, info: StatementMetaWriter<W>) -> io::Result<()> {
22+
info.reply(42, &[], &[])
23+
}
24+
fn on_execute(
25+
&mut self,
26+
_: u32,
27+
_: msql_srv::ParamParser,
28+
results: QueryResultWriter<W>,
29+
) -> io::Result<()> {
30+
results.completed(0, 0)
31+
}
32+
fn on_close(&mut self, _: u32) {}
33+
34+
fn on_query(&mut self, sql: &str, results: QueryResultWriter<W>) -> io::Result<()> {
35+
println!("execute sql {:?}", sql);
36+
results.start(&[])?.finish()
37+
}
38+
39+
/// authenticate method for the specified plugin
40+
fn authenticate(
41+
&self,
42+
auth_plugin: &str,
43+
username: &[u8],
44+
salt: &[u8],
45+
auth_data: &[u8],
46+
) -> bool {
47+
println!(
48+
"auth_plugin, {:?}, user: {:?} , salt: {:?}, auth_data:{:?}",
49+
auth_plugin, username, salt, auth_data
50+
);
51+
52+
username == "default".as_bytes()
53+
}
54+
55+
fn on_init(&mut self, _: &str, _: InitWriter<'_, W>) -> Result<(), Self::Error> {
56+
Ok(())
57+
}
58+
59+
fn version(&self) -> &str {
60+
// 5.1.10 because that's what Ruby's ActiveRecord requires
61+
"5.1.10-alpha-msql-proxy"
62+
}
63+
64+
fn connect_id(&self) -> u32 {
65+
u32::from_le_bytes([0x08, 0x00, 0x00, 0x00])
66+
}
67+
68+
fn default_auth_plugin(&self) -> &str {
69+
"mysql_native_password"
70+
}
71+
72+
fn auth_plugin_for_username(&self, _user: &[u8]) -> &str {
73+
"mysql_native_password"
74+
}
75+
76+
fn salt(&self) -> [u8; 20] {
77+
let bs = ";X,po_k}>o6^Wz!/kM}N".as_bytes();
78+
let mut scramble: [u8; 20] = [0; 20];
79+
for i in 0..20 {
80+
scramble[i] = bs[i];
81+
if scramble[i] == b'\0' || scramble[i] == b'$' {
82+
scramble[i] = scramble[i] + 1;
83+
}
84+
}
85+
scramble
86+
}
87+
}
88+
89+
fn main() {
90+
let mut threads = Vec::new();
91+
let listener = net::TcpListener::bind("127.0.0.1:3306").unwrap();
92+
93+
while let Ok((s, _)) = listener.accept() {
94+
println!("{:?}", "got one socket");
95+
threads.push(thread::spawn(move || {
96+
MysqlIntermediary::run_on_tcp(Backend, s).unwrap();
97+
}));
98+
}
99+
100+
for t in threads {
101+
t.join().unwrap();
102+
}
103+
}
104+
105+
#[test]
106+
fn it_works() {
107+
let c: u8 = b'\0';
108+
let d: u8 = 0 as u8;
109+
let e: u8 = 0x00;
110+
111+
assert_eq!(c, d);
112+
assert_eq!(e, d);
113+
}

examples/serve_one.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ fn main() {
4141
let listener = net::TcpListener::bind("127.0.0.1:3306").unwrap();
4242

4343
while let Ok((s, _)) = listener.accept() {
44+
println!("{:?}", "got one socket");
4445
threads.push(thread::spawn(move || {
4546
MysqlIntermediary::run_on_tcp(Backend, s).unwrap();
4647
}));

src/commands.rs

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,78 @@
11
use crate::myc::constants::{CapabilityFlags, Command as CommandByte};
22

33
#[derive(Debug)]
4-
pub struct ClientHandshake<'a> {
4+
pub struct ClientHandshake {
55
capabilities: CapabilityFlags,
66
maxps: u32,
77
collation: u16,
8-
username: &'a [u8],
8+
pub(crate) db: Option<Vec<u8>>,
9+
pub(crate) username: Vec<u8>,
10+
pub(crate) auth_response: Vec<u8>,
11+
pub(crate) auth_plugin: Vec<u8>,
912
}
1013

1114
#[allow(clippy::branches_sharing_code)]
12-
pub fn client_handshake(i: &[u8]) -> nom::IResult<&[u8], ClientHandshake<'_>> {
15+
pub fn client_handshake(i: &[u8]) -> nom::IResult<&[u8], ClientHandshake> {
1316
// mysql handshake protocol documentation
1417
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_response.html
15-
1618
let (i, cap) = nom::number::complete::le_u16(i)?;
1719

18-
if CapabilityFlags::from_bits_truncate(cap as u32).contains(CapabilityFlags::CLIENT_PROTOCOL_41)
19-
{
20+
let mut capabilities = CapabilityFlags::from_bits_truncate(cap as u32);
21+
if capabilities.contains(CapabilityFlags::CLIENT_PROTOCOL_41) {
2022
// HandshakeResponse41
2123
let (i, cap2) = nom::number::complete::le_u16(i)?;
2224
let cap = (cap2 as u32) << 16 | cap as u32;
2325

26+
capabilities = CapabilityFlags::from_bits_truncate(cap as u32);
27+
2428
let (i, maxps) = nom::number::complete::le_u32(i)?;
2529
let (i, collation) = nom::bytes::complete::take(1u8)(i)?;
30+
2631
let (i, _) = nom::bytes::complete::take(23u8)(i)?;
32+
2733
let (i, username) = nom::bytes::complete::take_until(&b"\0"[..])(i)?;
2834
let (i, _) = nom::bytes::complete::tag(b"\0")(i)?;
2935

36+
let (i, auth_response) =
37+
if capabilities.contains(CapabilityFlags::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) {
38+
let (i, size) = read_length_encoded_number(i)?;
39+
nom::bytes::complete::take(size)(i)?
40+
} else if capabilities.contains(CapabilityFlags::CLIENT_SECURE_CONNECTION) {
41+
let (i, size) = nom::number::complete::le_u8(i)?;
42+
nom::bytes::complete::take(size)(i)?
43+
} else {
44+
nom::bytes::complete::take_until(&b"\0"[..])(i)?
45+
};
46+
47+
let (i, db) =
48+
if capabilities.contains(CapabilityFlags::CLIENT_CONNECT_WITH_DB) && !i.is_empty() {
49+
let (i, db) = nom::bytes::complete::take_until(&b"\0"[..])(i)?;
50+
let (i, _) = nom::bytes::complete::tag(b"\0")(i)?;
51+
(i, Some(db))
52+
} else {
53+
(i, None)
54+
};
55+
56+
let (i, auth_plugin) =
57+
if capabilities.contains(CapabilityFlags::CLIENT_PLUGIN_AUTH) && !i.is_empty() {
58+
let (i, auth_plugin) = nom::bytes::complete::take_until(&b"\0"[..])(i)?;
59+
60+
let (i, _) = nom::bytes::complete::tag(b"\0")(i)?;
61+
(i, auth_plugin)
62+
} else {
63+
(i, &b""[..])
64+
};
65+
3066
Ok((
3167
i,
3268
ClientHandshake {
3369
capabilities: CapabilityFlags::from_bits_truncate(cap),
3470
maxps,
3571
collation: u16::from(collation[0]),
36-
username,
72+
username: username.to_vec(),
73+
db: db.map(|c| c.to_vec()),
74+
auth_response: auth_response.to_vec(),
75+
auth_plugin: auth_plugin.to_vec(),
3776
},
3877
))
3978
} else {
@@ -42,19 +81,51 @@ pub fn client_handshake(i: &[u8]) -> nom::IResult<&[u8], ClientHandshake<'_>> {
4281
let (i, maxps2) = nom::number::complete::le_u8(i)?;
4382
let maxps = (maxps2 as u32) << 16 | maxps1 as u32;
4483
let (i, username) = nom::bytes::complete::take_until(&b"\0"[..])(i)?;
84+
let (i, _) = nom::bytes::complete::tag(b"\0")(i)?;
85+
86+
let (i, auth_response, db) =
87+
if capabilities.contains(CapabilityFlags::CLIENT_CONNECT_WITH_DB) {
88+
let (i, auth_response) = nom::bytes::complete::tag(b"\0")(i)?;
89+
let (i, _) = nom::bytes::complete::tag(b"\0")(i)?;
90+
91+
let (i, db) = nom::bytes::complete::tag(b"\0")(i)?;
92+
let (i, _) = nom::bytes::complete::tag(b"\0")(i)?;
93+
94+
(i, auth_response, Some(db))
95+
} else {
96+
(&b""[..], i, None)
97+
};
4598

4699
Ok((
47100
i,
48101
ClientHandshake {
49102
capabilities: CapabilityFlags::from_bits_truncate(cap as u32),
50103
maxps,
51104
collation: 0,
52-
username,
105+
username: username.to_vec(),
106+
db: db.map(|c| c.to_vec()),
107+
auth_response: auth_response.to_vec(),
108+
auth_plugin: vec![],
53109
},
54110
))
55111
}
56112
}
57113

114+
fn read_length_encoded_number(i: &[u8]) -> nom::IResult<&[u8], u64> {
115+
let (i, b) = nom::number::complete::le_u8(i)?;
116+
let size: usize = match b {
117+
0xfb => return Ok((i, 0)),
118+
0xfc => 2,
119+
0xfd => 3,
120+
0xfe => 8,
121+
_ => return Ok((i, b as u64)),
122+
};
123+
let mut bytes = [0u8; 8];
124+
let (i, b) = nom::bytes::complete::take(size)(i)?;
125+
bytes[..size].copy_from_slice(b);
126+
Ok((i, u64::from_le_bytes(bytes)))
127+
}
128+
58129
#[derive(Debug, PartialEq, Eq)]
59130
pub enum Command<'a> {
60131
Query(&'a [u8]),
@@ -143,10 +214,15 @@ mod tests {
143214
#[test]
144215
fn it_parses_handshake() {
145216
let data = &[
146-
0x25, 0x00, 0x00, 0x01, 0x85, 0xa6, 0x3f, 0x20, 0x00, 0x00, 0x00, 0x01, 0x21, 0x00,
217+
0x5b, 0x00, 0x00, 0x01, 0x8d, 0xa6, 0xff, 0x09, 0x00, 0x00, 0x00, 0x01, 0x21, 0x00,
147218
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
148-
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x6f, 0x6e, 0x00, 0x00,
219+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c,
220+
0x74, 0x00, 0x14, 0xf7, 0xd1, 0x6c, 0xe9, 0x0d, 0x2f, 0x34, 0xb0, 0x2f, 0xd8, 0x1d,
221+
0x18, 0xc7, 0xa4, 0xe8, 0x98, 0x97, 0x67, 0xeb, 0xad, 0x64, 0x65, 0x66, 0x61, 0x75,
222+
0x6c, 0x74, 0x00, 0x6d, 0x79, 0x73, 0x71, 0x6c, 0x5f, 0x6e, 0x61, 0x74, 0x69, 0x76,
223+
0x65, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x00,
149224
];
225+
150226
let r = Cursor::new(&data[..]);
151227
let mut pr = PacketReader::new(r);
152228
let (_, p) = pr.next().unwrap().unwrap();
@@ -158,14 +234,14 @@ mod tests {
158234
assert!(handshake
159235
.capabilities
160236
.contains(CapabilityFlags::CLIENT_MULTI_RESULTS));
161-
assert!(!handshake
237+
assert!(handshake
162238
.capabilities
163239
.contains(CapabilityFlags::CLIENT_CONNECT_WITH_DB));
164-
assert!(!handshake
240+
assert!(handshake
165241
.capabilities
166242
.contains(CapabilityFlags::CLIENT_DEPRECATE_EOF));
167243
assert_eq!(handshake.collation, UTF8_GENERAL_CI);
168-
assert_eq!(handshake.username, &b"jon"[..]);
244+
assert_eq!(handshake.username, &b"default"[..]);
169245
assert_eq!(handshake.maxps, 16777216);
170246
}
171247

0 commit comments

Comments
 (0)