diff --git a/CHANGELOG.md b/CHANGELOG.md index c8eeb16..1d7e494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,9 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Hooks now have the optional `allow_failure` field. +- In hooks, the `stdin_str` has been added in replacement of the previous `stdin` behavior. ### Changed - Hooks are now cleaned right after the current challenge has been validated instead of after the certificate's retrieval. +- In hooks, the `stdin` field now refers to the path of the file that should be written into the hook's standard input. ### Fixed - The http-01-echo hook now correctly sets the file's access rights diff --git a/acmed/src/config.rs b/acmed/src/config.rs index 8e88c42..f41de6f 100644 --- a/acmed/src/config.rs +++ b/acmed/src/config.rs @@ -17,6 +17,25 @@ macro_rules! set_cfg_attr { }; } +fn get_stdin(hook: &Hook) -> Result { + match &hook.stdin { + Some(file) => match &hook.stdin_str { + Some(_) => { + let msg = format!( + "{}: A hook cannot have both stdin and stdin_str", + &hook.name + ); + Err(msg.into()) + } + None => Ok(hooks::HookStdin::File(file.to_string())), + }, + None => match &hook.stdin_str { + Some(s) => Ok(hooks::HookStdin::Str(s.to_string())), + None => Ok(hooks::HookStdin::None), + }, + } +} + #[derive(Deserialize)] #[serde(deny_unknown_fields)] pub struct Config { @@ -55,7 +74,7 @@ impl Config { hook_type: hook.hook_type.to_owned(), cmd: hook.cmd.to_owned(), args: hook.args.to_owned(), - stdin: hook.stdin.to_owned(), + stdin: get_stdin(&hook)?, stdout: hook.stdout.to_owned(), stderr: hook.stderr.to_owned(), allow_failure: hook @@ -159,6 +178,7 @@ pub struct Hook { pub cmd: String, pub args: Option>, pub stdin: Option, + pub stdin_str: Option, pub stdout: Option, pub stderr: Option, pub allow_failure: Option, diff --git a/acmed/src/hooks.rs b/acmed/src/hooks.rs index 0d860c6..4b260cf 100644 --- a/acmed/src/hooks.rs +++ b/acmed/src/hooks.rs @@ -8,6 +8,7 @@ use std::collections::hash_map::Iter; use std::collections::HashMap; use std::fs::File; use std::io::prelude::*; +use std::io::BufReader; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::{env, fmt}; @@ -75,13 +76,20 @@ pub struct FileStorageHookData { imple_hook_data_env!(FileStorageHookData); +#[derive(Clone, Debug)] +pub enum HookStdin { + File(String), + Str(String), + None, +} + #[derive(Clone, Debug)] pub struct Hook { pub name: String, pub hook_type: Vec, pub cmd: String, pub args: Option>, - pub stdin: Option, + pub stdin: HookStdin, pub stdout: Option, pub stderr: Option, pub allow_failure: bool, @@ -131,15 +139,29 @@ where .stdout(get_hook_output!(&hook.stdout, reg, &data)) .stderr(get_hook_output!(&hook.stderr, reg, &data)) .stdin(match &hook.stdin { - Some(_) => Stdio::piped(), - None => Stdio::null(), + HookStdin::Str(_) | HookStdin::File(_) => Stdio::piped(), + HookStdin::None => Stdio::null(), }) .spawn()?; - if hook.stdin.is_some() { - let data_in = reg.render_template(&hook.stdin.to_owned().unwrap(), &data)?; - debug!("Hook {}: stdin: {}", hook.name, data_in); - let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?; - stdin.write_all(data_in.as_bytes())?; + match &hook.stdin { + HookStdin::Str(s) => { + let data_in = reg.render_template(&s, &data)?; + debug!("Hook {}: string stdin: {}", hook.name, &data_in); + let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?; + stdin.write_all(data_in.as_bytes())?; + } + HookStdin::File(f) => { + let file_name = reg.render_template(&f, &data)?; + debug!("Hook {}: file stdin: {}", hook.name, &file_name); + let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?; + let file = File::open(&file_name)?; + let buf_reader = BufReader::new(file); + for line in buf_reader.lines() { + let line = format!("{}\n", line?); + stdin.write_all(line.as_bytes())?; + } + } + HookStdin::None => {} } // TODO: add a timeout let status = cmd.wait()?; diff --git a/man/en/acmed.toml.5 b/man/en/acmed.toml.5 index 1503809..b566746 100644 --- a/man/en/acmed.toml.5 +++ b/man/en/acmed.toml.5 @@ -116,7 +116,11 @@ The name of the command that will be launched. .It Ic args Ar array Array of strings representing the command's arguments. .It Ic stdin Ar string -String that will be written into the command's standard input. +Path to the file that will be written into the command's standard intput. Mutually exclusive with +.Em stdin_str . +.It Ic stdin_str Ar string +String that will be written into the command's standard input. Mutually exclusive with +.Em stdin . .It Ic stdout Ar string Path to the file where the command's standard output if written. .It Ic stderr Ar string @@ -199,6 +203,7 @@ A hook have a type that will influence both the moment it is called and the avai When writing a hook, the values of .Em args , .Em stdin , +.Em stdin_str , .Em stdout and .Em stderr @@ -549,7 +554,7 @@ args = [ "-f", "noreply.certs@example.net", "contact@example.net" ] -stdin = """Subject: Certificate renewal {{#if is_success}}succeeded{{else}}failed{{/if}} for {{domains.[0]}} +stdin_str = """Subject: Certificate renewal {{#if is_success}}succeeded{{else}}failed{{/if}} for {{domains.[0]}} The following certificate has {{#unless is_success}}*not* {{/unless}}been renewed. domains: {{#each domains}}{{#if @index}}, {{/if}}{{this}}{{/each}}