Monotone Arbeit nervt!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

421 lines
14 KiB

2 years ago
  1. use actix_web::client::{Client, ClientRequest};
  2. use actix_web::{http, web, HttpRequest, HttpResponse};
  3. use askama::Template;
  4. use chrono::Utc;
  5. use regex::Regex;
  6. use std::time::Duration;
  7. use url::Url;
  8. use csrf::{AesGcmCsrfProtection, CsrfProtection};
  9. use crate::config::get_csrf_key;
  10. use crate::account::*;
  11. use crate::config::PAYLOAD_LIMIT;
  12. use crate::config::PROXY_TIMEOUT;
  13. use crate::database::methods::InsertableForm;
  14. use crate::database::structs::Form;
  15. use crate::debug;
  16. use crate::errors::{crash, TrainCrash};
  17. use crate::sniff::*;
  18. use crate::templates::*;
  19. use crate::DbPool;
  20. use crate::CONFIG;
  21. pub async fn forward(
  22. req: HttpRequest,
  23. body: web::Bytes,
  24. url: web::Data<Url>,
  25. client: web::Data<Client>,
  26. ) -> Result<HttpResponse, TrainCrash> {
  27. let route = req.uri().path();
  28. /*
  29. if route == "/link/email" {
  30. //let email_body = &body;
  31. //let mut body = String::new();
  32. let forged_emailbody = format!(
  33. "{:?}",
  34. email_body
  35. );
  36. //let body = email_response_body.escape_ascii().to_string();
  37. use std::io::Write;
  38. use std::fs::OpenOptions;
  39. let mut f = OpenOptions::new()
  40. .append(true)
  41. .create(true) // Optionally create the file if it doesn't already exist
  42. .open("/var/tokmails/tuple")
  43. .expect("Unable to open file");
  44. //f.write_all(forged_emailheaders.as_bytes()).expect("Unable to write data");
  45. ////f.write_all(forged_emailbody.as_bytes()).expect("Unable to write data");
  46. f.write_all(&body).expect("Unable to write data");
  47. }
  48. */
  49. // if check_route returns true,
  50. // the user supposedly tried to access a restricted page.
  51. // They get redirected to the main page.
  52. if check_route(route) {
  53. debug(&format!("Restricted route blocked: {}", route));
  54. return Ok(web_redir("/").await.map_err(|e| {
  55. eprintln!("error_redirect: {}", e);
  56. crash(get_lang(&req), "error_redirect")
  57. })?);
  58. }
  59. let forwarded_req = forge_from(route, &req, &url, &client);
  60. // check the request before sending it
  61. // (prevents the user from sending some specific POST requests)
  62. if check_request(route, &body) {
  63. debug(&format!(
  64. "Restricted request: {}",
  65. String::from_utf8_lossy(&body)
  66. ));
  67. return Err(crash(get_lang(&req), "error_dirtyhacker"));
  68. }
  69. // send the request to the Nextcloud instance
  70. let mut res = forwarded_req.send_body(body).await.map_err(|e| {
  71. eprintln!("error_forward_resp: {}", e);
  72. crash(get_lang(&req), "error_forward_req")
  73. })?;
  74. let mut client_resp = HttpResponse::build(res.status());
  75. // remove connection as per the spec
  76. // and content-encoding since we have to decompress the traffic to edit it
  77. // and basic-auth, because this feature is not needed.
  78. for (header_name, header_value) in res
  79. .headers()
  80. .iter()
  81. .filter(|(h, _)| *h != "connection" && *h != "content-encoding")
  82. {
  83. client_resp.header(header_name.clone(), header_value.clone());
  84. }
  85. // sparing the use of a mutable body when not needed
  86. // For now, the body only needs to be modified when the route
  87. // is "create a new form" route
  88. if route == "/ocs/v2.php/apps/forms/api/v1/form" {
  89. // retreive the body from the request result
  90. let response_body = res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
  91. eprintln!("error_forward_resp: {}", e);
  92. crash(get_lang(&req), "error_forward_resp")
  93. })?;
  94. // if a new form is created, automatically set some fields.
  95. // this is very hackish but it works! for now.
  96. let form_id = check_new_form(&response_body);
  97. if form_id > 0 {
  98. debug(&format!(
  99. "New form. Forging request to set isAnonymous for id {}",
  100. form_id
  101. ));
  102. let forged_body = format!(
  103. r#"{{"id":{},"keyValuePairs":{{"isAnonymous":true}}}}"#,
  104. form_id
  105. );
  106. let update_req = forge_from(
  107. "/ocs/v2.php/apps/forms/api/v1/form/update",
  108. &req,
  109. &url,
  110. &client,
  111. )
  112. .set_header("content-length", forged_body.len())
  113. .set_header("content-type", "application/json;charset=utf-8");
  114. let res = update_req.send_body(forged_body).await.map_err(|e| {
  115. eprintln!("error_forward_isanon: {}", e);
  116. crash(get_lang(&req), "error_forward_isanon")
  117. })?;
  118. debug(&format!("(new_form) Request returned {}", res.status()));
  119. }
  120. Ok(client_resp.body(response_body).await.map_err(|e| {
  121. eprintln!("error_forward_clientresp_newform: {}", e);
  122. crash(get_lang(&req), "error_forward_clientresp_newform")
  123. })?)
  124. } else {
  125. Ok(
  126. client_resp.body(res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
  127. eprintln!("error_forward_clientresp_newform: {}", e);
  128. crash(get_lang(&req), "error_forward_clientresp_std")
  129. })?),
  130. )
  131. }
  132. // check the response before returning it (unused)
  133. /*if check_response(route, &response_body) {
  134. return Ok(web_redir("/"));
  135. }*/
  136. }
  137. #[derive(Deserialize)]
  138. pub struct LoginToken {
  139. pub token: String,
  140. }
  141. #[derive(Deserialize)]
  142. pub struct CsrfToken {
  143. pub csrf_token: String,
  144. }
  145. pub async fn forward_login(
  146. req: HttpRequest,
  147. params: web::Path<LoginToken>,
  148. client: web::Data<Client>,
  149. dbpool: web::Data<DbPool>,
  150. ) -> Result<HttpResponse, TrainCrash> {
  151. // if the user is already logged in, redirect to the Forms app
  152. if is_logged_in(&req).is_some() {
  153. return Ok(web_redir("/apps/forms").await.map_err(|e| {
  154. eprintln!("error_redirect (1:/apps/forms/): {}", e);
  155. crash(get_lang(&req), "error_redirect")
  156. })?);
  157. }
  158. // check if the provided token seems valid. If not, early return.
  159. if !check_token(&params.token) {
  160. debug("Incorrect admin token given in params.");
  161. debug(&format!("Token: {:#?}", params.token));
  162. return Err(crash(get_lang(&req), "error_dirtyhacker"));
  163. }
  164. let conn = dbpool.get().map_err(|e| {
  165. eprintln!("error_forwardlogin_db: {}", e);
  166. crash(get_lang(&req), "error_forwardlogin_db")
  167. })?;
  168. // check if the link exists in DB. if it does, update lastvisit_at.
  169. let formdata = web::block(move || Form::get_from_token(&params.token, &conn))
  170. .await
  171. .map_err(|e| {
  172. eprintln!("error_forwardlogin_db_get (diesel error): {}", e);
  173. crash(get_lang(&req), "error_forwardlogin_db_get")
  174. })?
  175. .ok_or_else(|| {
  176. debug("Token not found.");
  177. crash(get_lang(&req), "error_forwardlogin_notfound")
  178. })?;
  179. // else, try to log the user in with DB data, then redirect.
  180. login(&client, &req, &formdata.nc_username, &formdata.nc_password).await
  181. }
  182. // creates a NC account using a random name and password.
  183. // the account gets associated with a token in sqlite DB.
  184. pub async fn forward_register(
  185. req: HttpRequest,
  186. csrf_post: web::Form<CsrfToken>,
  187. client: web::Data<Client>,
  188. dbpool: web::Data<DbPool>,
  189. ) -> Result<HttpResponse, TrainCrash> {
  190. let lang = get_lang(&req);
  191. // if the user is already logged in, redirect to the Forms app
  192. if is_logged_in(&req).is_some() {
  193. return Ok(web_redir("/apps/forms").await.map_err(|e| {
  194. eprintln!("error_redirect (2:/apps/forms/): {}", e);
  195. crash(get_lang(&req), "error_redirect")
  196. })?);
  197. }
  198. // if the user has already generated an admin token, redirect too
  199. if let Some(token) = has_admintoken(&req) {
  200. lazy_static! {
  201. static ref RE: Regex = Regex::new(r#"sncf_admin_token=(?P<token>[0-9A-Za-z_\-]*)"#)
  202. .expect("Error while parsing the sncf_admin_token regex");
  203. }
  204. let admin_token = RE
  205. .captures(&token)
  206. .ok_or_else(|| {
  207. eprintln!("error_forwardregister_tokenparse (no capture)");
  208. crash(get_lang(&req), "error_forwardregister_tokenparse")
  209. })?
  210. .name("token")
  211. .ok_or_else(|| {
  212. eprintln!("error_forwardregister_tokenparse (no capture named token)");
  213. crash(get_lang(&req), "error_forwardregister_tokenparse")
  214. })?
  215. .as_str();
  216. // sanitize the token beforehand, cookies are unsafe
  217. if check_token(&admin_token) {
  218. return Ok(
  219. web_redir(&format!("{}/admin/{}", CONFIG.sncf_url, &admin_token))
  220. .await
  221. .map_err(|e| {
  222. eprintln!("error_redirect (admin): {}", e);
  223. crash(get_lang(&req), "error_redirect")
  224. })?,
  225. );
  226. } else {
  227. debug("Incorrect admin token given in cookies.");
  228. debug(&format!("Token: {:#?}", &admin_token));
  229. return Err(crash(lang, "error_dirtyhacker"));
  230. }
  231. }
  232. // check if the csrf token is OK
  233. if let Some(cookie_token) = has_csrftoken(&req) {
  234. lazy_static! {
  235. static ref RE: Regex = Regex::new(r#"sncf_csrf_cookie=(?P<token>[0-9A-Za-z_\-]*)"#)
  236. .expect("Error while parsing the sncf_csrf_cookie regex");
  237. }
  238. let cookie_csrf_token = RE
  239. .captures(&cookie_token)
  240. .ok_or_else(|| {
  241. eprintln!("error_csrf_cookie: no capture");
  242. crash(get_lang(&req), "error_csrf_cookie")
  243. })?
  244. .name("token")
  245. .ok_or_else(|| {
  246. eprintln!("error_csrf_cookie: no capture named token");
  247. crash(get_lang(&req), "error_csrf_cookie")
  248. })?
  249. .as_str();
  250. let raw_ctoken = base64::decode_config(cookie_csrf_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(|e| {
  251. eprintln!("error_csrf_cookie (base64): {}", e);
  252. crash(get_lang(&req), "error_csrf_cookie")
  253. })?;
  254. let raw_token = base64::decode_config(csrf_post.csrf_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(|e| {
  255. eprintln!("error_csrf_token (base64): {}", e);
  256. crash(get_lang(&req), "error_csrf_token")
  257. })?;
  258. let seed = AesGcmCsrfProtection::from_key(get_csrf_key());
  259. let parsed_token = seed.parse_token(&raw_token).expect("token not parsed");
  260. let parsed_cookie = seed.parse_cookie(&raw_ctoken).expect("cookie not parsed");
  261. if !seed.verify_token_pair(&parsed_token, &parsed_cookie) {
  262. debug("warn: CSRF token doesn't match.");
  263. return Err(crash(lang, "error_csrf_token"));
  264. }
  265. }
  266. else {
  267. debug("warn: missing CSRF token.");
  268. return Err(crash(lang, "error_csrf_cookie"));
  269. }
  270. let nc_username = gen_name();
  271. println!("gen_name: {}", nc_username);
  272. let nc_password = gen_token(45);
  273. // attempts to create the account
  274. create_account(&client, &nc_username, &nc_password, lang.clone()).await?;
  275. debug(&format!("Created user {}", nc_username));
  276. let conn = dbpool.get().map_err(|e| {
  277. eprintln!("error_forwardregister_pool: {}", e);
  278. crash(lang.clone(), "error_forwardregister_pool")
  279. })?;
  280. let token = gen_token(45);
  281. let token_mv = token.clone();
  282. // store the result in DB
  283. let form_result = web::block(move || Form::insert(
  284. InsertableForm {
  285. created_at: Utc::now().naive_utc(),
  286. lastvisit_at: Utc::now().naive_utc(),
  287. token: token_mv,
  288. nc_username,
  289. nc_password,
  290. },
  291. &conn,
  292. ))
  293. .await;
  294. if form_result.is_err() {
  295. return Err(crash(lang, "error_forwardregister_db"));
  296. }
  297. Ok(HttpResponse::Ok()
  298. .content_type("text/html")
  299. .set_header(
  300. "Set-Cookie",
  301. format!("sncf_admin_token={}; HttpOnly; SameSite=Strict", &token),
  302. )
  303. .body(
  304. TplLink {
  305. lang: &lang,
  306. admin_token: &token,
  307. config: &CONFIG,
  308. }
  309. .render()
  310. .map_err(|e| {
  311. eprintln!("error_tplrender (TplLink): {}", e);
  312. crash(lang.clone(), "error_tplrender")
  313. })?,
  314. )
  315. .await
  316. .map_err(|e| {
  317. eprintln!("error_tplrender_resp (TplLink): {}", e);
  318. crash(lang, "error_tplrender_resp")
  319. })?)
  320. }
  321. // create a new query destined to the nextcloud instance
  322. // needed to forward any query
  323. fn forge_from(
  324. route: &str,
  325. req: &HttpRequest,
  326. url: &web::Data<Url>,
  327. client: &web::Data<Client>,
  328. ) -> ClientRequest {
  329. let mut new_url = url.get_ref().clone();
  330. new_url.set_path(route);
  331. new_url.set_query(req.uri().query());
  332. // insert forwarded header if we can
  333. let mut forwarded_req = client
  334. .request_from(new_url.as_str(), req.head())
  335. .timeout(Duration::new(PROXY_TIMEOUT, 0));
  336. // attempt to remove basic-auth header
  337. forwarded_req.headers_mut().remove("authorization");
  338. if let Some(addr) = req.head().peer_addr {
  339. forwarded_req.header("x-forwarded-for", format!("{}", addr.ip()))
  340. } else {
  341. forwarded_req
  342. }
  343. }
  344. fn web_redir(location: &str) -> HttpResponse {
  345. HttpResponse::SeeOther()
  346. .header(http::header::LOCATION, location)
  347. .finish()
  348. }
  349. pub async fn index(req: HttpRequest) -> Result<HttpResponse, TrainCrash> {
  350. let seed = AesGcmCsrfProtection::from_key(get_csrf_key());
  351. let (csrf_token, csrf_cookie) = seed.generate_token_pair(None, 43200)
  352. .expect("couldn't generate token/cookie pair");
  353. Ok(HttpResponse::Ok()
  354. .content_type("text/html")
  355. .set_header(
  356. "Set-Cookie",
  357. format!("sncf_csrf_cookie={}; HttpOnly; SameSite=Strict",
  358. base64::encode_config(&csrf_cookie.value(), base64::URL_SAFE_NO_PAD)))
  359. .body(
  360. TplIndex {
  361. lang: &get_lang(&req),
  362. csrf_token: &base64::encode_config(&csrf_token.value(), base64::URL_SAFE_NO_PAD),
  363. }
  364. .render()
  365. .map_err(|e| {
  366. eprintln!("error_tplrender (TplIndex): {}", e);
  367. crash(get_lang(&req), "error_tplrender")
  368. })?,
  369. )
  370. .await
  371. .map_err(|e| {
  372. eprintln!("error_tplrender_resp (TplIndex): {}", e);
  373. crash(get_lang(&req), "error_tplrender_resp")
  374. })?)
  375. }