I'm batman

This commit has no parents
This commit is contained in:
Valentin Brandl 2018-11-07 20:28:46 +01:00
commit 7cbd19f49a
No known key found for this signature in database
GPG Key ID: 30D341DD34118D7D
6 changed files with 1702 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

1490
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "rub-login"
version = "0.1.0"
authors = ["Valentin Brandl <vbrandl@riseup.net>"]
[dependencies]
html5ever = "0.22.3"
reqwest = "0.9.2"
serde = "1.0.79"
serde_derive = "1.0.79"
structopt = "0.2.11"
failure = "0.1.2"

14
LICENSE Normal file
View File

@ -0,0 +1,14 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2018 Valentin Brandl <spam@vbrandl.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# RUB Login
Login helper for the form at https://login.rz.rub.de/
Since the internet in my university and dormitory requires a login via the form on https://login.rz.rub.de/, I tried to
automate that task. The tool parses the login mask and sends a login request using the supplied username and password.
The tool also supports logging out but I have never needed that feature.
The parsing is done using [html5ever](https://github.com/servo/html5ever). While it should also be possible using simple
RegExes or even string operations, I thought it would be fun to properly parse the website.
Nothing more to say.
## License
This project is licensed under the [WTFPL](./LICENSE).

168
src/main.rs Normal file
View File

@ -0,0 +1,168 @@
#[macro_use]
extern crate failure;
extern crate html5ever;
extern crate reqwest;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate structopt;
use failure::Error;
use html5ever::{
driver::ParseOpts,
parse_document,
rcdom::{Node, NodeData, RcDom},
tendril::TendrilSink,
tree_builder::TreeBuilderOpts,
};
use std::{net::Ipv4Addr, str::FromStr};
use structopt::StructOpt;
const MASK_URL: &str = "https://login.rz.ruhr-uni-bochum.de/cgi-bin/start";
const API_URL: &str = "https://login.rz.ruhr-uni-bochum.de/cgi-bin/laklogin";
const LOGIN_OK_MARKER: &str = "Verbindung wurde hergestellt";
const LOGOUT_OK_MARKER: &str = "Logout erfolgreich";
#[derive(Debug, Fail)]
enum LocalError {
#[fail(display = "Cannot parse value from HTML")]
ParseError,
#[fail(display = "Operation {} failed", _0)]
OperationFailed(String),
}
#[derive(Debug, StructOpt)]
enum Opt {
/// Perform a login operation
#[structopt(name = "login")]
Login { loginid: String, password: String },
/// Perform a logout operation
#[structopt(name = "logout")]
Logout,
}
#[derive(Serialize)]
#[serde(tag = "action")]
enum Request {
Login {
loginid: String,
password: String,
ipaddr: Ipv4Addr,
},
Logout,
}
fn find_text(node: &Node, text: &str) -> bool {
if let NodeData::Text { ref contents } = node.data {
if contents.borrow().to_string() == text {
return true;
}
}
for chld in node.children.borrow().iter() {
if find_text(chld, text) {
return true;
}
}
false
}
fn find_value<T>(node: &Node, tag: &str, attr_name: &str) -> Option<T>
where
T: FromStr,
{
if let NodeData::Element {
ref name,
ref attrs,
..
} = node.data
{
if name.local == *tag && attrs
.borrow()
.iter()
.any(|attr| attr.name.local == *"name" && attr.value.to_string() == attr_name)
{
let t = attrs
.borrow()
.iter()
.find(|attr| attr.name.local == *"value")
.and_then(|attr| attr.value.parse().ok());
if t.is_some() {
return t;
}
}
}
for chld in node.children.borrow().iter() {
let res = find_value(chld, tag, attr_name);
if res.is_some() {
return res;
}
}
None
}
fn login(loginid: String, password: String) -> Result<bool, Error> {
let mut res = reqwest::get(MASK_URL)?;
let opts = ParseOpts {
tree_builder: TreeBuilderOpts {
drop_doctype: true,
..Default::default()
},
..Default::default()
};
let dom = parse_document(RcDom::default(), opts.clone())
.from_utf8()
.read_from(&mut res)?;
let ipaddr: Ipv4Addr =
find_value(&dom.document, "input", "ipaddr").ok_or(LocalError::ParseError)?;
let data = Request::Login {
loginid,
password,
ipaddr,
};
let client = reqwest::Client::new();
let mut res = client.post(API_URL).form(&data).send()?;
let dom = parse_document(RcDom::default(), opts)
.from_utf8()
.read_from(&mut res)?;
Ok(find_text(&dom.document, LOGIN_OK_MARKER))
}
fn logout() -> Result<bool, Error> {
let data = Request::Logout;
let client = reqwest::Client::new();
let mut res = client.post(API_URL).form(&data).send()?;
let opts = ParseOpts {
tree_builder: TreeBuilderOpts {
drop_doctype: true,
..Default::default()
},
..Default::default()
};
let dom = parse_document(RcDom::default(), opts.clone())
.from_utf8()
.read_from(&mut res)?;
Ok(find_text(&dom.document, LOGOUT_OK_MARKER))
}
fn convert_operation_result(res: bool, op_name: String) -> Result<(), Error> {
if !res {
Err(LocalError::OperationFailed(op_name).into())
} else {
Ok(())
}
}
fn main() -> Result<(), Error> {
let opt = Opt::from_args();
match opt {
Opt::Login { loginid, password } => login(loginid, password)
.and_then(|res| convert_operation_result(res, "Login".to_owned())),
Opt::Logout => logout().and_then(|res| convert_operation_result(res, "Logout".to_owned())),
}
}