From: Ian Jackson Date: Sat, 5 Dec 2020 14:54:12 +0000 (+0000) Subject: wip email templates etc. X-Git-Tag: otter-0.2.0~280 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=340e63bedabf58a8ecbfeee627b6f9867c460ce7;p=otter.git wip email templates etc. Signed-off-by: Ian Jackson --- diff --git a/Makefile b/Makefile index bc628b6f..5d842878 100644 --- a/Makefile +++ b/Makefile @@ -266,6 +266,7 @@ deploy: stamp/cargo.deploy-build bundled-sources assets libraries rsync -r --delete --exclude=\*~ library specs $(DEPLOY_BASE)/. rsync -r $(FILEASSETS) $(addprefix $(WASM_PACKED)/, $(WASM_ASSETS)) \ $(DEPLOY_BASE)/assets/ + rsync -r nwtemplates/*.tera $(DEPLOY_BASE)/nwtemplates/ ssh -o BatchMode=true $(DEPLOY_USER) $(DEPLOY_FINISH) git branch -f $(DEPLOYED_BRANCH) diff --git a/server.toml b/server.toml index 7d008402..1282d47d 100644 --- a/server.toml +++ b/server.toml @@ -13,6 +13,7 @@ public_url = "http://localhost:8000" save_dir = "/home/rustcargo/Rustup/Game/server" command_socket = "/home/rustcargo/Rustup/Game/server/command.socket" template_dir = "/home/ian/Rustup/Game/server/templates" +nwtemplate_dir = "/home/ian/Rustup/Game/server/nwtemplates" bundled_sources = "/home/rustcargo/Rustup/Game/server/target/bundled-sources" wasm_dir = "/home/rustcargo/Rustup/Game/server/target/packed-wasm" diff --git a/src/bin/otter.rs b/src/bin/otter.rs index 8cd15dd4..1f7d675a 100644 --- a/src/bin/otter.rs +++ b/src/bin/otter.rs @@ -309,6 +309,13 @@ fn main() { let mut access = ap.refer(&mut rma.access); + access.metavar("EMAIL-ADDRESS").add_option( + &["--email"], + MapStore(|addr| Ok(Some( + TokenByEmail { addr: addr.to_string() }.into() + ))), + "send token by email, to EMAIL-ADDRESS (RFC822 recipient field syntax)" + ); access.add_option(&["--url-on-stdout"], StoreConst(Some(UrlOnStdout.into())), "show game access url by printing to stdout"); diff --git a/src/config.rs b/src/config.rs index 854c4d6c..536abff6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -26,6 +26,7 @@ pub struct ServerConfigSpec { pub sse_wildcard_url: Option, pub rocket_workers: Option, pub template_dir: Option, + pub nwtemplate_dir: Option, pub wasm_dir: Option, pub log: Option, pub bundled_sources: Option, @@ -43,6 +44,7 @@ pub struct ServerConfig { pub sse_wildcard_url: Option<(String, String)>, pub rocket_workers: u16, pub template_dir: String, + pub nwtemplate_dir: String, pub wasm_dir: String, pub log: LogSpecification, pub bundled_sources: String, @@ -57,8 +59,8 @@ impl TryFrom for ServerConfig { let ServerConfigSpec { base_dir, save_dir, command_socket, debug, http_port, public_url, sse_wildcard_url, rocket_workers, - template_dir, wasm_dir, - log, bundled_sources, shapelibs, + template_dir, nwtemplate_dir, wasm_dir, + log, bundled_sources, shapelibs, sendmail, } = spec; let defpath = |specd: Option, leaf: &str| -> String { @@ -72,6 +74,7 @@ impl TryFrom for ServerConfig { let command_socket = defpath(command_socket, "var/command.socket"); let template_dir = defpath(template_dir, "assets" ); let wasm_dir = defpath(wasm_dir, "assets" ); + let nwtemplate_dir = defpath(nwtemplate_dir, "nwtemplates" ); let bundled_sources = defpath(bundled_sources, "bundled-sources" ); const DEFAULT_LIBRARY_GLOB : &str = "library/*.toml"; @@ -80,6 +83,10 @@ impl TryFrom for ServerConfig { vec![ shapelib::Config1::PathGlob(glob) ] }); + let sendmail = sendmail.unwrap_or_else( + || DEFAULT_SENDMAIL_PROGRAM.into() + ); + let public_url = public_url .trim_end_matches('/') .into(); @@ -116,8 +123,8 @@ impl TryFrom for ServerConfig { ServerConfig { save_dir, command_socket, debug, http_port, public_url, sse_wildcard_url, rocket_workers, - template_dir, wasm_dir, - log, bundled_sources, shapelibs, + template_dir, nwtemplate_dir, wasm_dir, + log, bundled_sources, shapelibs, sendmail, } } } diff --git a/src/global.rs b/src/global.rs index 6faca941..ac853ced 100644 --- a/src/global.rs +++ b/src/global.rs @@ -756,7 +756,7 @@ impl<'ig> InstanceGuard<'ig> { &config().public_url.trim_end_matches("/"), token.0); let info = AccessTokenInfo { url }; - let report = access.deliver(&self.c.g, &gpl, &ipl, info)?; + let report = access.deliver(accounts, &self.c.g, &gpl, &ipl, info)?; report } diff --git a/src/imports.rs b/src/imports.rs index 8206ee6a..d1b4b045 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -19,15 +19,16 @@ pub use std::hash::Hash; pub use std::io; pub use std::io::ErrorKind; pub use std::io::{BufReader, Read, BufRead, BufWriter, Write}; -pub use std::io::{SeekFrom}; +pub use std::io::{Seek, SeekFrom}; pub use std::iter::repeat_with; pub use std::iter; pub use std::marker::PhantomData; pub use std::mem; pub use std::num::{Wrapping, TryFromIntError}; pub use std::ops::{Deref, DerefMut}; -pub use std::os::unix::ffi::OsStrExt; pub use std::os::unix; +pub use std::os::unix::ffi::OsStrExt; +pub use std::os::unix::process::CommandExt; pub use std::path::PathBuf; pub use std::process::{exit, Command}; pub use std::str::FromStr; diff --git a/src/nwtemplates.rs b/src/nwtemplates.rs index 393d1480..117fd2c5 100644 --- a/src/nwtemplates.rs +++ b/src/nwtemplates.rs @@ -4,7 +4,8 @@ use crate::imports::*; -use parking_lot::{const_rwlock, RwLock, MappedRwLockReadGuard}; +use parking_lot::{const_rwlock, RwLock, RwLockReadGuard}; +use parking_lot::{MappedRwLockReadGuard}; static STATE : RwLock> = const_rwlock(None); @@ -14,18 +15,26 @@ struct State { #[throws(StartupError)] pub fn init() { - let guard = STATE.write(); + let mut guard = STATE.write(); assert!(guard.is_none()); - let glob = format!("{}/*.tera", config().nwtemplates); - *guard = State { - tera: tera::Tera::new(&glob)?, - }; + let config = config(); + let nwtemplate_dir = &config.nwtemplate_dir; + let glob = format!("{}/*.tera", nwtemplate_dir); + let tera = tera::Tera::new(&glob) + .map_err(|e| anyhow!("{}", e)) + .context("load tamplates") + .with_context(|| nwtemplate_dir.to_string())?; + + *guard = Some(State { + tera, + }) } #[throws(tera::Error)] -pub fn render(template_name: &str, data: &D) { - fn get_st() -> MappedRwLockReadGuard<'static, State> { - STATE.read().as_ref().unwrap() +pub fn render(template_name: &str, data: &D) -> String { + fn get_tera() -> MappedRwLockReadGuard<'static, tera::Tera> { + let g = STATE.read(); + RwLockReadGuard::map(g, |g| &g.as_ref().unwrap().tera) } - get_st().render(template_name, data) + get_tera().render(template_name, data)? } diff --git a/src/spec.rs b/src/spec.rs index 3afb86e4..35ed1068 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -331,6 +331,7 @@ pub mod implementation { fn check_spec_permission(&self, _: Option) { } fn deliver(&self, + ag: &AccountsGuard, g: &Instance, gpl: &GPlayerState, ipl: &IPlayerState, @@ -346,6 +347,7 @@ pub mod implementation { impl PlayerAccessSpec for PlayerAccessUnset { #[throws(TokenDeliveryError)] fn deliver(&self, + _ag: &AccountsGuard, _g: &Instance, _gpl: &GPlayerState, _ipl: &IPlayerState, @@ -368,7 +370,8 @@ pub mod implementation { } #[throws(TokenDeliveryError)] fn deliver(&self, - _g: &Instance, + _ag: &AccountsGuard, + _g: &Instance, _gpl: &GPlayerState, _ipl: &IPlayerState, _token: AccessTokenInfo) -> AccessTokenReport { @@ -380,6 +383,7 @@ pub mod implementation { impl PlayerAccessSpec for UrlOnStdout { #[throws(TDE)] fn deliver<'t>(&self, + _ag: &AccountsGuard, _g: &Instance, _gpl: &GPlayerState, _ipl: &IPlayerState, @@ -393,6 +397,7 @@ pub mod implementation { impl PlayerAccessSpec for TokenByEmail { #[throws(TDE)] fn deliver<'t>(&self, + ag: &AccountsGuard, g: &Instance, gpl: &GPlayerState, ipl: &IPlayerState, @@ -412,11 +417,13 @@ pub mod implementation { nick: &gpl.nick, }; - if self.addr.find(['\r','\n'] as &[char]).is_some() { + if self.addr.find((&['\r','\n']) as &[char]).is_some() { throw!(anyhow!("email address may not contain line endings")); } - let message = match &ipl.account { + let (account, _) = ag.lookup(ipl.acctid).context("find account")?; + let account = &account.account; + let message = match &account.scope { AS::Unix { user } => { #[derive(Debug,Serialize)] struct Data<'r> { @@ -430,7 +437,7 @@ pub mod implementation { }; nwtemplates::render("token-unix", &data) } - other => { + _ => { #[derive(Debug,Serialize)] struct Data<'r> { account: String, @@ -438,35 +445,40 @@ pub mod implementation { common: CommonData<'r>, }; let data = Data { - account: other.to_string(), + account: account.to_string(), common, }; nwtemplates::render("token-other", &data) } - }.context("render email template")?; + }.map_err(|e| anyhow!(e.to_string())) + .context("render email template")?; let messagefile = (||{ - let messagefile = tempfile::tempfile().context("tempfile")?; + let mut messagefile = tempfile::tempfile().context("tempfile")?; messagefile.write_all(message.as_bytes()).context("write")?; messagefile.flush().context("flush")?; messagefile.seek(SeekFrom::Start(0)).context("seek")?; - messagefile + Ok::<_,AE>(messagefile) })().context("write email to temporary file.")?; let sendmail = &config().sendmail; - let command = Command::new(sendmail) + let mut command = Command::new(sendmail); + command .args(&["-oee","-odb","-oi","-t","--"]) - .stdin(messagefile) - .pre_exec(|| unsafe { + .stdin(messagefile); + unsafe { + command.pre_exec(|| { // https://github.com/rust-lang/rust/issues/79731 let r = libc::dup2(2,1); - assert_eq!(r, 1); + if r == 0 { Ok(()) } + else { Err(io::Error::last_os_error()) } }); + } let st = command .status() - .with_context(!! format!("run sendmail ({})", sendmail))?; + .with_context(|| format!("run sendmail ({})", sendmail))?; if !st.success() { - throw!(format!("sendmail ({}) failed: {} ({})", sendmail, st, st)); + throw!(anyhow!("sendmail ({}) failed: {} ({})", sendmail, st, st)); } AccessTokenReport { lines: vec![