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};
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() {
});
}
-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);
}
});
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
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