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.

224 lines
6.3 KiB

2 years ago
  1. #[cfg(feature = "crypto_openssl")]
  2. mod openssl_server;
  3. #[cfg(feature = "crypto_openssl")]
  4. use crate::openssl_server::start as server_start;
  5. use acme_common::crypto::{get_lib_name, get_lib_version, HashFunction, KeyType, X509Certificate};
  6. use acme_common::logs::{set_log_system, DEFAULT_LOG_LEVEL};
  7. use acme_common::{clean_pid_file, to_idna};
  8. use anyhow::{anyhow, Result};
  9. use clap::builder::PossibleValuesParser;
  10. use clap::{Arg, ArgAction, ArgMatches, Command};
  11. use log::{debug, error, info};
  12. use std::fs::File;
  13. use std::io::{self, Read};
  14. const APP_NAME: &str = env!("CARGO_PKG_NAME");
  15. const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
  16. const DEFAULT_PID_FILE: &str = env!("TACD_DEFAULT_PID_FILE");
  17. const DEFAULT_LISTEN_ADDR: &str = "127.0.0.1:5001";
  18. const DEFAULT_CRT_KEY_TYPE: KeyType = KeyType::EcdsaP256;
  19. const DEFAULT_CRT_DIGEST: HashFunction = HashFunction::Sha256;
  20. const ALPN_ACME_PROTO_NAME: &[u8] = b"\x0aacme-tls/1";
  21. fn read_line(path: Option<&String>) -> Result<String> {
  22. let mut input = String::new();
  23. match path {
  24. Some(p) => File::open(p)?.read_to_string(&mut input)?,
  25. None => io::stdin().read_line(&mut input)?,
  26. };
  27. let line = input.trim().to_string();
  28. Ok(line)
  29. }
  30. fn get_acme_value(cnf: &ArgMatches, opt: &str, opt_file: &str) -> Result<String> {
  31. match cnf.get_one::<String>(opt) {
  32. Some(v) => Ok(v.to_string()),
  33. None => {
  34. debug!(
  35. "reading {opt} from {}",
  36. cnf.get_one::<String>(opt_file)
  37. .map(|e| e.as_str())
  38. .unwrap_or("stdin")
  39. );
  40. read_line(cnf.get_one::<String>(opt_file))
  41. }
  42. }
  43. }
  44. fn init(cnf: &ArgMatches) -> Result<()> {
  45. acme_common::init_server(
  46. cnf.get_flag("foreground"),
  47. cnf.get_one::<String>("pid-file").map(|e| e.as_str()),
  48. );
  49. let domain = get_acme_value(cnf, "domain", "domain-file")?;
  50. let domain = to_idna(&domain).map_err(|e| anyhow!(e))?;
  51. let ext = get_acme_value(cnf, "acme-ext", "acme-ext-file")?;
  52. let listen_addr = cnf
  53. .get_one::<String>("listen")
  54. .map(|e| e.as_str())
  55. .unwrap_or(DEFAULT_LISTEN_ADDR);
  56. let crt_signature_alg = match cnf.get_one::<&str>("crt-signature-alg") {
  57. Some(alg) => alg
  58. .parse()
  59. .map_err(|e: acme_common::error::Error| anyhow!(e))?,
  60. None => DEFAULT_CRT_KEY_TYPE,
  61. };
  62. let crt_digest = match cnf.get_one::<&str>("crt-digest") {
  63. Some(alg) => alg
  64. .parse()
  65. .map_err(|e: acme_common::error::Error| anyhow!(e))?,
  66. None => DEFAULT_CRT_DIGEST,
  67. };
  68. let (pk, cert) = X509Certificate::from_acme_ext(&domain, &ext, crt_signature_alg, crt_digest)
  69. .map_err(|e| anyhow!(e))?;
  70. info!("starting {APP_NAME} on {listen_addr} for {domain}");
  71. server_start(listen_addr, &cert, &pk)?;
  72. Ok(())
  73. }
  74. fn main() {
  75. let full_version = format!(
  76. "{APP_VERSION} built for {}\n\nCryptographic library:\n - {} {}",
  77. env!("TACD_TARGET"),
  78. get_lib_name(),
  79. get_lib_version(),
  80. );
  81. let default_crt_key_type = DEFAULT_CRT_KEY_TYPE.to_string();
  82. let default_crt_digest = DEFAULT_CRT_DIGEST.to_string();
  83. let default_log_level = DEFAULT_LOG_LEVEL.to_string().to_lowercase();
  84. let matches = Command::new(APP_NAME)
  85. .version(APP_VERSION)
  86. .long_version(full_version)
  87. .arg(
  88. Arg::new("listen")
  89. .long("listen")
  90. .short('l')
  91. .help("Host and port to listen on")
  92. .num_args(1)
  93. .value_name("host:port|unix:path")
  94. .default_value(DEFAULT_LISTEN_ADDR),
  95. )
  96. .arg(
  97. Arg::new("domain")
  98. .long("domain")
  99. .short('d')
  100. .help("The domain that is being validated")
  101. .num_args(1)
  102. .value_name("STRING")
  103. .conflicts_with("domain-file"),
  104. )
  105. .arg(
  106. Arg::new("domain-file")
  107. .long("domain-file")
  108. .help("File from which is read the domain that is being validated")
  109. .num_args(1)
  110. .value_name("FILE")
  111. .conflicts_with("domain"),
  112. )
  113. .arg(
  114. Arg::new("acme-ext")
  115. .long("acme-ext")
  116. .short('e')
  117. .help("The acmeIdentifier extension to set in the self-signed certificate")
  118. .num_args(1)
  119. .value_name("STRING")
  120. .conflicts_with("acme-ext-file"),
  121. )
  122. .arg(
  123. Arg::new("acme-ext-file")
  124. .long("acme-ext-file")
  125. .help("File from which is read the acmeIdentifier extension to set in the self-signed certificate")
  126. .num_args(1)
  127. .value_name("FILE")
  128. .conflicts_with("acme-ext"),
  129. )
  130. .arg(
  131. Arg::new("crt-signature-alg")
  132. .long("crt-signature-alg")
  133. .help("The certificate's signature algorithm")
  134. .num_args(1)
  135. .value_name("STRING")
  136. .value_parser(PossibleValuesParser::new(KeyType::list_possible_values()))
  137. .default_value(default_crt_key_type),
  138. )
  139. .arg(
  140. Arg::new("crt-digest")
  141. .long("crt-digest")
  142. .help("The certificate's digest algorithm")
  143. .num_args(1)
  144. .value_name("STRING")
  145. .value_parser(PossibleValuesParser::new(HashFunction::list_possible_values()))
  146. .default_value(default_crt_digest),
  147. )
  148. .arg(
  149. Arg::new("log-level")
  150. .long("log-level")
  151. .help("Specify the log level")
  152. .num_args(1)
  153. .value_name("LEVEL")
  154. .value_parser(["error", "warn", "info", "debug", "trace"])
  155. .default_value(default_log_level),
  156. )
  157. .arg(
  158. Arg::new("to-syslog")
  159. .long("log-syslog")
  160. .help("Sends log messages via syslog")
  161. .conflicts_with("to-stderr")
  162. .action(ArgAction::SetTrue),
  163. )
  164. .arg(
  165. Arg::new("to-stderr")
  166. .long("log-stderr")
  167. .help("Prints log messages to the standard error output")
  168. .conflicts_with("to-syslog")
  169. .action(ArgAction::SetTrue),
  170. )
  171. .arg(
  172. Arg::new("foreground")
  173. .long("foreground")
  174. .short('f')
  175. .help("Runs in the foreground")
  176. .action(ArgAction::SetTrue),
  177. )
  178. .arg(
  179. Arg::new("pid-file")
  180. .long("pid-file")
  181. .help("Path to the PID file")
  182. .num_args(1)
  183. .value_name("FILE")
  184. .default_value(DEFAULT_PID_FILE)
  185. .default_value_if("no-pid-file", clap::builder::ArgPredicate::IsPresent, None)
  186. .conflicts_with("no-pid-file"),
  187. )
  188. .arg(
  189. Arg::new("no-pid-file")
  190. .long("no-pid-file")
  191. .help("Do not create any PID file")
  192. .conflicts_with("pid-file")
  193. .action(ArgAction::SetTrue),
  194. )
  195. .get_matches();
  196. match set_log_system(
  197. matches.get_one::<String>("log-level").map(|e| e.as_str()),
  198. matches.get_flag("to-syslog"),
  199. matches.get_flag("to-stderr"),
  200. ) {
  201. Ok(_) => {}
  202. Err(e) => {
  203. eprintln!("Error: {e}");
  204. std::process::exit(2);
  205. }
  206. };
  207. match init(&matches) {
  208. Ok(_) => {}
  209. Err(e) => {
  210. error!("{e}");
  211. let pid_file = matches.get_one::<String>("pid-file").map(|e| e.as_str());
  212. let _ = clean_pid_file(pid_file);
  213. std::process::exit(1);
  214. }
  215. };
  216. }