|
| 1 | +//! Shared runtime state of the module. |
| 2 | +use core::ffi::c_void; |
| 3 | +use core::ptr; |
| 4 | + |
| 5 | +use nginx_sys::{ngx_int_t, ngx_shm_zone_t, NGX_LOG_EMERG}; |
| 6 | +use ngx::allocator::{AllocError, Allocator, Box, TryCloneIn}; |
| 7 | +use ngx::collections::Queue; |
| 8 | +use ngx::core::{SlabPool, Status}; |
| 9 | +use ngx::log::ngx_cycle_log; |
| 10 | +use ngx::sync::RwLock; |
| 11 | +use ngx::{ngx_log_debug, ngx_log_error}; |
| 12 | + |
| 13 | +use crate::conf::shared_zone::SharedZone; |
| 14 | +use crate::conf::AcmeMainConfig; |
| 15 | + |
| 16 | +pub use self::certificate::CertificateContext; |
| 17 | +pub use self::issuer::IssuerContext; |
| 18 | + |
| 19 | +pub mod certificate; |
| 20 | +pub mod issuer; |
| 21 | + |
| 22 | +#[derive(Debug)] |
| 23 | +pub struct AcmeSharedData<A = SlabPool> |
| 24 | +where |
| 25 | + A: Allocator + Clone, |
| 26 | +{ |
| 27 | + pub issuers: Queue<RwLock<IssuerContext>, A>, |
| 28 | +} |
| 29 | + |
| 30 | +impl<A> AcmeSharedData<A> |
| 31 | +where |
| 32 | + A: Allocator + Clone, |
| 33 | +{ |
| 34 | + pub fn try_new_in(alloc: A) -> Result<Self, AllocError> { |
| 35 | + Ok(Self { |
| 36 | + issuers: Queue::try_new_in(alloc)?, |
| 37 | + }) |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +pub extern "C" fn ngx_acme_shared_zone_init( |
| 42 | + shm_zone: *mut ngx_shm_zone_t, |
| 43 | + data: *mut c_void, |
| 44 | +) -> ngx_int_t { |
| 45 | + // SAFETY: shm_zone is always valid in this callback |
| 46 | + let shm_zone = unsafe { &mut *shm_zone }; |
| 47 | + let log = ngx_cycle_log().as_ptr(); |
| 48 | + |
| 49 | + ngx_log_debug!( |
| 50 | + log, |
| 51 | + "acme: init shared zone \"{}:{}\"", |
| 52 | + shm_zone.shm.name, |
| 53 | + shm_zone.shm.size, |
| 54 | + ); |
| 55 | + |
| 56 | + let oamcf = unsafe { data.cast::<AcmeMainConfig>().as_ref() }; |
| 57 | + let amcf = unsafe { shm_zone.data.cast::<AcmeMainConfig>().as_mut().unwrap() }; |
| 58 | + let zone = SharedZone::Ready(shm_zone.into()); |
| 59 | + |
| 60 | + let mut alloc = zone.allocator().expect("shared zone allocator"); |
| 61 | + |
| 62 | + // Our shared zone is `noreuse`, meaning that we get an empty zone every time unless we are |
| 63 | + // running on Windows. |
| 64 | + |
| 65 | + let Ok(mut data) = |
| 66 | + AcmeSharedData::try_new_in(alloc.clone()).and_then(|x| Box::try_new_in(x, alloc.clone())) |
| 67 | + else { |
| 68 | + ngx_log_error!(NGX_LOG_EMERG, log, "cannot allocate acme shared data"); |
| 69 | + return Status::NGX_ERROR.into(); |
| 70 | + }; |
| 71 | + |
| 72 | + for issuer in &mut amcf.issuers[..] { |
| 73 | + // Create new shared data. |
| 74 | + let Ok(ctx) = IssuerContext::try_new_in(issuer, alloc.clone()) else { |
| 75 | + ngx_log_error!( |
| 76 | + NGX_LOG_EMERG, |
| 77 | + log, |
| 78 | + "cannot allocate acme issuer \"{}\"", |
| 79 | + issuer.name, |
| 80 | + ); |
| 81 | + return Status::NGX_ERROR.into(); |
| 82 | + }; |
| 83 | + |
| 84 | + // Copy data from the previous cycle. |
| 85 | + if let Some(oissuer) = oamcf.and_then(|x| x.issuer(&issuer.name)) { |
| 86 | + ngx_log_debug!(log, "acme: copy old data for issuer \"{}\"", issuer.name); |
| 87 | + |
| 88 | + for (order, ctx) in issuer.orders.iter_mut() { |
| 89 | + // Should not fail as we just allocated all the certificate contexts. |
| 90 | + let CertificateContext::Shared(ctx) = ctx else { |
| 91 | + continue; |
| 92 | + }; |
| 93 | + |
| 94 | + let Some(CertificateContext::Shared(octx)) = oissuer.orders.get(order) else { |
| 95 | + continue; |
| 96 | + }; |
| 97 | + |
| 98 | + // The old shared zone is going away as soon as we're done, so we have to copy the |
| 99 | + // data to the new slab pool. |
| 100 | + let Ok(cloned) = octx.read().try_clone_in(alloc.clone()) else { |
| 101 | + return Status::NGX_ERROR.into(); |
| 102 | + }; |
| 103 | + |
| 104 | + *ctx.write() = cloned; |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + if let Ok(ctx) = data.issuers.push_back(RwLock::new(ctx)) { |
| 109 | + // SAFETY: we ensured that the chosen data structure will not move the IssuerContext, |
| 110 | + // thus the pointer will remain valid beyond this scope. |
| 111 | + // |
| 112 | + // The assigned lifetime is a bit misleading though; shared zone will be unmapped |
| 113 | + // while the main config is still present, right before calling the cycle pool cleanup. |
| 114 | + // A proper ownership-tracking pointer could attempt to unref the data from the config |
| 115 | + // destructor _after_ the zone is unmapped and thus trip on an invalid address. |
| 116 | + // |
| 117 | + // Of all the ways to handle that, we are picking the most obviously unsafe to make |
| 118 | + // sure this detail is not missed while reading. |
| 119 | + issuer.data = Some(unsafe { &*ptr::from_ref(ctx) }); |
| 120 | + } else { |
| 121 | + ngx_log_error!( |
| 122 | + NGX_LOG_EMERG, |
| 123 | + log, |
| 124 | + "cannot allocate acme issuer \"{}\"", |
| 125 | + issuer.name, |
| 126 | + ); |
| 127 | + return Status::NGX_ERROR.into(); |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + // Will be freed when the zone is unmapped. |
| 132 | + let data = Box::leak(data); |
| 133 | + |
| 134 | + alloc.as_mut().data = ptr::from_mut(data).cast(); |
| 135 | + |
| 136 | + amcf.data = Some(data); |
| 137 | + amcf.shm_zone = zone; |
| 138 | + |
| 139 | + Status::NGX_OK.into() |
| 140 | +} |
0 commit comments