itsi-scheduler 0.2.26 → 0.2.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,6 @@ use super::bind_protocol::BindProtocol;
8
8
 
9
9
  use super::tls::ItsiTlsAcceptor;
10
10
  use itsi_error::{ItsiError, Result};
11
- use itsi_tracing::info;
12
11
  use socket2::{Domain, Protocol, SockRef, Socket, Type};
13
12
  use std::fmt::Display;
14
13
  use std::net::{IpAddr, SocketAddr, TcpListener};
@@ -21,7 +20,6 @@ use tokio::net::UnixListener as TokioUnixListener;
21
20
  use tokio::net::{unix, TcpStream, UnixStream};
22
21
  use tokio::sync::watch::Receiver;
23
22
  use tokio::time::timeout;
24
- use tokio_stream::StreamExt;
25
23
 
26
24
  pub(crate) enum Listener {
27
25
  Tcp(TcpListener),
@@ -115,25 +113,8 @@ impl TokioListener {
115
113
  }
116
114
 
117
115
  pub async fn spawn_acme_event_task(&self, mut shutdown_receiver: Receiver<RunningPhase>) {
118
- if let TokioListener::TcpTls(
119
- _,
120
- ItsiTlsAcceptor::Automatic(_acme_acceptor, state, _server_config),
121
- ) = self
122
- {
123
- let mut state = state.lock().await;
124
- loop {
125
- tokio::select! {
126
- stream_event = StreamExt::next(&mut *state) => {
127
- match stream_event {
128
- Some(event) => info!("ACME Event: {:?}", event),
129
- None => error!("Received no acme event"),
130
- }
131
- },
132
- _ = shutdown_receiver.changed() => {
133
- break;
134
- }
135
- }
136
- }
116
+ if let TokioListener::TcpTls(_, ItsiTlsAcceptor::Automatic { .. }) = self {
117
+ let _ = shutdown_receiver.changed().await;
137
118
  }
138
119
  }
139
120
 
@@ -215,7 +196,11 @@ impl AcceptedStream {
215
196
  Err(_) => Err(ItsiError::Pass),
216
197
  }
217
198
  }
218
- ItsiTlsAcceptor::Automatic(acme_acceptor, _, rustls_config) => {
199
+ ItsiTlsAcceptor::Automatic {
200
+ acme_acceptor,
201
+ server_config,
202
+ ..
203
+ } => {
219
204
  let accept_future = acme_acceptor.accept(stream);
220
205
  match timeout(handshake_timeout, accept_future).await {
221
206
  Err(_) => Err(ItsiError::Pass),
@@ -224,7 +209,7 @@ impl AcceptedStream {
224
209
  Ok(Some(start_handshake)) => {
225
210
  match timeout(
226
211
  handshake_timeout,
227
- start_handshake.into_stream(rustls_config.clone()),
212
+ start_handshake.into_stream(server_config.clone()),
228
213
  )
229
214
  .await
230
215
  {
@@ -259,7 +244,7 @@ impl AcceptedStream {
259
244
  Err(_) => Err(ItsiError::Pass),
260
245
  }
261
246
  }
262
- ItsiTlsAcceptor::Automatic(_, _, _) => {
247
+ ItsiTlsAcceptor::Automatic { .. } => {
263
248
  error!("Automatic TLS not supported on Unix sockets");
264
249
  Err(ItsiError::UnsupportedProtocol(
265
250
  "Automatic TLS on Unix Sockets".to_owned(),
@@ -1,8 +1,9 @@
1
1
  use base64::{engine::general_purpose, Engine as _};
2
- use itsi_acme::{AcmeAcceptor, AcmeConfig, AcmeState};
2
+ use itsi_acme::{AcmeAcceptor, AcmeConfig, AcmeState, Http01Handler, ResolvesServerCertAcme};
3
3
  use itsi_error::Result;
4
- use itsi_tracing::info;
4
+ use itsi_tracing::{error, info};
5
5
  use locked_dir_cache::LockedDirCache;
6
+ use parking_lot::{Mutex as ParkingMutex, RwLock as ParkingRwLock};
6
7
  use rcgen::ExtendedKeyUsagePurpose;
7
8
  use rcgen::{
8
9
  BasicConstraints, CertificateParams, DistinguishedName, DnType, IsCa, KeyPair, KeyUsagePurpose,
@@ -17,9 +18,14 @@ use std::{
17
18
  collections::HashMap,
18
19
  fs,
19
20
  io::{BufReader, Error},
20
- sync::Arc,
21
+ sync::{
22
+ atomic::{AtomicBool, Ordering},
23
+ Arc,
24
+ },
25
+ thread::JoinHandle,
21
26
  };
22
- use tokio::sync::Mutex;
27
+ use tokio::runtime::Builder as RuntimeBuilder;
28
+ use tokio::sync::{mpsc, watch};
23
29
  use tokio_rustls::{rustls::ServerConfig, TlsAcceptor};
24
30
 
25
31
  use crate::env::{
@@ -29,14 +35,300 @@ use crate::env::{
29
35
 
30
36
  mod locked_dir_cache;
31
37
 
38
+ #[derive(Debug, Clone)]
39
+ pub struct ManagedTlsDomainStatus {
40
+ pub domain: String,
41
+ pub status: String,
42
+ pub last_error: Option<String>,
43
+ }
44
+
45
+ #[derive(Clone)]
46
+ struct DynamicAcmeConfigTemplate {
47
+ client_config: Arc<ClientConfig>,
48
+ directory_url: String,
49
+ contact: Vec<String>,
50
+ cache_dir: String,
51
+ }
52
+
53
+ impl DynamicAcmeConfigTemplate {
54
+ fn state_for_domain(
55
+ &self,
56
+ domain: &str,
57
+ resolver: Arc<ResolvesServerCertAcme>,
58
+ http01_handler: Arc<Http01Handler>,
59
+ http01_enabled: bool,
60
+ ) -> AcmeState<Error> {
61
+ let state = AcmeConfig::new([domain])
62
+ .contact(self.contact.clone())
63
+ .cache(LockedDirCache::new(self.cache_dir.clone()))
64
+ .directory(&self.directory_url)
65
+ .client_tls_config(self.client_config.clone());
66
+ let mut state = AcmeState::new_with_resolver(
67
+ state,
68
+ resolver,
69
+ http01_handler,
70
+ Some(domain.to_string()),
71
+ );
72
+ state.set_http01_enabled(http01_enabled);
73
+ state
74
+ }
75
+ }
76
+
77
+ enum DynamicAcmeCommand {
78
+ Register(String),
79
+ Unregister(String),
80
+ Shutdown,
81
+ }
82
+
83
+ #[derive(Clone)]
84
+ pub struct DynamicAcmeManager {
85
+ inner: Arc<DynamicAcmeManagerInner>,
86
+ }
87
+
88
+ struct DynamicAcmeManagerInner {
89
+ resolver: Arc<ResolvesServerCertAcme>,
90
+ http01_registry: Arc<ParkingRwLock<HashMap<String, Arc<Http01Handler>>>>,
91
+ statuses: Arc<ParkingRwLock<HashMap<String, ManagedTlsDomainStatus>>>,
92
+ http01_enabled: Arc<AtomicBool>,
93
+ initialized: AtomicBool,
94
+ initial_domains: Vec<String>,
95
+ command_tx: mpsc::UnboundedSender<DynamicAcmeCommand>,
96
+ thread_handle: ParkingMutex<Option<JoinHandle<()>>>,
97
+ }
98
+
99
+ impl DynamicAcmeManager {
100
+ fn new(template: DynamicAcmeConfigTemplate, initial_domains: Vec<String>) -> Self {
101
+ let resolver = ResolvesServerCertAcme::new();
102
+ let http01_handler = Arc::new(Http01Handler::new());
103
+ let http01_registry = Arc::new(ParkingRwLock::new(HashMap::new()));
104
+ let statuses = Arc::new(ParkingRwLock::new(HashMap::new()));
105
+ let http01_enabled = Arc::new(AtomicBool::new(false));
106
+ let (command_tx, mut command_rx) = mpsc::unbounded_channel();
107
+
108
+ let resolver_clone = resolver.clone();
109
+ let http01_handler_clone = http01_handler.clone();
110
+ let http01_registry_clone = http01_registry.clone();
111
+ let statuses_clone = statuses.clone();
112
+ let http01_enabled_clone = http01_enabled.clone();
113
+
114
+ let thread_handle = std::thread::spawn(move || {
115
+ let runtime = RuntimeBuilder::new_current_thread()
116
+ .enable_all()
117
+ .build()
118
+ .expect("Failed to build dynamic ACME runtime");
119
+ runtime.block_on(async move {
120
+ let mut cancellations: HashMap<String, watch::Sender<bool>> = HashMap::new();
121
+
122
+ while let Some(command) = command_rx.recv().await {
123
+ match command {
124
+ DynamicAcmeCommand::Register(domain) => {
125
+ let domain = domain.to_ascii_lowercase();
126
+ if cancellations.contains_key(&domain) {
127
+ continue;
128
+ }
129
+
130
+ statuses_clone.write().insert(
131
+ domain.clone(),
132
+ ManagedTlsDomainStatus {
133
+ domain: domain.clone(),
134
+ status: "pending".to_string(),
135
+ last_error: None,
136
+ },
137
+ );
138
+ http01_registry_clone
139
+ .write()
140
+ .insert(domain.clone(), http01_handler_clone.clone());
141
+
142
+ let (cancel_tx, mut cancel_rx) = watch::channel(false);
143
+ cancellations.insert(domain.clone(), cancel_tx);
144
+
145
+ let resolver = resolver_clone.clone();
146
+ let http01_handler = http01_handler_clone.clone();
147
+ let registry = http01_registry_clone.clone();
148
+ let statuses = statuses_clone.clone();
149
+ let template = template.clone();
150
+ let enabled = http01_enabled_clone.clone();
151
+ let task_domain = domain.clone();
152
+
153
+ tokio::spawn(async move {
154
+ statuses.write().insert(
155
+ task_domain.clone(),
156
+ ManagedTlsDomainStatus {
157
+ domain: task_domain.clone(),
158
+ status: "issuing".to_string(),
159
+ last_error: None,
160
+ },
161
+ );
162
+ let mut state = template.state_for_domain(
163
+ &task_domain,
164
+ resolver.clone(),
165
+ http01_handler,
166
+ enabled.load(Ordering::SeqCst),
167
+ );
168
+
169
+ loop {
170
+ tokio::select! {
171
+ changed = cancel_rx.changed() => {
172
+ if changed.is_ok() && *cancel_rx.borrow() {
173
+ resolver.remove_auth_key(&task_domain);
174
+ resolver.remove_cert_for_domain(&task_domain);
175
+ registry.write().remove(&task_domain);
176
+ statuses.write().remove(&task_domain);
177
+ break;
178
+ }
179
+ }
180
+ event = futures::StreamExt::next(&mut state) => {
181
+ match event {
182
+ Some(Ok(_)) => {
183
+ let mut statuses = statuses.write();
184
+ if let Some(status) = statuses.get_mut(&task_domain) {
185
+ status.status = "active".to_string();
186
+ status.last_error = None;
187
+ }
188
+ }
189
+ Some(Err(err)) => {
190
+ let mut statuses = statuses.write();
191
+ if let Some(status) = statuses.get_mut(&task_domain) {
192
+ status.status = "failed".to_string();
193
+ status.last_error = Some(err.to_string());
194
+ }
195
+ }
196
+ None => break,
197
+ }
198
+ }
199
+ }
200
+ }
201
+ });
202
+ }
203
+ DynamicAcmeCommand::Unregister(domain) => {
204
+ let domain = domain.to_ascii_lowercase();
205
+ if let Some(cancel_tx) = cancellations.remove(&domain) {
206
+ let _ = cancel_tx.send(true);
207
+ } else {
208
+ http01_registry_clone.write().remove(&domain);
209
+ resolver_clone.remove_auth_key(&domain);
210
+ resolver_clone.remove_cert_for_domain(&domain);
211
+ statuses_clone.write().remove(&domain);
212
+ }
213
+ }
214
+ DynamicAcmeCommand::Shutdown => {
215
+ for (_, cancel_tx) in cancellations.drain() {
216
+ let _ = cancel_tx.send(true);
217
+ }
218
+ break;
219
+ }
220
+ }
221
+ }
222
+ });
223
+ });
224
+
225
+ Self {
226
+ inner: Arc::new(DynamicAcmeManagerInner {
227
+ resolver,
228
+ http01_registry,
229
+ statuses,
230
+ http01_enabled,
231
+ initialized: AtomicBool::new(false),
232
+ initial_domains,
233
+ command_tx,
234
+ thread_handle: ParkingMutex::new(Some(thread_handle)),
235
+ }),
236
+ }
237
+ }
238
+
239
+ pub fn resolver(&self) -> Arc<ResolvesServerCertAcme> {
240
+ self.inner.resolver.clone()
241
+ }
242
+
243
+ pub fn set_http01_enabled(&self, enabled: bool) {
244
+ self.inner.http01_enabled.store(enabled, Ordering::SeqCst);
245
+ }
246
+
247
+ pub fn initialize_domains(&self) {
248
+ if self.inner.initialized.swap(true, Ordering::SeqCst) {
249
+ return;
250
+ }
251
+
252
+ for domain in &self.inner.initial_domains {
253
+ self.register_domain(domain.clone());
254
+ }
255
+ }
256
+
257
+ pub fn register_domain(&self, domain: String) {
258
+ let _ = self
259
+ .inner
260
+ .command_tx
261
+ .send(DynamicAcmeCommand::Register(domain));
262
+ }
263
+
264
+ pub fn unregister_domain(&self, domain: &str) {
265
+ let _ = self
266
+ .inner
267
+ .command_tx
268
+ .send(DynamicAcmeCommand::Unregister(domain.to_string()));
269
+ }
270
+
271
+ pub fn http01_response(&self, host: &str, path: &str) -> Option<String> {
272
+ self.inner
273
+ .http01_registry
274
+ .read()
275
+ .get(host)
276
+ .and_then(|handler| handler.handle_challenge_request(path))
277
+ }
278
+
279
+ pub fn statuses(&self) -> Vec<ManagedTlsDomainStatus> {
280
+ let mut statuses = self
281
+ .inner
282
+ .statuses
283
+ .read()
284
+ .values()
285
+ .cloned()
286
+ .collect::<Vec<_>>();
287
+ statuses.sort_by(|a, b| a.domain.cmp(&b.domain));
288
+ statuses
289
+ }
290
+ }
291
+
292
+ impl Drop for DynamicAcmeManagerInner {
293
+ fn drop(&mut self) {
294
+ let _ = self.command_tx.send(DynamicAcmeCommand::Shutdown);
295
+ if let Some(handle) = self.thread_handle.lock().take() {
296
+ if let Err(err) = handle.join() {
297
+ error!("Dynamic ACME manager thread join failed: {:?}", err);
298
+ }
299
+ }
300
+ }
301
+ }
302
+
32
303
  #[derive(Clone)]
33
304
  pub enum ItsiTlsAcceptor {
34
305
  Manual(TlsAcceptor),
35
- Automatic(
36
- AcmeAcceptor,
37
- Arc<Mutex<AcmeState<Error>>>,
38
- Arc<ServerConfig>,
39
- ),
306
+ Automatic {
307
+ acme_acceptor: AcmeAcceptor,
308
+ manager: DynamicAcmeManager,
309
+ server_config: Arc<ServerConfig>,
310
+ },
311
+ }
312
+
313
+ impl ItsiTlsAcceptor {
314
+ pub fn manager(&self) -> Option<DynamicAcmeManager> {
315
+ match self {
316
+ ItsiTlsAcceptor::Automatic { manager, .. } => Some(manager.clone()),
317
+ ItsiTlsAcceptor::Manual(_) => None,
318
+ }
319
+ }
320
+
321
+ pub fn set_http01_enabled(&self, enabled: bool) {
322
+ if let ItsiTlsAcceptor::Automatic { manager, .. } = self {
323
+ manager.set_http01_enabled(enabled);
324
+ }
325
+ }
326
+
327
+ pub fn initialize_domains(&self) {
328
+ if let ItsiTlsAcceptor::Automatic { manager, .. } = self {
329
+ manager.initialize_domains();
330
+ }
331
+ }
40
332
  }
41
333
 
42
334
  /// Generates a TLS configuration based on either :
@@ -52,66 +344,79 @@ pub fn configure_tls(
52
344
  let domains = query_params
53
345
  .get("domains")
54
346
  .map(|v| v.split(',').map(String::from).collect::<Vec<_>>())
55
- .or_else(|| query_params.get("domain").map(|v| vec![v.to_string()]));
347
+ .or_else(|| query_params.get("domain").map(|v| vec![v.to_string()]))
348
+ .unwrap_or_default();
56
349
 
57
350
  if query_params.get("cert").is_some_and(|c| c == "acme") {
58
- if let Some(domains) = domains {
59
- let directory_url = &*ITSI_ACME_DIRECTORY_URL;
60
- info!(
61
- domains = format!("{:?}", domains),
62
- directory_url, "Requesting acme cert"
63
- );
64
- let acme_contact_email = query_params
65
- .get("acme_email")
66
- .map(|s| s.to_string())
67
- .or_else(|| (*ITSI_ACME_CONTACT_EMAIL).as_ref().ok().map(|s| s.to_string()))
68
- .ok_or_else(|| itsi_error::ItsiError::ArgumentError(
69
- "acme_email query param or ITSI_ACME_CONTACT_EMAIL must be set before you can auto-generate let's encrypt certificates".to_string(),
70
- ))?;
71
-
72
- let acme_config = AcmeConfig::new(domains)
73
- .contact([format!("mailto:{}", acme_contact_email)])
74
- .cache(LockedDirCache::new(&*ITSI_ACME_CACHE_DIR))
75
- .directory(directory_url);
76
-
77
- let acme_state = if let Ok(ca_pem_path) = &*ITSI_ACME_CA_PEM_PATH {
78
- let mut root_cert_store = RootCertStore::empty();
79
-
80
- let ca_pem = fs::read(ca_pem_path).expect("failed to read CA pem file");
81
- let mut ca_reader = BufReader::new(&ca_pem[..]);
82
- let der_certs: Vec<CertificateDer> = certs(&mut ca_reader)
83
- .collect::<std::result::Result<Vec<CertificateDer>, _>>()
84
- .map_err(|e| {
85
- itsi_error::ItsiError::ArgumentError(format!(
86
- "Invalid ACME CA Pem path {:?}",
87
- e
88
- ))
89
- })?;
90
- root_cert_store.add_parsable_certificates(der_certs);
91
-
92
- let client_config = ClientConfig::builder()
351
+ let directory_url = &*ITSI_ACME_DIRECTORY_URL;
352
+ info!(
353
+ domains = format!("{:?}", domains),
354
+ directory_url, "Requesting acme cert"
355
+ );
356
+ let acme_contact_email = query_params
357
+ .get("acme_email")
358
+ .map(|s| s.to_string())
359
+ .or_else(|| (*ITSI_ACME_CONTACT_EMAIL).as_ref().ok().map(|s| s.to_string()))
360
+ .ok_or_else(|| itsi_error::ItsiError::ArgumentError(
361
+ "acme_email query param or ITSI_ACME_CONTACT_EMAIL must be set before you can auto-generate let's encrypt certificates".to_string(),
362
+ ))?;
363
+
364
+ let client_config = if let Ok(ca_pem_path) = &*ITSI_ACME_CA_PEM_PATH {
365
+ let mut root_cert_store = RootCertStore::empty();
366
+
367
+ let ca_pem = fs::read(ca_pem_path).expect("failed to read CA pem file");
368
+ let mut ca_reader = BufReader::new(&ca_pem[..]);
369
+ let der_certs: Vec<CertificateDer> = certs(&mut ca_reader)
370
+ .collect::<std::result::Result<Vec<CertificateDer>, _>>()
371
+ .map_err(|e| {
372
+ itsi_error::ItsiError::ArgumentError(format!("Invalid ACME CA Pem path {:?}", e))
373
+ })?;
374
+ root_cert_store.add_parsable_certificates(der_certs);
375
+
376
+ Arc::new(
377
+ ClientConfig::builder()
93
378
  .with_root_certificates(root_cert_store)
94
- .with_no_client_auth();
95
- acme_config
96
- .client_tls_config(Arc::new(client_config))
97
- .state()
98
- } else {
99
- acme_config.state()
100
- };
101
-
102
- let mut rustls_config = ServerConfig::builder()
103
- .with_no_client_auth()
104
- .with_cert_resolver(acme_state.resolver());
105
-
106
- rustls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
107
-
108
- let acceptor = acme_state.acceptor();
109
- return Ok(ItsiTlsAcceptor::Automatic(
110
- acceptor,
111
- Arc::new(Mutex::new(acme_state)),
112
- Arc::new(rustls_config),
113
- ));
114
- }
379
+ .with_no_client_auth(),
380
+ )
381
+ } else {
382
+ let mut root_store = RootCertStore::empty();
383
+ root_store.extend(
384
+ webpki_roots::TLS_SERVER_ROOTS
385
+ .iter()
386
+ .map(|ta| rustls::pki_types::TrustAnchor {
387
+ subject: ta.subject.clone(),
388
+ subject_public_key_info: ta.subject_public_key_info.clone(),
389
+ name_constraints: ta.name_constraints.clone(),
390
+ }),
391
+ );
392
+ Arc::new(
393
+ ClientConfig::builder()
394
+ .with_root_certificates(root_store)
395
+ .with_no_client_auth(),
396
+ )
397
+ };
398
+
399
+ let manager = DynamicAcmeManager::new(
400
+ DynamicAcmeConfigTemplate {
401
+ client_config,
402
+ directory_url: directory_url.to_string(),
403
+ contact: vec![format!("mailto:{}", acme_contact_email)],
404
+ cache_dir: ITSI_ACME_CACHE_DIR.to_string(),
405
+ },
406
+ domains.clone(),
407
+ );
408
+
409
+ let mut rustls_config = ServerConfig::builder()
410
+ .with_no_client_auth()
411
+ .with_cert_resolver(manager.resolver());
412
+
413
+ rustls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
414
+
415
+ return Ok(ItsiTlsAcceptor::Automatic {
416
+ acme_acceptor: AcmeAcceptor::new(manager.resolver()),
417
+ manager,
418
+ server_config: Arc::new(rustls_config),
419
+ });
115
420
  }
116
421
  let (certs, key) = if let (Some(cert_path), Some(key_path)) =
117
422
  (query_params.get("cert"), query_params.get("key"))
@@ -121,7 +426,7 @@ pub fn configure_tls(
121
426
  let key = load_private_key(key_path);
122
427
  (certs, key)
123
428
  } else {
124
- generate_ca_signed_cert(domains.unwrap_or(vec![host.to_owned()]))?
429
+ generate_ca_signed_cert(if domains.is_empty() { vec![host.to_owned()] } else { domains })?
125
430
  };
126
431
 
127
432
  let mut config = ServerConfig::builder()
@@ -263,7 +263,7 @@ impl MiddlewareSet {
263
263
  pub fn stack_for(
264
264
  &self,
265
265
  request: &HttpRequest,
266
- ) -> Result<(&Vec<Middleware>, Option<Arc<Regex>>)> {
266
+ ) -> Option<(&Vec<Middleware>, Option<Arc<Regex>>)> {
267
267
  let binding = self.route_set.matches(request.uri().path());
268
268
  let matches = binding.iter();
269
269
 
@@ -276,7 +276,7 @@ impl MiddlewareSet {
276
276
  let matching_pattern = self.patterns.get(index).cloned();
277
277
  if let Some(stack) = self.stacks.get(&index) {
278
278
  if stack.matches(request) {
279
- return Ok((&stack.layers, matching_pattern));
279
+ return Some((&stack.layers, matching_pattern));
280
280
  }
281
281
  }
282
282
  }
@@ -285,13 +285,7 @@ impl MiddlewareSet {
285
285
  request.uri().path(),
286
286
  self.route_set
287
287
  );
288
- Err(magnus::Error::new(
289
- magnus::Ruby::get().unwrap().exception_standard_error(),
290
- format!(
291
- "No matching middleware stack found for request: {:?}",
292
- request
293
- ),
294
- ))
288
+ None
295
289
  }
296
290
 
297
291
  pub fn parse_middleware(middleware_type: String, parameters: Value) -> Result<Middleware> {
@@ -63,7 +63,7 @@ pub fn send_lifecycle_event(event: LifecycleEvent) {
63
63
  }
64
64
  }
65
65
 
66
- fn receive_signal(signum: i32, _: sighandler_t) {
66
+ extern "C" fn receive_signal(signum: i32) {
67
67
  debug!("Received signal: {}", signum);
68
68
  SIGINT_COUNT.fetch_add(-1, Ordering::SeqCst);
69
69
  let event = match signum {
@@ -96,20 +96,25 @@ fn receive_signal(signum: i32, _: sighandler_t) {
96
96
  }
97
97
  }
98
98
 
99
+ fn signal_handler() -> sighandler_t {
100
+ receive_signal as *const () as sighandler_t
101
+ }
102
+
99
103
  pub fn reset_signal_handlers() -> bool {
100
104
  debug!("Resetting signal handlers");
101
105
  SIGINT_COUNT.store(0, Ordering::SeqCst);
102
106
  SHUTDOWN_REQUESTED.store(false, Ordering::SeqCst);
103
107
 
104
108
  unsafe {
105
- libc::signal(libc::SIGTERM, receive_signal as usize);
106
- libc::signal(libc::SIGINT, receive_signal as usize);
107
- libc::signal(libc::SIGUSR2, receive_signal as usize);
108
- libc::signal(libc::SIGUSR1, receive_signal as usize);
109
- libc::signal(libc::SIGHUP, receive_signal as usize);
110
- libc::signal(libc::SIGTTIN, receive_signal as usize);
111
- libc::signal(libc::SIGTTOU, receive_signal as usize);
112
- libc::signal(libc::SIGCHLD, receive_signal as usize);
109
+ let handler = signal_handler();
110
+ libc::signal(libc::SIGTERM, handler);
111
+ libc::signal(libc::SIGINT, handler);
112
+ libc::signal(libc::SIGUSR2, handler);
113
+ libc::signal(libc::SIGUSR1, handler);
114
+ libc::signal(libc::SIGHUP, handler);
115
+ libc::signal(libc::SIGTTIN, handler);
116
+ libc::signal(libc::SIGTTOU, handler);
117
+ libc::signal(libc::SIGCHLD, handler);
113
118
  }
114
119
  true
115
120
  }