]> git.otsuka.systems Git - qotdd/commitdiff
improve systemd service hardening, simplify and refactor logic, add rfc to readme main
authorCameron Otsuka <cameron@otsuka.haus>
Sun, 27 Jul 2025 03:56:31 +0000 (20:56 -0700)
committerCameron Otsuka <cameron@otsuka.haus>
Sun, 27 Jul 2025 03:56:31 +0000 (20:56 -0700)
Cargo.toml
README.md
aur/PKGBUILD
src/main.rs
systemd/qotdd.service

index 9f9ac9c91980152ba38c68d73f34952c89aaac5d..c0ba64bad12234461aa1379878979a114f47f682 100644 (file)
@@ -1,6 +1,6 @@
 [package]
 name = "qotdd"
-version = "0.1.0"
+version = "0.2.0"
 edition = "2024"
 
 [dependencies]
index b601f648bbb20ef1f24af2c516150839efc006b8..6603aa29e52ee2d66d779082e9a5c270ff55a7fb 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
-# qotdd
\ No newline at end of file
+# qotdd
+[RFC 865](https://www.rfc-editor.org/rfc/rfc865) Quote of the Day Daemon
\ No newline at end of file
index 7af6ff378c53e78fb9dcfe23bec6a2d9f036e8c8..b0c17f6a0cbb06eddcff51120468659ff96ba880 100644 (file)
@@ -1,7 +1,7 @@
 # Maintainer: Cameron Otsuka <cameron@otsuka.haus>
 # Contributor: Cameron Otsuka <cameron@otsuka.haus>
 pkgname=qotdd
-pkgver=0.1.0
+pkgver=0.2.0
 pkgrel=1
 pkgdesc='quote of the day daemon'
 url='https://github.com/cotsuka/qotdd'
index 6e271f40fefa6312c250fbefecc35704b6f6c56b..407fda02dcdc3d03e603c2ea65d84d411a2837c0 100644 (file)
@@ -2,7 +2,7 @@ use chrono::prelude::*;
 use rand::prelude::*;
 use std::fs::File;
 use std::io::{BufRead, BufReader, Error, ErrorKind, Result, Write};
-use std::path::{Path, PathBuf};
+use std::path::{PathBuf};
 use std::net::{TcpListener, TcpStream};
 use std::os::fd::{FromRawFd, IntoRawFd};
 use std::sync::{Arc, Mutex};
@@ -14,58 +14,17 @@ struct CachedQuote {
     quote: String,
 }
 
-#[derive(Clone)]
-struct QOTDDService {
-    quotes_file: PathBuf,
-    cached_quote: Arc<Mutex<Option<CachedQuote>>>,
+fn get_date() -> NaiveDate {
+    let now: DateTime<Local> = Local::now();
+    return now.date_naive();
 }
 
-impl QOTDDService {
-    fn new(quotes_file: &str) -> Result<Self> {
-        let path = PathBuf::from(quotes_file.to_string());
-        if !path.exists() {
-            return Err(Error::new(ErrorKind::NotFound, format!("quotes file not found: {:?}", path)));
-        }
-
-        Ok(Self {
-            quotes_file: path,
-            cached_quote: Arc::new(Mutex::new(None)),
-        })
+fn get_quote() -> Result<String> {
+    let path = PathBuf::from("/etc/qotdd/quotes.txt".to_string());
+    if !path.exists() {
+        return Err(Error::new(ErrorKind::NotFound, format!("quotes file not found: {:?}", path)));
     }
-
-    fn get_quote(&self) -> Result<String> {
-        let now: DateTime<Local> = Local::now();
-        let today = now.date_naive();
-
-        // check cache
-        {
-            let cache_guard = self.cached_quote.lock().unwrap();
-            if let Some(ref cached) = *cache_guard {
-                if cached.date == today {
-                    return Ok(cached.quote.clone());
-                }
-            }
-        }
-
-        // cache miss: get a new quote
-        let quote = get_random_line(&self.quotes_file)?;
-        let new_cached_quote = CachedQuote {
-            date: today,
-            quote: quote.clone(),
-        };
-
-        // update cache
-        {
-            let mut cache_guard = self.cached_quote.lock().unwrap();
-            *cache_guard = Some(new_cached_quote);
-        }
-        
-        Ok(quote)
-    }
-}
-
-fn get_random_line(file_path: &Path) -> Result<String> {
-    let file = File::open(file_path)?;
+    let file = File::open(path)?;
     let reader = BufReader::new(file);
     let lines: Vec<String> = reader.lines().filter_map(|line| line.ok()).collect();
     if lines.is_empty() {
@@ -90,20 +49,36 @@ fn get_socket() -> Result<TcpListener> {
     });
 }
 
-fn handle_connection(mut stream: TcpStream, service: &QOTDDService) -> Result<()> {
-    let quote = service.get_quote()?;
+fn handle_connection(mut stream: TcpStream, quote: &String) -> Result<()> {
     stream.write_all(quote.as_bytes())?;
     stream.flush()?;
     Ok(())
 }
 
-fn run(listener: TcpListener, service: QOTDDService) {
+fn main() {
+    let listener = get_socket().unwrap();
+
+    let initial_date = get_date();
+    let initial_quote = get_quote().unwrap();
+
+    let initial_cache = Arc::new(Mutex::new(CachedQuote {
+        date: initial_date,
+        quote: initial_quote
+    }));
+
     for stream in listener.incoming() {
         match stream {
             Ok(stream) => {
-                let cloned_service = service.clone();
+                let cached_quote = Arc::clone(&initial_cache);
                 thread::spawn(move || {
-                    if let Err(e) = handle_connection(stream, &cloned_service) {
+                    let mut cache = cached_quote.lock().unwrap();
+                    let today = get_date();
+                    if cache.date != today {
+                        let new_quote = get_quote().unwrap();
+                        cache.date = today;
+                        cache.quote = new_quote;
+                    }
+                    if let Err(e) = handle_connection(stream, &cache.quote) {
                         eprintln!("error handling tcp connection: {}", e);
                     }
                 });
@@ -111,10 +86,4 @@ fn run(listener: TcpListener, service: QOTDDService) {
             Err(e) => eprintln!("error accepting tcp connection: {}", e),
         }
     }
-}
-
-fn main() {
-    let listener = get_socket().unwrap();
-    let service = QOTDDService::new("/etc/qotdd/quotes.txt").unwrap();
-    run(listener, service);
 }
\ No newline at end of file
index 0b6f85bb3fff445388c744e68377689347f7eed3..0cbc2ce432efb2caa8a479157489d1269674b2ac 100644 (file)
@@ -5,25 +5,38 @@ Wants=network-online.target
 After=network-online.target qotdd.socket
 
 [Service]
-Type=simple
 ExecStart=/usr/bin/qotdd
+Type=simple
 
 # Security hardening
-ProtectHome=yes
-PrivateDevices=yes
-NoNewPrivileges=yes
-MemoryDenyWriteExecute=yes
+CapabilityBoundingSet=~CAP_SYS_TIME CAP_SYS_PACCT CAP_KILL CAP_WAKE_ALARM CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER CAP_LINUX_IMMUTABLE CAP_IPC_LOCK CAP_SYS_MODULE CAP_BPF CAP_SYS_TTY_CONFIG CAP_SYS_BOOT CAP_SYS_CHROOT CAP_BLOCK_SUSPEND CAP_LEASE CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_MAC_ADMIN CAP_MAC_OVERRIDE CAP_SYS_PTRACE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE CAP_SYS_ADMIN CAP_SYS_LOG
+ConfigurationDirectory=qotdd::ro
+DynamicUser=yes
 LockPersonality=yes
-SystemCallFilter=@system-service
-RestrictAddressFamilies=AF_UNIX
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+PrivateDevices=yes
+PrivateNetwork=yes
+PrivateUsers=yes
+ProcSubset=pid
+ProtectClock=yes
+ProtectControlGroups=yes
+ProtectHome=yes
+ProtectKernelLogs=yes
+ProtectKernelModules=yes
+ProtectKernelTunables=yes
 ProtectHostname=true
-RestrictRealtime=yes
-RestrictSUIDSGID=yes
 ProtectProc=invisible
-DynamicUser=yes
-ConfigurationDirectory=qotdd::ro
 Restart=on-failure
 RestartSec=5s
+RestrictAddressFamilies=none
+RestrictNamespaces=~user pid net uts mnt cgroup ipc
+RestrictRealtime=yes
+RestrictSUIDSGID=yes
+SystemCallArchitectures=native
+SystemCallErrorNumber=EPERM
+SystemCallFilter=@system-service
+SystemCallFilter=~@mount @resources @privileged
 
 [Install]
 WantedBy=multi-user.target
\ No newline at end of file