crdhcpc 0.1.1

Standalone DHCP Client for Linux with DHCPv4, DHCPv6, PXE, and Dynamic DNS support
Documentation
/// Unix socket server for JSON-RPC communication
///
/// Server used by crrouterd daemon to handle requests from crrouter_web
use super::{
    credentials::PeerCredentials,
    error::{ProtocolError, ProtocolResult},
    framing::MessageFraming,
    jsonrpc::{
        JsonRpcRequest, JsonRpcResponse, JsonRpcErrorResponse, JsonRpcMessage,
        JsonRpcNotification,
    },
};
use async_trait::async_trait;
use serde_json::Value;
use std::path::Path;
use std::sync::Arc;
use tokio::{
    net::{UnixListener, UnixStream},
    sync::Semaphore,
};
use tracing::{debug, error, info, warn};

/// Server configuration
#[derive(Debug, Clone)]
pub struct ServerConfig {
    /// Path to Unix socket
    pub socket_path: String,
    /// Socket file permissions (octal)
    pub socket_mode: u32,
    /// Maximum concurrent connections
    pub max_connections: usize,
    /// Allowed client UIDs (None = any UID)
    pub allowed_uids: Option<Vec<u32>>,
    /// Require root client (overrides allowed_uids)
    pub require_root: bool,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            socket_path: "/var/run/crrouterd.sock".to_string(),
            socket_mode: 0o666,  // Allow all users to read/write (daemon validates credentials)
            max_connections: 100,
            allowed_uids: None,
            require_root: false,
        }
    }
}

impl ServerConfig {
    /// Create configuration from environment variables
    pub fn from_env() -> Self {
        Self {
            socket_path: std::env::var("DAEMON_SOCKET_PATH")
                .unwrap_or_else(|_| "/var/run/crrouterd.sock".to_string()),
            socket_mode: std::env::var("DAEMON_SOCKET_MODE")
                .ok()
                .and_then(|v| u32::from_str_radix(&v, 8).ok())
                .unwrap_or(0o666),
            max_connections: std::env::var("DAEMON_MAX_CONNECTIONS")
                .ok()
                .and_then(|v| v.parse().ok())
                .unwrap_or(100),
            allowed_uids: None,
            require_root: false,
        }
    }
}

/// Connection handler trait
///
/// Implement this trait to handle incoming JSON-RPC requests
#[async_trait]
pub trait ConnectionHandler: Send + Sync {
    /// Handle a JSON-RPC request
    async fn handle_request(
        &self,
        request: JsonRpcRequest,
        credentials: PeerCredentials,
    ) -> ProtocolResult<Value>;

    /// Handle a JSON-RPC notification (optional)
    async fn handle_notification(
        &self,
        notification: JsonRpcNotification,
        credentials: PeerCredentials,
    ) -> ProtocolResult<()> {
        debug!(
            "Received notification: {} from PID {}",
            notification.method, credentials.pid
        );
        Ok(())
    }

    /// Called when a client connects (optional)
    async fn on_connect(&self, credentials: PeerCredentials) -> ProtocolResult<()> {
        info!(
            "Client connected: PID {} (UID {}, GID {})",
            credentials.pid, credentials.uid, credentials.gid
        );
        Ok(())
    }

    /// Called when a client disconnects (optional)
    async fn on_disconnect(&self, credentials: PeerCredentials) {
        info!(
            "Client disconnected: PID {} (UID {}, GID {})",
            credentials.pid, credentials.uid, credentials.gid
        );
    }
}

/// Unix socket server
pub struct UnixServer<H: ConnectionHandler + 'static> {
    config: ServerConfig,
    handler: Arc<H>,
    connection_semaphore: Arc<Semaphore>,
}

impl<H: ConnectionHandler + 'static> UnixServer<H> {
    /// Create a new server with handler
    pub fn new(config: ServerConfig, handler: H) -> Self {
        let semaphore = Arc::new(Semaphore::new(config.max_connections));

        Self {
            config,
            handler: Arc::new(handler),
            connection_semaphore: semaphore,
        }
    }

    /// Start the server and listen for connections
    pub async fn run(self: Arc<Self>) -> ProtocolResult<()> {
        let socket_path = Path::new(&self.config.socket_path);

        // Remove existing socket file if it exists
        if socket_path.exists() {
            std::fs::remove_file(socket_path).map_err(|e| {
                ProtocolError::Internal(format!("Failed to remove existing socket: {}", e))
            })?;
        }

        // Create Unix socket listener
        let listener = UnixListener::bind(socket_path).map_err(|e| {
            ProtocolError::Internal(format!("Failed to bind Unix socket: {}", e))
        })?;

        // Set socket permissions
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let permissions = std::fs::Permissions::from_mode(self.config.socket_mode);
            std::fs::set_permissions(socket_path, permissions).map_err(|e| {
                ProtocolError::Internal(format!("Failed to set socket permissions: {}", e))
            })?;
        }

        info!(
            "Server listening on {} (mode: {:o})",
            self.config.socket_path, self.config.socket_mode
        );

        // Accept connections
        loop {
            match listener.accept().await {
                Ok((stream, _addr)) => {
                    let server = Arc::clone(&self);
                    tokio::spawn(async move {
                        if let Err(e) = server.handle_connection(stream).await {
                            error!("Connection error: {}", e);
                        }
                    });
                }
                Err(e) => {
                    error!("Failed to accept connection: {}", e);
                }
            }
        }
    }

    /// Handle a single connection
    async fn handle_connection(&self, stream: UnixStream) -> ProtocolResult<()> {
        // Acquire connection slot
        let _permit = self
            .connection_semaphore
            .acquire()
            .await
            .map_err(|e| ProtocolError::Internal(format!("Semaphore error: {}", e)))?;

        // Get peer credentials
        let credentials = PeerCredentials::from_socket(&stream)?;

        // Verify credentials
        self.verify_credentials(&stream, &credentials)?;

        // Notify handler of connection
        if let Err(e) = self.handler.on_connect(credentials).await {
            warn!("Connection rejected by handler: {}", e);
            return Err(e);
        }

        // Handle messages
        let result = self.handle_messages(stream, credentials).await;

        // Notify handler of disconnection
        self.handler.on_disconnect(credentials).await;

        result
    }

    /// Verify peer credentials against configuration
    fn verify_credentials<F: std::os::fd::AsFd>(
        &self,
        _socket: &F,
        credentials: &PeerCredentials,
    ) -> ProtocolResult<()> {
        // Check if root is required
        if self.config.require_root && !credentials.is_root() {
            return Err(ProtocolError::PermissionDenied(format!(
                "Root required (peer UID: {})",
                credentials.uid
            )));
        }

        // Check allowed UIDs
        if let Some(ref allowed) = self.config.allowed_uids {
            if !allowed.contains(&credentials.uid) {
                return Err(ProtocolError::PermissionDenied(format!(
                    "UID {} not in allowed list",
                    credentials.uid
                )));
            }
        }

        Ok(())
    }

    /// Handle messages from a connection
    async fn handle_messages(
        &self,
        mut stream: UnixStream,
        credentials: PeerCredentials,
    ) -> ProtocolResult<()> {
        loop {
            // Receive message
            let message: JsonRpcMessage = match MessageFraming::recv_json(&mut stream).await {
                Ok(msg) => msg,
                Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
                    // Connection closed
                    debug!("Connection closed by peer");
                    break;
                }
                Err(e) => {
                    return Err(ProtocolError::Io(e));
                }
            };

            // Process message
            match message {
                JsonRpcMessage::Request(request) => {
                    let response = self.process_request(request, credentials).await;
                    MessageFraming::send_json(&mut stream, &response)
                        .await
                        .map_err(ProtocolError::Io)?;
                }
                JsonRpcMessage::Notification(notification) => {
                    if let Err(e) = self.handler.handle_notification(notification, credentials).await {
                        warn!("Notification handler error: {}", e);
                    }
                }
                _ => {
                    warn!("Unexpected message type from client");
                }
            }
        }

        Ok(())
    }

    /// Process a JSON-RPC request
    async fn process_request(
        &self,
        request: JsonRpcRequest,
        credentials: PeerCredentials,
    ) -> JsonRpcMessage {
        debug!("Processing request: {}", request.method);

        match self.handler.handle_request(request.clone(), credentials).await {
            Ok(result) => {
                JsonRpcMessage::Response(JsonRpcResponse::new(result, request.id))
            }
            Err(e) => {
                error!("Request handler error: {}", e);
                JsonRpcMessage::ErrorResponse(JsonRpcErrorResponse::new(
                    e.to_jsonrpc_error(),
                    request.id,
                ))
            }
        }
    }
}

/// Example handler implementation for testing
#[cfg(test)]
pub struct EchoHandler;

#[cfg(test)]
#[async_trait]
impl ConnectionHandler for EchoHandler {
    async fn handle_request(
        &self,
        request: JsonRpcRequest,
        _credentials: PeerCredentials,
    ) -> ProtocolResult<Value> {
        // Echo back the params
        Ok(request.params.unwrap_or(Value::Null))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_server_config_default() {
        let config = ServerConfig::default();
        assert_eq!(config.socket_path, "/var/run/crrouterd.sock");
        assert_eq!(config.socket_mode, 0o666);
        assert_eq!(config.max_connections, 100);
    }

    #[tokio::test]
    async fn test_server_creation() {
        let config = ServerConfig {
            socket_path: "/tmp/test-server.sock".to_string(),
            ..Default::default()
        };

        let handler = EchoHandler;
        let server = UnixServer::new(config, handler);

        assert_eq!(server.config.socket_path, "/tmp/test-server.sock");
    }
}