diff --git a/Cargo.toml b/Cargo.toml index d847ecf..ea8c380 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bitsrun" description = "A headless login and logout CLI app for 10.0.0.55 at BIT" -version = "0.2.1" +version = "0.3.0" edition = "2021" license = "MIT" homepage = "https://github.com/spencerwooo/bitsrun-rs" diff --git a/README.md b/README.md index 78414f7..2d0890c 100644 --- a/README.md +++ b/README.md @@ -78,17 +78,20 @@ Options: > [!TIP] > Use environment variable `NO_COLOR=true` to disable colored output. -## Credentials +## Config and credentials -To save your credentials, create config file `bit-user.json` under an available config path as: +To save your credentials and configurations, create config file `bit-user.json` under an available config path as: ```json { "username": "", - "password": "" + "password": "", + "dm": true } ``` +**`dm` is for specifying whether the current device is a dumb terminal, and requires logging out through the alternative endpoint. Set to `true` (no quotes!) if the device you are working with is a dumb terminal.** + Available config file paths can be listed with: ```console diff --git a/src/cli.rs b/src/cli.rs index 2cb67de..6f265e1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -48,9 +48,13 @@ pub struct ClientArgs { pub password: Option, /// Manually specify IP address (IPv4) - #[arg(short, long)] + #[arg(long)] pub ip: Option, + /// Use alternative `dm` logout endpoint for registered dumb terminals + #[arg(long)] + pub dm: bool, + /// Optionally provide path to the config file #[arg(short, long)] pub config: Option, diff --git a/src/client.rs b/src/client.rs index 7c44cd8..5e0684a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -186,6 +186,7 @@ pub struct SrunClient { // srun portal info pub ip: IpAddr, pub ac_id: String, + pub dm: bool, // whether the device is authenticated with its mac address pub login_state: SrunLoginState, } @@ -197,23 +198,28 @@ impl SrunClient { /// * `username` - The username of the SRUN account (student id) /// * `password` - The password of the SRUN account /// * `ip` - The IP address (`online_ip` from the login portal if not specified) + /// * `dm` - Whether the device is authenticated through the campus login portal with its mac + /// address (important for dumb terminals!!!) /// * `http_client` - The http client to be used (a new one will be created if not specified) pub async fn new( username: String, password: String, http_client: Option, ip: Option, + dm: Option, ) -> Result { let http_client = http_client.unwrap_or_default(); let ac_id = get_acid(&http_client).await?; let login_state = get_login_state(&http_client).await?; let ip = ip.unwrap_or(login_state.online_ip); + let dm = dm.unwrap_or(false); Ok(SrunClient { http_client, username, password, ip, ac_id, + dm, login_state, }) } @@ -333,14 +339,44 @@ impl SrunClient { } // perform logout action - let params = [ - ("callback", "jsonp"), - ("action", "logout"), - ("ip", &self.ip.to_string()), - ("ac_id", self.ac_id.as_str()), - ("username", logged_in_username.as_str()), + let url = { + // dumb terminals use a different endpoint (dm logout) + match self.dm { + true => format!("{}/cgi-bin/rad_user_dm", SRUN_PORTAL), + false => format!("{}/cgi-bin/srun_portal", SRUN_PORTAL), + } + }; + + let ip_str = self.ip.to_string(); + let mut params = vec![ + ("callback", String::from("jsonp")), + ("ip", self.ip.to_string()), + ("username", logged_in_username.clone()), ]; - let url = format!("{}/cgi-bin/srun_portal", SRUN_PORTAL); + + if self.dm { + use chrono::Utc; + let timestamp = Utc::now().timestamp().to_string(); + let unbind = String::from("1"); + + let sign = { + let mut hasher = Sha1::new(); + let sn = format!( + "{0}{1}{2}{3}{0}", + timestamp, logged_in_username, ip_str, unbind + ); + + hasher.update(sn); + format!("{:x}", hasher.finalize()) + }; + + params.push(("time", timestamp)); + params.push(("unbind", unbind)); + params.push(("sign", sign)); + } else { + params.push(("action", String::from("logout"))); + params.push(("ac_id", self.ac_id.clone())); + } let resp = self .http_client diff --git a/src/main.rs b/src/main.rs index aab92a6..dab5229 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,6 +82,7 @@ async fn cli() -> Result<()> { let bit_user = user::get_bit_user( &client_args.username, &client_args.password, + client_args.dm, &client_args.config, matches!(args.command, Some(Commands::Login(_))), ) @@ -92,6 +93,7 @@ async fn cli() -> Result<()> { bit_user.password, Some(http_client), client_args.ip, + Some(client_args.dm), ) .await?; @@ -126,7 +128,7 @@ async fn cli() -> Result<()> { } else if matches!(args.command, Some(Commands::Logout(_))) { let resp = srun_client.logout().await?; match resp.error.as_str() { - "ok" => println!( + "ok" | "logout_ok" => println!( "{} {} logged out", "bitsrun:".if_supports_color(Stdout, |t| t.green()), resp.online_ip diff --git a/src/user.rs b/src/user.rs index 82ed645..585c540 100644 --- a/src/user.rs +++ b/src/user.rs @@ -14,6 +14,7 @@ use serde::Serialize; pub struct BitUser { pub username: String, pub password: String, + pub dm: bool, } /// Partial campus network user credentials @@ -21,13 +22,15 @@ pub struct BitUser { pub struct BitUserPartial { pub username: Option, pub password: Option, + pub dm: Option, } impl BitUserPartial { - pub fn new(username: &Option, password: &Option) -> Self { + pub fn new(username: &Option, password: &Option, dm: Option) -> Self { Self { username: username.clone(), password: password.clone(), + dm, } } } @@ -132,7 +135,7 @@ fn parse_config_file(config_path: &Option) -> Result { #[cfg(windows)] #[allow(unused)] - fn check_permissions(_config: &String, _meta: &std::fs::Metadata) -> Result<(), anyhow::Error> { + fn check_permissions(_config: &str, _meta: &std::fs::Metadata) -> Result<(), anyhow::Error> { // Windows doesn't support Unix-style permissions, so we'll just return Ok here. Ok(()) } @@ -171,10 +174,11 @@ fn parse_config_file(config_path: &Option) -> Result { pub fn get_bit_user( username: &Option, password: &Option, + dm: bool, config_path: &Option, require_password: bool, ) -> Result { - let mut bit_user = BitUserPartial::new(username, password); + let mut bit_user = BitUserPartial::new(username, password, Some(dm)); // username and password priority: command line > config file > prompt if bit_user.username.is_none() | (require_password & bit_user.password.is_none()) { @@ -188,6 +192,20 @@ pub fn get_bit_user( ), } + if user_from_file.dm.is_none() & !dm { + println!( + "{} logout endpoint not specified in config file! \ + logging out may encounter unexpected results", + "warning:".if_supports_color(Stdout, |t| t.yellow()), + ); + println!( + "{} if this device is a '{}', explicity specify `{}` to use alternative logout endpoint", + "warning:".if_supports_color(Stdout, |t| t.yellow()), + "registered dumb terminal".if_supports_color(Stdout, |t| t.on_yellow()), + "--dm".if_supports_color(Stdout, |t| t.underline()) + ); + } + match user_from_file.username { Some(username) => bit_user.username.get_or_insert(username), None => bit_user.username.get_or_insert_with(|| { @@ -219,5 +237,6 @@ pub fn get_bit_user( Ok(BitUser { username: bit_user.username.unwrap_or_default(), password: bit_user.password.unwrap_or_default(), + dm: bit_user.dm.unwrap_or_default(), }) }