209 lines
6 KiB
Rust
209 lines
6 KiB
Rust
// Copyright © 2023 Aravinth Manivannan <realravinth@batsense.net>
|
|
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
use std::time::Instant;
|
|
|
|
use clap::*;
|
|
use mcaptcha_pow_sha256::ConfigBuilder;
|
|
use mcaptcha_pow_sha256::PoW;
|
|
use reqwest::Client;
|
|
use scraper::{Html, Selector};
|
|
use serde::{Deserialize, Serialize};
|
|
use url::Url;
|
|
|
|
#[derive(Deserialize, Parser, Serialize, Clone, Debug)]
|
|
/// Compute PoW with offline parameters
|
|
struct Offline {
|
|
/// Salt with which PoW should be computed
|
|
#[arg(short, long)]
|
|
salt: String,
|
|
|
|
/// Phrase over which PoW should be computed
|
|
#[arg(short, long)]
|
|
phrase: String,
|
|
|
|
/// Difficulty Factor
|
|
#[arg(short, long)]
|
|
difficulty_factor: u32,
|
|
}
|
|
|
|
#[derive(Deserialize, Parser, Serialize, Clone, Debug)]
|
|
/// Compute PoW by fetching parameters from protected page URL
|
|
struct ProtectedPage {
|
|
/// URL of the protected page. Example: https://example.org/protected.html
|
|
url: Url,
|
|
}
|
|
|
|
impl ProtectedPage {
|
|
async fn extract_url(self) -> Option<WidgetUrl> {
|
|
let c = Client::default();
|
|
let text = c.get(self.url).send().await.unwrap().text().await.unwrap();
|
|
let fragement = Html::parse_fragment(&text);
|
|
let selector = Selector::parse("label#mcaptcha__token-label").unwrap();
|
|
if let Some(label) = fragement.select(&selector).next() {
|
|
if let Some(val) = label.value().attr("data-mcaptcha_url") {
|
|
if let Ok(url) = Url::parse(val) {
|
|
return Some(WidgetUrl { url });
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize, Parser, Serialize, Clone, Debug)]
|
|
/// Compute PoW by fetching parameters from CAPTCHA URL
|
|
struct WidgetUrl {
|
|
url: Url,
|
|
}
|
|
|
|
impl WidgetUrl {
|
|
async fn run(&self) {
|
|
let c = Client::default();
|
|
let sitekey = self.extract_sitekey();
|
|
|
|
if sitekey.is_none() {
|
|
println!("ERROR: Sitekey not found in URL. Please enter correct URL");
|
|
return;
|
|
}
|
|
let sitekey = sitekey.unwrap();
|
|
|
|
let url = self.get_config_url();
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
struct ConfigRequest {
|
|
key: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
struct ConfigResp {
|
|
string: String,
|
|
difficulty_factor: u32,
|
|
salt: String,
|
|
}
|
|
let req = ConfigRequest {
|
|
key: sitekey.to_string(),
|
|
};
|
|
|
|
let resp: ConfigResp = c
|
|
.post(url)
|
|
.json(&req)
|
|
.send()
|
|
.await
|
|
.unwrap()
|
|
.json()
|
|
.await
|
|
.unwrap();
|
|
let start = Instant::now();
|
|
let work = prove_work(resp.string.clone(), resp.salt, resp.difficulty_factor);
|
|
let finish = Instant::now();
|
|
let time_elapsed = finish.duration_since(start);
|
|
let time = time_elapsed.as_millis() as usize;
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
struct VerifyRequest {
|
|
key: String,
|
|
nonce: u64,
|
|
result: String,
|
|
string: String,
|
|
time: usize,
|
|
worker_type: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
struct VerifyResp {
|
|
token: String,
|
|
}
|
|
|
|
let req = VerifyRequest {
|
|
key: sitekey.clone(),
|
|
nonce: work.nonce,
|
|
result: work.result,
|
|
string: resp.string,
|
|
time,
|
|
worker_type: String::from("mCaptcha CLI"),
|
|
};
|
|
let url = self.get_verify_url();
|
|
let resp: VerifyResp = c
|
|
.post(url)
|
|
.json(&req)
|
|
.send()
|
|
.await
|
|
.unwrap()
|
|
.json()
|
|
.await
|
|
.unwrap();
|
|
println!("Authorization token: {}", resp.token);
|
|
}
|
|
fn extract_sitekey(&self) -> Option<String> {
|
|
let mut sitekey = None;
|
|
for (k, v) in self.url.query_pairs() {
|
|
if &k == "sitekey" {
|
|
let x: &str = &v;
|
|
sitekey = Some(x.to_string())
|
|
}
|
|
}
|
|
|
|
sitekey
|
|
}
|
|
|
|
fn get_config_url(&self) -> Url {
|
|
let mut url = self.url.clone();
|
|
url.set_path("/api/v1/pow/config");
|
|
url
|
|
}
|
|
|
|
fn get_verify_url(&self) -> Url {
|
|
let mut url = self.url.clone();
|
|
url.set_path("/api/v1/pow/verify");
|
|
url
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize, Parser, Serialize, Clone, Debug)]
|
|
#[command(author, version, about, long_about = None)]
|
|
enum Args {
|
|
Offline(Offline),
|
|
ProtectedPage(ProtectedPage),
|
|
WidgetUrl(WidgetUrl),
|
|
}
|
|
|
|
fn prove_work(phrase: String, salt: String, difficulty_factor: u32) -> PoW<String> {
|
|
let config = ConfigBuilder::default().salt(salt).build().unwrap();
|
|
config.prove_work(&phrase, difficulty_factor).unwrap()
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let args = Args::parse();
|
|
match args {
|
|
Args::Offline(matches) => {
|
|
let phrase = matches.phrase;
|
|
let salt = matches.salt;
|
|
let difficulty_factor = matches.difficulty_factor;
|
|
let work = prove_work(phrase.clone(), salt, difficulty_factor);
|
|
|
|
// let config = ConfigBuilder::default().salt(salt.into()).build().unwrap();
|
|
// let work = config.prove_work(&phrase, difficulty_factor).unwrap();
|
|
println!("difficulty: {}", &difficulty_factor);
|
|
println!("nonce: {}", &work.nonce);
|
|
println!("original phrase: {}", &phrase);
|
|
println!("result: {}", &work.result);
|
|
}
|
|
Args::WidgetUrl(matches) => {
|
|
matches.run().await;
|
|
}
|
|
|
|
Args::ProtectedPage(matches) => {
|
|
let widget = matches
|
|
.extract_url()
|
|
.await
|
|
.unwrap_or_else(|| panic!("No mCaptcha widget found in the web page"));
|
|
widget.run().await;
|
|
}
|
|
}
|
|
}
|