@@ -14,6 +14,15 @@ use super::{
1414
1515const ACME_ACCOUNT_DIR : & str = "accounts" ;
1616const ACME_ACCOUNT_CREDENTIALS_FILE : & str = "credentials.json" ;
17+ const ACME_ACCOUNT_DEACTIVATION_FILE : & str = ".credentials.deactivation.pending" ;
18+
19+ #[ cfg( feature = "acme-client" ) ]
20+ pub ( crate ) struct AccountDeactivationTransaction {
21+ directory : PathBuf ,
22+ active : PathBuf ,
23+ pending : PathBuf ,
24+ _lock : AcmeMutationLock ,
25+ }
1726
1827pub fn account_credentials_path ( storage : & Path , issuer_name : & str ) -> AcmeAccountCredentialsPath {
1928 AcmeAccountCredentialsPath {
@@ -29,6 +38,36 @@ pub fn load_account_credentials(
2938 issuer_name : & str ,
3039) -> Result < Option < instant_acme:: AccountCredentials > , AcmeAccountStoreError > {
3140 let path = account_credentials_path ( storage, issuer_name) . path ;
41+ let directory = path
42+ . parent ( )
43+ . ok_or_else ( || AcmeAccountStoreError :: UnsafePath {
44+ path : path. clone ( ) ,
45+ message : "credentials path has no parent directory" . to_owned ( ) ,
46+ } ) ?;
47+ match fs:: symlink_metadata ( directory) {
48+ Ok ( metadata) if metadata. file_type ( ) . is_symlink ( ) || !metadata. is_dir ( ) => {
49+ return Err ( AcmeAccountStoreError :: UnsafePath {
50+ path : directory. to_path_buf ( ) ,
51+ message : "account directory is not a real directory" . to_owned ( ) ,
52+ } ) ;
53+ }
54+ Ok ( _) => { }
55+ Err ( error) if error. kind ( ) == io:: ErrorKind :: NotFound => return Ok ( None ) ,
56+ Err ( error) => return Err ( account_store_io_error ( directory, error) ) ,
57+ }
58+ let _lock = AcmeMutationLock :: acquire ( directory)
59+ . map_err ( |error| account_store_io_error ( & directory. join ( ".fluxheim-acme.lock" ) , error) ) ?;
60+ let pending = directory. join ( ACME_ACCOUNT_DEACTIVATION_FILE ) ;
61+ if pending
62+ . try_exists ( )
63+ . map_err ( |error| account_store_io_error ( & pending, error) ) ?
64+ {
65+ return Err ( AcmeAccountStoreError :: UnsafePath {
66+ path : pending,
67+ message : "account deactivation is in an ambiguous pending state; operator recovery is required"
68+ . to_owned ( ) ,
69+ } ) ;
70+ }
3271 let metadata = match fs:: symlink_metadata ( & path) {
3372 Ok ( metadata) => metadata,
3473 Err ( error) if error. kind ( ) == io:: ErrorKind :: NotFound => return Ok ( None ) ,
@@ -80,6 +119,61 @@ pub fn load_account_credentials(
80119 } )
81120}
82121
122+ #[ cfg( feature = "acme-client" ) ]
123+ pub ( crate ) fn begin_account_deactivation (
124+ storage : & Path ,
125+ issuer_name : & str ,
126+ ) -> Result < AccountDeactivationTransaction , AcmeAccountStoreError > {
127+ let active = account_credentials_path ( storage, issuer_name) . path ;
128+ let directory = active
129+ . parent ( )
130+ . ok_or_else ( || AcmeAccountStoreError :: UnsafePath {
131+ path : active. clone ( ) ,
132+ message : "credentials path has no parent directory" . to_owned ( ) ,
133+ } ) ?;
134+ ensure_safe_account_directory ( directory) ?;
135+ let lock = AcmeMutationLock :: acquire ( directory)
136+ . map_err ( |error| account_store_io_error ( & directory. join ( ".fluxheim-acme.lock" ) , error) ) ?;
137+ ensure_safe_account_destination ( & active) ?;
138+ let pending = directory. join ( ACME_ACCOUNT_DEACTIVATION_FILE ) ;
139+ match fs:: symlink_metadata ( & pending) {
140+ Ok ( _) => {
141+ return Err ( AcmeAccountStoreError :: UnsafePath {
142+ path : pending,
143+ message : "account deactivation transaction already exists" . to_owned ( ) ,
144+ } ) ;
145+ }
146+ Err ( error) if error. kind ( ) == io:: ErrorKind :: NotFound => { }
147+ Err ( error) => return Err ( account_store_io_error ( & pending, error) ) ,
148+ }
149+ fs:: rename ( & active, & pending) . map_err ( |error| account_store_io_error ( & active, error) ) ?;
150+ sync_account_directory ( directory) ?;
151+ Ok ( AccountDeactivationTransaction {
152+ directory : directory. to_path_buf ( ) ,
153+ active,
154+ pending,
155+ _lock : lock,
156+ } )
157+ }
158+
159+ #[ cfg( feature = "acme-client" ) ]
160+ impl AccountDeactivationTransaction {
161+ #[ cfg( test) ]
162+ pub ( crate ) fn abandon ( self ) { }
163+
164+ pub ( crate ) fn rollback ( self ) -> Result < ( ) , AcmeAccountStoreError > {
165+ fs:: rename ( & self . pending , & self . active )
166+ . map_err ( |error| account_store_io_error ( & self . active , error) ) ?;
167+ sync_account_directory ( & self . directory )
168+ }
169+
170+ pub ( crate ) fn complete ( self ) -> Result < ( ) , AcmeAccountStoreError > {
171+ fs:: remove_file ( & self . pending )
172+ . map_err ( |error| account_store_io_error ( & self . pending , error) ) ?;
173+ sync_account_directory ( & self . directory )
174+ }
175+ }
176+
83177pub fn store_account_credentials (
84178 storage : & Path ,
85179 issuer_name : & str ,
0 commit comments