rblur 0.1.0

一個支援網頁配置介面的輕量級網頁伺服器
Documentation
use std::{
    str::FromStr,
    sync::atomic::{AtomicPtr, Ordering},
    time::Duration,
};

use racme::{
    account::{Account, AccountBuilder},
    certificate::{Certificate, CertificateError},
    key_pair::KeyPair,
    order::{DnsProvider, Order, OrderError},
};
use serde_json::Value;
use thiserror::Error;

use crate::{
    core::config::{
        command::{CommandBuilder, ParameterBuilder},
        config_context::ConfigContext,
        config_manager::{bool_str_to_bool, get_config_param},
    },
    register_commands,
};

#[derive(Debug, Error)]
pub enum HttpSSLError {
    #[error("SSL is not enabled")]
    SSLNotEnabled,
    #[error("Email empty")]
    EmailEmpty,
    #[error("Domain empty")]
    DomainEmpty,
    #[error("Order error: {0}")]
    OrderError(Box<OrderError>),
    #[error("Certificate error: {0}")]
    CertificateError(#[from] CertificateError),
}

impl From<OrderError> for HttpSSLError {
    fn from(error: OrderError) -> Self {
        HttpSSLError::OrderError(Box::new(error))
    }
}

type Result<T> = std::result::Result<T, HttpSSLError>;

register_commands!(
    CommandBuilder::new("ssl")
        .is_block()
        .allowed_parents(vec!["server".to_string()])
        .display_name("en", "SSL")
        .display_name("zh-tw", "SSL")
        .desc("en", "Configures SSL/TLS security settings for the server")
        .desc("zh-tw", "配置伺服器的 SSL/TLS 安全設定")
        .params(vec![ParameterBuilder::new(0)
            .display_name("en", "Enable SSL")
            .display_name("zh-tw", "啟用 SSL")
            .type_name("bool")
            .is_required(true)
            .default("false")
            .desc("en", "Activates SSL/TLS encryption for the server")
            .desc("zh-tw", "為伺服器啟用 SSL/TLS 加密")
            .build()])
        .build(handle_create_ssl),
    CommandBuilder::new("ssl_email")
        .allowed_parents(vec!["ssl".to_string()])
        .display_name("en", "SSL Email")
        .display_name("zh-tw", "SSL 電子郵件")
        .desc(
            "en",
            "Specifies contact email for SSL certificate management"
        )
        .desc("zh-tw", "指定 SSL 憑證管理的聯絡電子郵件")
        .params(vec![ParameterBuilder::new(0)
            .display_name("en", "Email")
            .display_name("zh-tw", "電子郵件")
            .type_name("String")
            .is_required(true)
            .default("example@example.com")
            .desc(
                "en",
                "Email address used for SSL certificate registration and renewal notifications"
            )
            .desc("zh-tw", "用於 SSL 憑證註冊和更新通知的電子郵件地址")
            .build()])
        .build(handle_set_ssl_email),
    CommandBuilder::new("ssl_auto_renew")
        .allowed_parents(vec!["ssl".to_string()])
        .display_name("en", "SSL Auto Renew")
        .display_name("zh-tw", "SSL 自動更新")
        .desc("en", "Manages automatic SSL certificate renewal")
        .desc("zh-tw", "管理 SSL 憑證的自動更新")
        .params(vec![ParameterBuilder::new(0)
            .display_name("en", "Enable Auto Renew")
            .display_name("zh-tw", "啟用自動更新")
            .type_name("bool")
            .is_required(true)
            .default("false")
            .desc(
                "en",
                "Automatically renew SSL certificates before expiration"
            )
            .desc("zh-tw", "在憑證過期前自動續約 SSL 憑證")
            .build()])
        .build(handle_set_ssl_auto_renew),
    CommandBuilder::new("ssl_renew_day")
        .allowed_parents(vec!["ssl".to_string()])
        .display_name("en", "SSL Renew Day")
        .display_name("zh-tw", "SSL 更新天數")
        .desc("en", "Sets the timing for SSL certificate renewal")
        .desc("zh-tw", "設定 SSL 憑證更新的時間點")
        .params(vec![ParameterBuilder::new(0)
            .display_name("en", "Days")
            .display_name("zh-tw", "天數")
            .type_name("u32")
            .is_required(true)
            .default("30")
            .desc(
                "en",
                "Number of days before certificate expiration to initiate renewal"
            )
            .desc("zh-tw", "在憑證到期前幾天開始啟動更新程序")
            .build()])
        .build(handle_set_ssl_renew_day),
    CommandBuilder::new("ssl_domain")
        .allowed_parents(vec!["ssl".to_string()])
        .display_name("en", "SSL Domain")
        .display_name("zh-tw", "SSL 域名")
        .desc("en", "Specifies the domain for SSL certificate")
        .desc("zh-tw", "指定 SSL 憑證的網域")
        .params(vec![ParameterBuilder::new(0)
            .display_name("en", "Domain")
            .display_name("zh-tw", "域名")
            .type_name("String")
            .is_required(true)
            .default("example.com")
            .desc("en", "Primary domain name for the SSL certificate")
            .desc("zh-tw", "SSL 憑證的主要網域名稱")
            .build()])
        .build(handle_set_ssl_domain),
    CommandBuilder::new("ssl_dns_provider")
        .allowed_parents(vec!["ssl".to_string()])
        .display_name("en", "SSL DNS Provider")
        .display_name("zh-tw", "SSL DNS 供應商")
        .desc("en", "Configures DNS provider for domain validation")
        .desc("zh-tw", "配置用於網域驗證的 DNS 供應商")
        .params(vec![
            ParameterBuilder::new(0)
                .display_name("en", "Provider")
                .display_name("zh-tw", "供應商")
                .type_name("String")
                .is_required(true)
                .default("cloudflare")
                .desc(
                    "en",
                    "Name of the DNS service provider for domain validation"
                )
                .desc("zh-tw", "用於網域驗證的 DNS 服務供應商名稱")
                .build(),
            ParameterBuilder::new(1)
                .display_name("en", "API Token")
                .display_name("zh-tw", "API 令牌")
                .type_name("String")
                .is_required(true)
                .default("token")
                .desc("en", "Authentication token for the specified DNS provider")
                .desc("zh-tw", "指定 DNS 供應商的驗證令牌")
                .build()
        ])
        .build(handle_set_ssl_dns_provider),
    CommandBuilder::new("ssl_dns_instructions_lang")
        .allowed_parents(vec!["ssl".to_string()])
        .display_name("en", "SSL DNS Instructions Language")
        .display_name("zh-tw", "SSL DNS 指示語言")
        .desc("en", "Sets the language for DNS configuration instructions")
        .desc("zh-tw", "設定 DNS 配置指示的語言")
        .params(vec![ParameterBuilder::new(0)
            .display_name("en", "Language")
            .display_name("zh-tw", "語言")
            .type_name("String")
            .is_required(true)
            .default("en")
            .desc("en", "Language code for presenting DNS setup instructions")
            .desc("zh-tw", "用於呈現 DNS 設定說明的語言代碼")
            .build()])
        .build(handle_set_ssl_dns_instructions_lang),
);

fn update_ssl_context<F>(ctx: &mut ConfigContext, update_fn: F)
where
    F: FnOnce(&mut HttpSSLContext),
{
    if let Some(ssl_ctx) = get_mut_ssl_ctx(ctx) {
        update_fn(ssl_ctx);
    }
}

fn get_mut_ssl_ctx(ctx: &ConfigContext) -> Option<&mut HttpSSLContext> {
    if let Some(ssl_ctx_ptr) = &ctx.current_ctx {
        let ssl_ptr = ssl_ctx_ptr.load(Ordering::SeqCst);
        return Some(unsafe { &mut *(ssl_ptr as *mut HttpSSLContext) });
    }
    None
}

pub fn handle_create_ssl(
    ctx: &mut crate::core::config::config_context::ConfigContext,
    config: &Value,
) {
    let enable = get_config_param(config, 0).expect("Missing SSL enable parameter");
    let enable = bool_str_to_bool(&enable).unwrap();
    if !enable {
        return;
    }
    let mut ssl_ctx = Box::new(HttpSSLContext::new());
    ssl_ctx.ssl = true;
    let ssl_raw = Box::into_raw(ssl_ctx) as *mut u8;
    ctx.current_ctx = Some(AtomicPtr::new(ssl_raw));
    ctx.current_block_type_id = Some(std::any::TypeId::of::<HttpSSLContext>());
}

pub fn handle_set_ssl_email(
    ctx: &mut crate::core::config::config_context::ConfigContext,
    config: &Value,
) {
    let email = get_config_param(config, 0).expect("Missing ssl_email parameter");
    update_ssl_context(ctx, |ssl_ctx| {
        ssl_ctx.email = email.to_string();
    });
}

pub fn handle_set_ssl_domain(
    ctx: &mut crate::core::config::config_context::ConfigContext,
    config: &Value,
) {
    let domain = get_config_param(config, 0).expect("Missing ssl_domain parameter");
    update_ssl_context(ctx, |ssl_ctx| {
        ssl_ctx.domain = domain.to_string();
    });
}

pub fn handle_set_ssl_auto_renew(
    ctx: &mut crate::core::config::config_context::ConfigContext,
    config: &Value,
) {
    let enable_str = get_config_param(config, 0).expect("Missing ssl_auto_renew parameter");
    let enable = bool_str_to_bool(&enable_str).unwrap();
    if !enable {
        return;
    }
    update_ssl_context(ctx, |ssl_ctx| {
        ssl_ctx.auto_renew = true;
    });
}

pub fn handle_set_ssl_renew_day(
    ctx: &mut crate::core::config::config_context::ConfigContext,
    config: &Value,
) {
    let days_str = get_config_param(config, 0).expect("Missing ssl_renew_day parameter");
    let days = days_str
        .parse::<u32>()
        .expect("Invalid number for ssl_renew_day");
    update_ssl_context(ctx, |ssl_ctx| {
        ssl_ctx.renew_days = days;
    });
}

pub fn handle_set_ssl_dns_provider(
    ctx: &mut crate::core::config::config_context::ConfigContext,
    config: &Value,
) {
    let provider = get_config_param(config, 0).expect("Missing ssl_dns_provider parameter");
    let api_token =
        get_config_param(config, 1).expect("Missing ssl_dns_provider API token parameter");
    update_ssl_context(ctx, |ssl_ctx| {
        ssl_ctx.dns_provider = DnsProvider::from_str(&provider).unwrap();
        ssl_ctx.dns_provider_api_token = api_token.to_string();
    });
}

pub fn handle_set_ssl_dns_instructions_lang(
    ctx: &mut crate::core::config::config_context::ConfigContext,
    config: &Value,
) {
    let lang = get_config_param(config, 0).expect("Missing ssl_dns_instructions_lang parameter");
    update_ssl_context(ctx, |ssl_ctx| {
        ssl_ctx.dns_instructions_lang = lang.to_string();
    });
}

pub struct HttpSSLContext {
    pub ssl: bool,
    pub email: String,
    pub domain: String,
    pub auto_renew: bool,
    pub renew_days: u32,
    pub dns_provider: DnsProvider,
    pub dns_provider_api_token: String,
    pub dns_instructions_lang: String,
}

impl Default for HttpSSLContext {
    fn default() -> Self {
        Self {
            ssl: false,
            email: String::new(),
            domain: String::new(),
            auto_renew: true,
            renew_days: 30,
            dns_provider: DnsProvider::Default,
            dns_provider_api_token: String::new(),
            dns_instructions_lang: String::new(),
        }
    }
}

impl HttpSSLContext {
    pub fn new() -> Self {
        Self::default()
    }
}

pub struct HttpSSL {
    pub cert_key: KeyPair,
    pub cert: Certificate,
}

impl HttpSSL {
    pub fn new(ctx: &HttpSSLContext) -> Result<Self> {
        if !ctx.ssl {
            return Err(HttpSSLError::SSLNotEnabled);
        }
        if ctx.email.is_empty() {
            return Err(HttpSSLError::EmailEmpty);
        }
        if ctx.domain.is_empty() {
            return Err(HttpSSLError::DomainEmpty);
        }

        let mut account = AccountBuilder::new(&ctx.email).build().unwrap();

        Self::init(&mut account, ctx, false)?;

        let cert_key = account
            .get_cert_key(&ctx.domain)
            .expect("Failed to get certificate key");
        let mut cert = account
            .get_certificate(&ctx.domain)
            .expect("Failed to get certificate");

        if ctx.auto_renew && cert.should_renew(ctx.renew_days)? {
            println!("Renewing SSL certificate for domain: {}", ctx.domain);
            Self::init(&mut account, ctx, true)?;
            cert = account.get_certificate(&ctx.domain).unwrap();
        }

        Ok(Self { cert_key, cert })
    }

    pub fn from_config(ctx: &ConfigContext) -> Result<Self> {
        if let Some(ssl_ctx_ptr) = &ctx.current_ctx {
            let ssl_raw = ssl_ctx_ptr.load(Ordering::SeqCst);
            let ssl_ctx: &HttpSSLContext = unsafe { &*(ssl_raw as *const HttpSSLContext) };
            Self::new(ssl_ctx)
        } else {
            Err(HttpSSLError::SSLNotEnabled)
        }
    }

    fn init(account: &mut Account, ctx: &HttpSSLContext, renew: bool) -> Result<()> {
        let mut order = if renew {
            Order::renew(account, &ctx.domain)?
                .dns_provider(ctx.dns_provider, &ctx.dns_provider_api_token)?
        } else {
            Order::new(account, &ctx.domain)?
                .dns_provider(ctx.dns_provider, &ctx.dns_provider_api_token)?
        };

        let order = match order.validate_challenge(account) {
            Ok(order) => order,
            Err(_) => {
                order.display_challenges(&ctx.dns_instructions_lang);
                order.validation_with_retry(account, Duration::from_secs(3), 20)?
            }
        };

        order.finalize(account)?;
        order.download_certificate(account)?;

        Ok(())
    }
}