summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs168
1 files changed, 168 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..9a24515
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,168 @@
+use mt_net::{CltSender, SenderExt, ToCltPkt, ToSrvPkt};
+use rand::RngCore;
+use sha2::Sha256;
+use srp::{client::SrpClient, groups::G_2048};
+use std::time::Duration;
+use tokio::time::{interval, Interval};
+
+enum AuthState {
+ Init(ToSrvPkt, Interval),
+ Verify(Vec<u8>, SrpClient<'static, Sha256>),
+ Done(bool),
+}
+
+pub struct Auth {
+ tx: CltSender,
+ state: AuthState,
+ username: String,
+ password: String,
+ lang: String,
+}
+
+impl Auth {
+ pub fn new(
+ tx: CltSender,
+ username: impl Into<String>,
+ password: impl Into<String>,
+ lang: impl Into<String>,
+ ) -> Self {
+ let username = username.into();
+ Self {
+ tx,
+ state: AuthState::Init(
+ ToSrvPkt::Init {
+ serialize_version: 29,
+ proto_version: 40..=40,
+ player_name: username.clone(),
+ send_full_item_meta: false,
+ },
+ interval(Duration::from_millis(100)),
+ ),
+ username,
+ password: password.into(),
+ lang: lang.into(),
+ }
+ }
+
+ pub fn username(&self) -> &str {
+ &self.username
+ }
+
+ pub fn password(&self) -> &str {
+ &self.password
+ }
+
+ pub fn lang(&self) -> &str {
+ &self.lang
+ }
+
+ pub fn mut_init_pkt(&mut self) -> Option<&mut ToSrvPkt> {
+ if let AuthState::Init(pkt, _) = &mut self.state {
+ Some(pkt)
+ } else {
+ None
+ }
+ }
+
+ pub async fn poll(&mut self) {
+ match &mut self.state {
+ AuthState::Init(pkt, interval) => {
+ loop {
+ // cancel safety: since init pkt is unreliable, cancelation is not an issue
+ self.tx.send(pkt).await.unwrap();
+ interval.tick().await;
+ }
+ }
+ AuthState::Verify(_, _) | AuthState::Done(false) => futures::future::pending().await,
+ AuthState::Done(unconsumed) => {
+ *unconsumed = false;
+ }
+ }
+ }
+
+ pub async fn handle_pkt(&mut self, pkt: &ToCltPkt) {
+ use ToCltPkt::*;
+ match pkt {
+ Hello {
+ auth_methods,
+ username: name,
+ ..
+ } => {
+ use mt_net::AuthMethod;
+
+ if !matches!(self.state, AuthState::Init(_, _)) {
+ return;
+ }
+
+ let srp = SrpClient::<Sha256>::new(&G_2048);
+
+ let mut rand_bytes = vec![0; 32];
+ rand::thread_rng().fill_bytes(&mut rand_bytes);
+
+ if &self.username != name {
+ panic!("username changed");
+ }
+
+ if auth_methods.contains(AuthMethod::FirstSrp) {
+ let verifier = srp.compute_verifier(
+ self.username.to_lowercase().as_bytes(),
+ self.password.as_bytes(),
+ &rand_bytes,
+ );
+
+ self.tx
+ .send(&ToSrvPkt::FirstSrp {
+ salt: rand_bytes,
+ verifier,
+ empty_passwd: self.password.is_empty(),
+ })
+ .await
+ .unwrap();
+
+ self.state = AuthState::Done(false);
+ } else if auth_methods.contains(AuthMethod::Srp) {
+ let a = srp.compute_public_ephemeral(&rand_bytes);
+
+ self.tx
+ .send(&ToSrvPkt::SrpBytesA { a, no_sha1: true })
+ .await
+ .unwrap();
+
+ self.state = AuthState::Verify(rand_bytes, srp);
+ } else {
+ panic!("unsupported auth methods: {auth_methods:?}");
+ }
+ }
+ SrpBytesSaltB { salt, b } => {
+ if let AuthState::Verify(a, srp) = &self.state {
+ let m = srp
+ .process_reply(
+ a,
+ self.username.to_lowercase().as_bytes(),
+ self.password.as_bytes(),
+ salt,
+ b,
+ )
+ .unwrap()
+ .proof()
+ .into();
+
+ self.tx.send(&ToSrvPkt::SrpBytesM { m }).await.unwrap();
+
+ self.state = AuthState::Done(false);
+ }
+ }
+ AcceptAuth { .. } => {
+ self.tx
+ .send(&ToSrvPkt::Init2 {
+ lang: self.lang.clone(),
+ })
+ .await
+ .unwrap();
+
+ self.state = AuthState::Done(true);
+ }
+ _ => {}
+ }
+ }
+}