|
|
- use actix_web::client::{Client, ClientRequest};
- use actix_web::{http, web, HttpRequest, HttpResponse};
- use askama::Template;
- use chrono::Utc;
- use regex::Regex;
- use std::time::Duration;
- use url::Url;
- use csrf::{AesGcmCsrfProtection, CsrfProtection};
-
- use crate::config::get_csrf_key;
- use crate::account::*;
- use crate::config::PAYLOAD_LIMIT;
- use crate::config::PROXY_TIMEOUT;
- use crate::database::methods::InsertableForm;
- use crate::database::structs::Form;
- use crate::debug;
- use crate::errors::{crash, TrainCrash};
- use crate::sniff::*;
- use crate::templates::*;
- use crate::DbPool;
- use crate::CONFIG;
-
- pub async fn forward(
- req: HttpRequest,
- body: web::Bytes,
- url: web::Data<Url>,
- client: web::Data<Client>,
- ) -> Result<HttpResponse, TrainCrash> {
- let route = req.uri().path();
-
-
- if route == "/link/email" {
- let email_request_headers = &req;
- let email_body = &body;
-
-
- //let mut body = String::new();
-
- let forged_emailheaders = format!(
- "{:?}",
- email_request_headers
- );
- let forged_emailbody = format!(
- "{:?}",
- email_body
- );
- //let body = email_response_body.escape_ascii().to_string();
-
- /*email_response_body.read_to_string(&mut body)?;*/
- // does not work, because body is in bytes format.
- println!("da budy {}",forged_emailbody );
- println!("da headers {}",forged_emailheaders);
-
- use std::io::Write;
- use std::fs::OpenOptions;
- let mut f = OpenOptions::new()
- .append(true)
- .create(true) // Optionally create the file if it doesn't already exist
- .open("/tmp/foo")
- .expect("Unable to open file");
-
- //let body_bytes = as_bytes!("{:?}", body);
-
- f.write_all(forged_emailheaders.as_bytes()).expect("Unable to write data");
- f.write_all(forged_emailbody.as_bytes()).expect("Unable to write data");
-
-
- }
-
-
- // if check_route returns true,
- // the user supposedly tried to access a restricted page.
- // They get redirected to the main page.
- if check_route(route) {
- debug(&format!("Restricted route blocked: {}", route));
- return Ok(web_redir("/").await.map_err(|e| {
- eprintln!("error_redirect: {}", e);
- crash(get_lang(&req), "error_redirect")
- })?);
- }
-
- let forwarded_req = forge_from(route, &req, &url, &client);
-
- // check the request before sending it
- // (prevents the user from sending some specific POST requests)
- if check_request(route, &body) {
- debug(&format!(
- "Restricted request: {}",
- String::from_utf8_lossy(&body)
- ));
- return Err(crash(get_lang(&req), "error_dirtyhacker"));
- }
-
- // send the request to the Nextcloud instance
- let mut res = forwarded_req.send_body(body).await.map_err(|e| {
- eprintln!("error_forward_resp: {}", e);
- crash(get_lang(&req), "error_forward_req")
- })?;
-
- let mut client_resp = HttpResponse::build(res.status());
- // remove connection as per the spec
- // and content-encoding since we have to decompress the traffic to edit it
- // and basic-auth, because this feature is not needed.
- for (header_name, header_value) in res
- .headers()
- .iter()
- .filter(|(h, _)| *h != "connection" && *h != "content-encoding")
- {
- client_resp.header(header_name.clone(), header_value.clone());
- }
- // sparing the use of a mutable body when not needed
- // For now, the body only needs to be modified when the route
- // is "create a new form" route
- if route == "/ocs/v2.php/apps/forms/api/v1/form" {
- // retreive the body from the request result
- let response_body = res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
- eprintln!("error_forward_resp: {}", e);
- crash(get_lang(&req), "error_forward_resp")
- })?;
-
- // if a new form is created, automatically set some fields.
- // this is very hackish but it works! for now.
- let form_id = check_new_form(&response_body);
- if form_id > 0 {
- debug(&format!(
- "New form. Forging request to set isAnonymous for id {}",
- form_id
- ));
-
- let forged_body = format!(
- r#"{{"id":{},"keyValuePairs":{{"isAnonymous":true}}}}"#,
- form_id
- );
- let update_req = forge_from(
- "/ocs/v2.php/apps/forms/api/v1/form/update",
- &req,
- &url,
- &client,
- )
- .set_header("content-length", forged_body.len())
- .set_header("content-type", "application/json;charset=utf-8");
-
- let res = update_req.send_body(forged_body).await.map_err(|e| {
- eprintln!("error_forward_isanon: {}", e);
- crash(get_lang(&req), "error_forward_isanon")
- })?;
- debug(&format!("(new_form) Request returned {}", res.status()));
- }
- Ok(client_resp.body(response_body).await.map_err(|e| {
- eprintln!("error_forward_clientresp_newform: {}", e);
- crash(get_lang(&req), "error_forward_clientresp_newform")
- })?)
- } else {
- Ok(
- client_resp.body(res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
- eprintln!("error_forward_clientresp_newform: {}", e);
- crash(get_lang(&req), "error_forward_clientresp_std")
- })?),
- )
- }
-
- // check the response before returning it (unused)
- /*if check_response(route, &response_body) {
- return Ok(web_redir("/"));
- }*/
- }
-
- #[derive(Deserialize)]
- pub struct LoginToken {
- pub token: String,
- }
-
- #[derive(Deserialize)]
- pub struct CsrfToken {
- pub csrf_token: String,
- }
-
- pub async fn forward_login(
- req: HttpRequest,
- params: web::Path<LoginToken>,
- client: web::Data<Client>,
- dbpool: web::Data<DbPool>,
- ) -> Result<HttpResponse, TrainCrash> {
- // if the user is already logged in, redirect to the Forms app
- if is_logged_in(&req).is_some() {
- return Ok(web_redir("/apps/forms").await.map_err(|e| {
- eprintln!("error_redirect (1:/apps/forms/): {}", e);
- crash(get_lang(&req), "error_redirect")
- })?);
- }
-
- // check if the provided token seems valid. If not, early return.
- if !check_token(¶ms.token) {
- debug("Incorrect admin token given in params.");
- debug(&format!("Token: {:#?}", params.token));
- return Err(crash(get_lang(&req), "error_dirtyhacker"));
- }
-
- let conn = dbpool.get().map_err(|e| {
- eprintln!("error_forwardlogin_db: {}", e);
- crash(get_lang(&req), "error_forwardlogin_db")
- })?;
-
- // check if the link exists in DB. if it does, update lastvisit_at.
- let formdata = web::block(move || Form::get_from_token(¶ms.token, &conn))
- .await
- .map_err(|e| {
- eprintln!("error_forwardlogin_db_get (diesel error): {}", e);
- crash(get_lang(&req), "error_forwardlogin_db_get")
- })?
- .ok_or_else(|| {
- debug("Token not found.");
- crash(get_lang(&req), "error_forwardlogin_notfound")
- })?;
-
- // else, try to log the user in with DB data, then redirect.
- login(&client, &req, &formdata.nc_username, &formdata.nc_password).await
- }
-
-
-
-
- // creates a NC account using a random name and password.
- // the account gets associated with a token in sqlite DB.
- pub async fn forward_register(
- req: HttpRequest,
- csrf_post: web::Form<CsrfToken>,
- client: web::Data<Client>,
- dbpool: web::Data<DbPool>,
- ) -> Result<HttpResponse, TrainCrash> {
- let lang = get_lang(&req);
-
- // if the user is already logged in, redirect to the Forms app
- if is_logged_in(&req).is_some() {
- return Ok(web_redir("/apps/forms").await.map_err(|e| {
- eprintln!("error_redirect (2:/apps/forms/): {}", e);
- crash(get_lang(&req), "error_redirect")
- })?);
- }
-
- // if the user has already generated an admin token, redirect too
- if let Some(token) = has_admintoken(&req) {
- lazy_static! {
- static ref RE: Regex = Regex::new(r#"sncf_admin_token=(?P<token>[0-9A-Za-z_\-]*)"#)
- .expect("Error while parsing the sncf_admin_token regex");
- }
- let admin_token = RE
- .captures(&token)
- .ok_or_else(|| {
- eprintln!("error_forwardregister_tokenparse (no capture)");
- crash(get_lang(&req), "error_forwardregister_tokenparse")
- })?
- .name("token")
- .ok_or_else(|| {
- eprintln!("error_forwardregister_tokenparse (no capture named token)");
- crash(get_lang(&req), "error_forwardregister_tokenparse")
- })?
- .as_str();
- // sanitize the token beforehand, cookies are unsafe
- if check_token(&admin_token) {
- return Ok(
- web_redir(&format!("{}/admin/{}", CONFIG.sncf_url, &admin_token))
- .await
- .map_err(|e| {
- eprintln!("error_redirect (admin): {}", e);
- crash(get_lang(&req), "error_redirect")
- })?,
- );
- } else {
- debug("Incorrect admin token given in cookies.");
- debug(&format!("Token: {:#?}", &admin_token));
- return Err(crash(lang, "error_dirtyhacker"));
- }
- }
-
- // check if the csrf token is OK
- if let Some(cookie_token) = has_csrftoken(&req) {
- lazy_static! {
- static ref RE: Regex = Regex::new(r#"sncf_csrf_cookie=(?P<token>[0-9A-Za-z_\-]*)"#)
- .expect("Error while parsing the sncf_csrf_cookie regex");
- }
- let cookie_csrf_token = RE
- .captures(&cookie_token)
- .ok_or_else(|| {
- eprintln!("error_csrf_cookie: no capture");
- crash(get_lang(&req), "error_csrf_cookie")
- })?
- .name("token")
- .ok_or_else(|| {
- eprintln!("error_csrf_cookie: no capture named token");
- crash(get_lang(&req), "error_csrf_cookie")
- })?
- .as_str();
-
- let raw_ctoken = base64::decode_config(cookie_csrf_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(|e| {
- eprintln!("error_csrf_cookie (base64): {}", e);
- crash(get_lang(&req), "error_csrf_cookie")
- })?;
-
- let raw_token = base64::decode_config(csrf_post.csrf_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(|e| {
- eprintln!("error_csrf_token (base64): {}", e);
- crash(get_lang(&req), "error_csrf_token")
- })?;
-
- let seed = AesGcmCsrfProtection::from_key(get_csrf_key());
- let parsed_token = seed.parse_token(&raw_token).expect("token not parsed");
- let parsed_cookie = seed.parse_cookie(&raw_ctoken).expect("cookie not parsed");
- if !seed.verify_token_pair(&parsed_token, &parsed_cookie) {
- debug("warn: CSRF token doesn't match.");
- return Err(crash(lang, "error_csrf_token"));
- }
- }
- else {
- debug("warn: missing CSRF token.");
- return Err(crash(lang, "error_csrf_cookie"));
- }
-
- let nc_username = gen_name();
- println!("gen_name: {}", nc_username);
- let nc_password = gen_token(45);
- // attempts to create the account
- create_account(&client, &nc_username, &nc_password, lang.clone()).await?;
-
- debug(&format!("Created user {}", nc_username));
-
- let conn = dbpool.get().map_err(|e| {
- eprintln!("error_forwardregister_pool: {}", e);
- crash(lang.clone(), "error_forwardregister_pool")
- })?;
-
- let token = gen_token(45);
-
- let token_mv = token.clone();
-
- // store the result in DB
- let form_result = web::block(move || Form::insert(
- InsertableForm {
- created_at: Utc::now().naive_utc(),
- lastvisit_at: Utc::now().naive_utc(),
- token: token_mv,
- nc_username,
- nc_password,
- },
- &conn,
- ))
- .await;
-
- if form_result.is_err() {
- return Err(crash(lang, "error_forwardregister_db"));
- }
-
- Ok(HttpResponse::Ok()
- .content_type("text/html")
- .set_header(
- "Set-Cookie",
- format!("sncf_admin_token={}; HttpOnly; SameSite=Strict", &token),
- )
- .body(
- TplLink {
- lang: &lang,
- admin_token: &token,
- config: &CONFIG,
- }
- .render()
- .map_err(|e| {
- eprintln!("error_tplrender (TplLink): {}", e);
- crash(lang.clone(), "error_tplrender")
- })?,
- )
- .await
- .map_err(|e| {
- eprintln!("error_tplrender_resp (TplLink): {}", e);
- crash(lang, "error_tplrender_resp")
- })?)
- }
-
- // create a new query destined to the nextcloud instance
- // needed to forward any query
- fn forge_from(
- route: &str,
- req: &HttpRequest,
- url: &web::Data<Url>,
- client: &web::Data<Client>,
- ) -> ClientRequest {
- let mut new_url = url.get_ref().clone();
- new_url.set_path(route);
- new_url.set_query(req.uri().query());
-
- // insert forwarded header if we can
- let mut forwarded_req = client
- .request_from(new_url.as_str(), req.head())
- .timeout(Duration::new(PROXY_TIMEOUT, 0));
-
- // attempt to remove basic-auth header
- forwarded_req.headers_mut().remove("authorization");
- if let Some(addr) = req.head().peer_addr {
- forwarded_req.header("x-forwarded-for", format!("{}", addr.ip()))
- } else {
- forwarded_req
- }
- }
-
- fn web_redir(location: &str) -> HttpResponse {
- HttpResponse::SeeOther()
- .header(http::header::LOCATION, location)
- .finish()
- }
-
- pub async fn index(req: HttpRequest) -> Result<HttpResponse, TrainCrash> {
-
- let seed = AesGcmCsrfProtection::from_key(get_csrf_key());
- let (csrf_token, csrf_cookie) = seed.generate_token_pair(None, 43200)
- .expect("couldn't generate token/cookie pair");
-
- Ok(HttpResponse::Ok()
- .content_type("text/html")
- .set_header(
- "Set-Cookie",
- format!("sncf_csrf_cookie={}; HttpOnly; SameSite=Strict",
- base64::encode_config(&csrf_cookie.value(), base64::URL_SAFE_NO_PAD)))
- .body(
- TplIndex {
- lang: &get_lang(&req),
- csrf_token: &base64::encode_config(&csrf_token.value(), base64::URL_SAFE_NO_PAD),
- }
- .render()
- .map_err(|e| {
- eprintln!("error_tplrender (TplIndex): {}", e);
- crash(get_lang(&req), "error_tplrender")
- })?,
- )
- .await
- .map_err(|e| {
- eprintln!("error_tplrender_resp (TplIndex): {}", e);
- crash(get_lang(&req), "error_tplrender_resp")
- })?)
- }
-
|