1 use std
::collections
::HashMap
;
4 use anyhow
::{bail, format_err, Error}
;
7 use proxmox
::sys
::error
::SysError
;
8 use proxmox
::tools
::fs
::{CreateOptions, file_read_string}
;
10 use crate::api2
::types
::{
11 PROXMOX_SAFE_ID_REGEX
,
16 use crate::tools
::ControlFlow
;
18 pub(crate) const ACME_DIR
: &str = configdir
!("/acme");
19 pub(crate) const ACME_ACCOUNT_DIR
: &str = configdir
!("/acme/accounts");
21 pub(crate) const ACME_DNS_SCHEMA_FN
: &str = "/usr/share/proxmox-acme/dns-challenge-schema.json";
25 // `const fn`ify this once it is supported in `proxmox`
26 fn root_only() -> CreateOptions
{
28 .owner(nix
::unistd
::ROOT
)
29 .group(nix
::unistd
::Gid
::from_raw(0))
30 .perm(nix
::sys
::stat
::Mode
::from_bits_truncate(0o700))
33 fn create_acme_subdir(dir
: &str) -> nix
::Result
<()> {
34 match proxmox
::tools
::fs
::create_dir(dir
, root_only()) {
36 Err(err
) if err
.already_exists() => Ok(()),
41 pub(crate) fn make_acme_dir() -> nix
::Result
<()> {
42 create_acme_subdir(ACME_DIR
)
45 pub(crate) fn make_acme_account_dir() -> nix
::Result
<()> {
47 create_acme_subdir(ACME_ACCOUNT_DIR
)
50 pub const KNOWN_ACME_DIRECTORIES
: &[KnownAcmeDirectory
] = &[
52 name
: "Let's Encrypt V2",
53 url
: "https://acme-v02.api.letsencrypt.org/directory",
56 name
: "Let's Encrypt V2 Staging",
57 url
: "https://acme-staging-v02.api.letsencrypt.org/directory",
61 pub const DEFAULT_ACME_DIRECTORY_ENTRY
: &KnownAcmeDirectory
= &KNOWN_ACME_DIRECTORIES
[0];
63 pub fn account_path(name
: &str) -> String
{
64 format
!("{}/{}", ACME_ACCOUNT_DIR
, name
)
68 pub fn foreach_acme_account
<F
>(mut func
: F
) -> Result
<(), Error
>
70 F
: FnMut(AcmeAccountName
) -> ControlFlow
<Result
<(), Error
>>,
72 match crate::tools
::fs
::scan_subdir(-1, ACME_ACCOUNT_DIR
, &PROXMOX_SAFE_ID_REGEX
) {
76 let file_name
= unsafe { file.file_name_utf8_unchecked() }
;
78 if file_name
.starts_with('_'
) {
82 let account_name
= match AcmeAccountName
::from_string(file_name
.to_owned()) {
83 Ok(account_name
) => account_name
,
87 if let ControlFlow
::Break(result
) = func(account_name
) {
93 Err(err
) if err
.not_found() => Ok(()),
94 Err(err
) => Err(err
.into()),
98 pub fn mark_account_deactivated(name
: &str) -> Result
<(), Error
> {
99 let from
= account_path(name
);
101 let to
= account_path(&format
!("_deactivated_{}_{}", name
, i
));
102 if !Path
::new(&to
).exists() {
103 return std
::fs
::rename(&from
, &to
).map_err(|err
| {
105 "failed to move account path {:?} to {:?} - {}",
114 "No free slot to rename deactivated account {:?}, please cleanup {:?}",
120 pub fn load_dns_challenge_schema() -> Result
<Vec
<AcmeChallengeSchema
>, Error
> {
121 let raw
= file_read_string(&ACME_DNS_SCHEMA_FN
)?
;
122 let schemas
: serde_json
::Map
<String
, Value
> = serde_json
::from_str(&raw
)?
;
126 .map(|(id
, schema
)| AcmeChallengeSchema
{
130 .and_then(Value
::as_str
)
134 schema
: schema
.to_owned(),
139 pub fn complete_acme_account(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
140 let mut out
= Vec
::new();
141 let _
= foreach_acme_account(|name
| {
142 out
.push(name
.into_string());
143 ControlFlow
::CONTINUE
148 pub fn complete_acme_plugin(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
149 match plugin
::config() {
150 Ok((config
, _digest
)) => config
152 .map(|(id
, (_type
, _cfg
))| id
.clone())
154 Err(_
) => Vec
::new(),
158 pub fn complete_acme_plugin_type(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
161 //"http".to_string(), // makes currently not realyl sense to create or the like