rustpbx 0.4.9

A SIP PBX implementation in Rust
Documentation
use super::user::UserBackend;
use crate::call::user::SipUser;
use crate::proxy::auth::AuthError;
use anyhow::Result;
use async_trait::async_trait;
use std::io::BufRead;
use std::sync::{Arc, Mutex};
use std::{collections::HashMap, fs::File, io, path::Path};
use tracing::{info, warn};

pub struct PlainTextBackend {
    users: Arc<Mutex<HashMap<String, SipUser>>>,
    path: String,
}

impl PlainTextBackend {
    pub fn new(path: &str) -> Self {
        Self {
            users: Arc::new(Mutex::new(HashMap::new())),
            path: path.to_owned(),
        }
    }

    pub async fn load(&self) -> Result<()> {
        let path = Path::new(&self.path);
        let file = match File::open(path) {
            Ok(file) => file,
            Err(e) => {
                warn!(
                    "Files not found, creating empty file: {} {}",
                    e,
                    path.display()
                );
                return Err(e.into());
            }
        };
        let reader = io::BufReader::new(file);
        let mut users = self.users.lock().unwrap();
        users.clear();

        for line in reader.lines() {
            let line = line?;
            let line = line.trim();
            if line.is_empty() || line.starts_with('#') {
                continue;
            }

            let parts: Vec<&str> = line.splitn(2, ':').collect();
            if parts.len() != 2 {
                continue;
            }

            let username = parts[0].trim();
            let password = parts[1].trim();
            let user = SipUser {
                id: 0,
                username: username.to_string(),
                password: Some(password.to_string()),
                enabled: true,
                realm: None,
                origin_contact: None,
                contact: None,
                from: None,
                destination: None,
                is_support_webrtc: false,
                call_forwarding_mode: None,
                call_forwarding_destination: None,
                call_forwarding_timeout: None,
                departments: None,
                display_name: None,
                email: None,
                phone: None,
                note: None,
                allow_guest_calls: false,
                voicemail_disabled: false,
            };
            users.insert(username.to_string(), user);
        }
        info!("Loaded {} users from {}", users.len(), self.path);
        Ok(())
    }
}

#[async_trait]
impl UserBackend for PlainTextBackend {
    async fn is_same_realm(&self, realm: &str) -> bool {
        return realm.is_empty();
    }
    async fn get_user(
        &self,
        username: &str,
        realm: Option<&str>,
        _request: Option<&rsipstack::sip::Request>,
    ) -> Result<Option<SipUser>, AuthError> {
        let mut user = match self.users.lock().unwrap().get(username) {
            Some(user) => user.clone(),
            None => return Ok(None),
        };
        if !user.enabled {
            return Err(AuthError::Disabled);
        }
        user.realm = realm.map(|r| r.to_string());
        Ok(Some(user))
    }
}