wgtk/net/element/
login.rs

1//! Definition of elements related to login application.
2//! 
3//! When a client send a login request to the login app, it might be
4//! encrypted with RSA, the server then decide which response to return
5//! depending on the input, it might send a challenge that is required.
6//! When the login succeed, the server sends a login key that is used
7//! by the client when first connecting to the base app.
8//! 
9//! This app also provides a way to ping test the server.
10
11use std::io::{self, Read, Write};
12use std::net::SocketAddrV4;
13use std::sync::Arc;
14use std::time::Duration;
15
16use rsa::{RsaPrivateKey, RsaPublicKey};
17use blowfish::Blowfish;
18
19use crate::net::filter::{RsaWriter, RsaReader, BlowfishWriter, BlowfishReader};
20use crate::util::io::*;
21
22use super::{Element, SimpleElement, TopElement, ElementLength};
23
24
25/// This modules defines in constants the numerical identifiers for
26/// login app elements.
27pub mod id {
28    pub const LOGIN_REQUEST: u8         = 0x00;
29    pub const PING: u8                  = 0x02;
30    pub const CHALLENGE_RESPONSE: u8    = 0x03;
31}
32
33
34/// A ping sent from the client to the login app or replied from the
35/// login app to the client.
36#[derive(Debug, Clone, Copy)]
37pub struct Ping {
38    /// The number of the ping, the same number must be sent back to
39    /// the client when login app receives it.
40    pub num: u8,
41}
42
43impl SimpleElement for Ping {
44
45    fn encode(&self, write: &mut impl Write) -> io::Result<()> {
46        write.write_u8(self.num)
47    }
48
49    fn decode(read: &mut impl Read, _len: usize) -> io::Result<Self> {
50        Ok(Self { num: read.read_u8()? })
51    }
52
53}
54
55impl TopElement for Ping {
56    const LEN: ElementLength = ElementLength::Fixed(1);
57}
58
59
60/// A login request to be sent with [`LoginCodec`], send from client to 
61/// server when it wants to log into and gain access to a base app.
62#[derive(Debug, Default, Clone)]
63pub struct LoginRequest {
64    pub protocol: u32,
65    pub username: String,
66    pub password: String,
67    pub blowfish_key: Vec<u8>,
68    pub context: String,
69    pub digest: Option<[u8; 16]>,
70    pub nonce: u32,
71}
72
73
74/// Describe all kinds of responses returned from server to client when
75/// the client attempt to login. This includes challenge or error codes.
76#[derive(Debug, Clone)]
77pub enum LoginResponse {
78    /// The login is successful.
79    Success(LoginSuccess),
80    /// An error happened server-side and the login process cannot succeed.
81    Error(LoginError, String),
82    /// A challenge must be completed in order to have a response.
83    Challenge(LoginChallenge),
84    /// Unknown response code.
85    Unknown(u8),
86}
87
88/// Describe a login success response. It provides the client with the
89/// address of the base app to connect, session key and an optional
90/// server message.
91#[derive(Debug, Clone)]
92pub struct LoginSuccess {
93    /// The socket address of the base app server to connect after successful
94    /// login.
95    pub addr: SocketAddrV4,
96    /// Session key, it's used to authenticate to the base app.
97    pub login_key: u32,
98    /// Server message for successful login.
99    pub server_message: String,
100}
101
102/// Describe an issued challenge as a response to a login request.
103#[derive(Debug, Clone)]
104pub enum LoginChallenge {
105    /// Cuckoo cycle challenge.
106    CuckooCycle {
107        prefix: String,
108        max_nonce: u64,
109    },
110}
111
112/// Describe a login error as a response to a login request.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114#[repr(u8)]
115pub enum LoginError {
116    MalformedRequest = 64,
117    BadProtocolVersion = 65,
118    // ChallengeIssued = 66, handled by a specific variant of LoginResponse.
119    InvalidUser = 67,
120    InvalidPassword = 68,
121    AlreadyLoggedIn = 69,
122    BadDigest = 70,
123    DatabaseGeneralFailure = 71,
124    DatabaseNotReady = 72,
125    IllegalCharacters = 73,
126    ServerNotReady = 74,
127    UpdaterNotReady = 75, // No longer used
128    NoBaseApp = 76,
129    BaseAppOverload = 77,
130    CellAppOverload = 78,
131    BaseAppTimeout = 79,
132    BaseAppManagerTimeout = 80,
133    DatabaseAppOverload = 81,
134    LoginNotAllowed = 82,
135    RateLimited = 83,
136    Banned = 84,
137    ChallengeError = 85,
138}
139
140
141/// Describe a generic challenge response of a given generic type.
142#[derive(Debug, Clone)]
143pub struct ChallengeResponse<T> {
144    /// Resolve duration of the challenge.
145    pub duration: Duration,
146    /// Inner data of the challenge response.
147    pub data: T,
148}
149
150/// Describe a challenge response for cuckoo cycle challenge type.
151#[derive(Debug, Clone)]
152pub struct CuckooCycleResponse {
153    pub key: String,
154    pub solution: Vec<u32>,
155}
156
157
158/// Describe the type of encryption to use for encoding/decoding
159/// a login request. This must be provided as configuration when
160/// writing or reading the element.
161#[derive(Debug)]
162pub enum LoginRequestEncryption {
163    /// Clear transmission between server and client.
164    Clear,
165    /// Encrypted encoding.
166    Client(Arc<RsaPublicKey>),
167    /// Encrypted decoding.
168    Server(Arc<RsaPrivateKey>),
169}
170
171impl Element for LoginRequest {
172
173    type Config = LoginRequestEncryption;
174
175    fn encode(&self, write: &mut impl Write, config: &Self::Config) -> io::Result<()> {
176        write.write_u32(self.protocol)?;
177        match config {
178            LoginRequestEncryption::Clear => {
179                write.write_u8(0)?;
180                encode_login_params(write, self)
181            }
182            LoginRequestEncryption::Client(key) => {
183                write.write_u8(1)?;
184                encode_login_params(RsaWriter::new(write, &key), self)
185            }
186            LoginRequestEncryption::Server(_) => panic!("cannot encode with server login codec"),
187        }
188    }
189
190    fn decode(read: &mut impl Read, _len: usize, config: &Self::Config) -> io::Result<Self> {
191        let protocol = read.read_u32()?;
192        if read.read_u8()? != 0 {
193            if let LoginRequestEncryption::Server(key) = config {
194                decode_login_params(RsaReader::new(read, &key), protocol)
195            } else {
196                Err(io::Error::new(io::ErrorKind::InvalidData, "cannot decode without server login codec"))
197            }
198        } else {
199            decode_login_params(read, protocol)
200        }
201    }
202
203}
204
205impl TopElement for LoginRequest {
206    const LEN: ElementLength = ElementLength::Variable16;
207}
208
209fn encode_login_params(mut write: impl Write, input: &LoginRequest) -> io::Result<()> {
210    write.write_u8(if input.digest.is_some() { 0x01 } else { 0x00 })?;
211    write.write_string_variable(&input.username)?;
212    write.write_string_variable(&input.password)?;
213    write.write_blob_variable(&input.blowfish_key)?;
214    write.write_string_variable(&input.context)?;
215    if let Some(digest) = input.digest {
216        write.write_all(&digest)?;
217    }
218    write.write_u32(input.nonce)
219}
220
221fn decode_login_params(mut input: impl Read, protocol: u32) -> io::Result<LoginRequest> {
222    let flags = input.read_u8()?;
223    Ok(LoginRequest {
224        protocol,
225        username: input.read_string_variable()?,
226        password: input.read_string_variable()?,
227        blowfish_key: input.read_blob_variable()?,
228        context: input.read_string_variable()?,
229        digest: if flags & 0x01 != 0 {
230            let mut digest = [0; 16];
231            input.read_exact(&mut digest)?;
232            Some(digest)
233        } else {
234            Option::None
235        },
236        nonce: input.read_u32()?
237    })
238}
239
240
241/// Describe if the login response has to be encrypted or not. This must be 
242/// provided as configuration when writing or reading the element.
243#[derive(Debug)]
244pub enum LoginResponseEncryption {
245    /// The login response is not encrypted. This should be selected if the
246    /// login request contains an empty blowfish key.
247    Clear,
248    /// The login response is encrypted with the given blowfish key.
249    /// This blowfish key should be created from the key provided by the client
250    /// in the login request.
251    /// 
252    /// *The blowfish key is only actually used when encoding or decoding a
253    /// login success, other statuses do not require the key.*
254    Encrypted(Arc<Blowfish>),
255}
256
257/// Text identifier of the cuckoo cycle challenge type.
258const CHALLENGE_CUCKOO_CYCLE: &'static str = "cuckoo_cycle";
259
260impl Element for LoginResponse {
261
262    type Config = LoginResponseEncryption;
263
264    fn encode(&self, write: &mut impl Write, config: &Self::Config) -> io::Result<()> {
265        
266        match self {
267            Self::Success(success) => {
268                
269                write.write_u8(1)?; // Logged-on
270                
271                if let LoginResponseEncryption::Encrypted(bf) = config {
272                    encode_login_success(BlowfishWriter::new(write, &bf), success)?;
273                } else {
274                    encode_login_success(write, success)?;
275                }
276
277            }
278            Self::Error(err, message) => {
279                write.write_u8(*err as _)?;
280                write.write_string_variable(&message)?;
281            }
282            Self::Challenge(challenge) => {
283
284                write.write_u8(66)?;
285                
286                match challenge {
287                    LoginChallenge::CuckooCycle { prefix, max_nonce } => {
288                        write.write_string_variable(CHALLENGE_CUCKOO_CYCLE)?;
289                        write.write_string_variable(&prefix)?;
290                        write.write_u64(*max_nonce)?;
291                    }
292                }
293                
294            }
295            Self::Unknown(code) => write.write_u8(*code)?
296        }
297
298        Ok(())
299
300    }
301
302    fn decode(read: &mut impl Read, _len: usize, config: &Self::Config) -> io::Result<Self> {
303        
304        let error = match read.read_u8()? {
305            1 => {
306                
307                let success = 
308                if let LoginResponseEncryption::Encrypted(bf) = config {
309                    decode_login_success(BlowfishReader::new(read, &bf))?
310                } else {
311                    decode_login_success(read)?
312                };
313
314                return Ok(LoginResponse::Success(success));
315
316            }
317            66 => {
318                
319                let challenge_name = read.read_string_variable()?;
320                let challenge = match &challenge_name[..] {
321                    CHALLENGE_CUCKOO_CYCLE => {
322                        let prefix = read.read_string_variable()?;
323                        let max_nonce = read.read_u64()?;
324                        LoginChallenge::CuckooCycle { prefix, max_nonce }
325                    }
326                    _ => return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid challenge name"))
327                };
328
329                return Ok(LoginResponse::Challenge(challenge));
330
331            }
332            64 => LoginError::MalformedRequest,
333            65 => LoginError::BadProtocolVersion,
334            67 => LoginError::InvalidUser,
335            68 => LoginError::InvalidPassword,
336            // TODO: Implement other variants
337            code => return Ok(LoginResponse::Unknown(code))
338        };
339
340        let message = match read.read_string_variable() {
341            Ok(msg) => msg,
342            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => String::new(),
343            Err(e) => return Err(e),
344        };
345
346        Ok(LoginResponse::Error(error, message))
347
348    }
349
350}
351
352/// Internal function for encoding login success. It is extracted here
353/// in order to be usable with optional encryption.
354fn encode_login_success<W: Write>(mut write: W, success: &LoginSuccess) -> io::Result<()> {
355    write.write_sock_addr_v4(success.addr)?;
356    write.write_u32(success.login_key)?;
357    if !success.server_message.is_empty() {
358        write.write_string_variable(&success.server_message)?;
359    }
360    Ok(())
361}
362
363/// Internal function for decoding login success. It is extracted here
364/// in order to be usable with optional encryption.
365fn decode_login_success<R: Read>(mut read: R) -> io::Result<LoginSuccess> {
366    Ok(LoginSuccess { 
367        addr: read.read_sock_addr_v4()?, 
368        login_key: read.read_u32()?, 
369        server_message: match read.read_string_variable() {
370            Ok(msg) => msg,
371            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => String::new(),
372            Err(e) => return Err(e),
373        },
374    })
375}
376
377
378impl<E: Element> Element for ChallengeResponse<E> {
379
380    type Config = E::Config;
381
382    fn encode(&self, write: &mut impl Write, config: &Self::Config) -> io::Result<()> {
383        write.write_f32(self.duration.as_secs_f32())?;
384        self.data.encode(write, config)
385    }
386
387    fn decode(read: &mut impl Read, len: usize, config: &Self::Config) -> io::Result<Self> {
388        Ok(ChallengeResponse { 
389            duration: Duration::from_secs_f32(read.read_f32()?), 
390            data: E::decode(read, len - 4, config)?
391        })
392    }
393
394}
395
396impl<E: Element> TopElement for ChallengeResponse<E> {
397    const LEN: ElementLength = ElementLength::Variable16;
398}
399
400impl SimpleElement for CuckooCycleResponse {
401
402    fn encode(&self, write: &mut impl Write) -> io::Result<()> {
403        write.write_string_variable(&self.key)?;
404        for &nonce in &self.solution {
405            write.write_u32(nonce)?;
406        }
407        Ok(())
408    }
409
410    fn decode(read: &mut impl Read, _len: usize) -> io::Result<Self> {
411
412        let key = read.read_string_variable()?;
413        let mut solution = Vec::with_capacity(42);
414
415        loop {
416            solution.push(match read.read_u32() {
417                Ok(n) => n,
418                Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => break,
419                Err(e) => return Err(e),
420            });
421        }
422        
423        Ok(CuckooCycleResponse { 
424            key, 
425            solution,
426        })
427
428    }
429
430}