#![warn(clippy::all, clippy::pedantic)]
#![doc = include_str!("../README.md")]
pub mod config;
pub mod error;
pub mod log;
pub mod request;
pub mod response;
pub mod threadpool;
use {
getopts::{Fail, Matches, Options},
log::{Log, LogError},
once_cell::sync::Lazy,
response::Response,
std::{
env,
ffi::CString,
fs::{self, File},
io::{self, BufWriter, Write},
net::TcpStream,
os::unix::prelude::OsStrExt,
process,
},
};
pub use {config::Config, request::Request, threadpool::ThreadPool};
pub static CONFIG: Lazy<Config> = Lazy::new(|| match Config::load() {
Ok(c) => c,
Err(e) => {
eprintln!("Unable to load config: {e}");
process::exit(1);
}
});
pub unsafe fn privdrop(user: *mut libc::passwd, group: *mut libc::group) -> io::Result<()> {
if libc::setgid((*group).gr_gid) != 0 {
eprintln!("privdrop: Unable to setgid of group: {}", &CONFIG.group);
return Err(std::io::Error::last_os_error());
}
if libc::setuid((*user).pw_uid) != 0 {
eprintln!("privdrop: Unable to setuid of user: {}", &CONFIG.user);
return Err(std::io::Error::last_os_error());
}
Ok(())
}
pub unsafe fn init_logs(user: libc::uid_t, group: libc::gid_t) -> Result<(), io::Error> {
if let Some(log) = CONFIG.access_log.as_ref() {
if let Some(parent) = log.parent() {
if !parent.exists() {
println!("Creating log directory");
fs::create_dir_all(parent)?;
}
}
if !log.exists() {
println!("Creating access log");
{
File::create(log)?;
}
let logstr = CString::new(log.clone().as_os_str().as_bytes())?;
println!("Setting access log permissions");
_ = libc::chown(logstr.as_ptr(), user, group);
}
}
if let Some(log) = CONFIG.error_log.as_ref() {
if let Some(parent) = log.parent() {
if !parent.exists() {
println!("Creating log directory");
fs::create_dir_all(parent)?;
}
}
if !log.exists() {
println!("Creating error log");
{
File::create(log)?;
}
let logstr = CString::new(log.clone().as_os_str().as_bytes())?;
println!("Setting error log permissions");
_ = libc::chown(logstr.as_ptr(), user, group);
}
}
Ok(())
}
pub fn handle_connection(mut stream: TcpStream) -> Result<(), io::Error> {
let (request, response) = match Request::try_from(&stream) {
Ok(request) => (request.to_string(), Response::from(request)),
Err(e) => (String::from("Malformed request"), e.into()),
};
let msg = response.to_string();
match response {
Response::Success {
mimetype: _,
body: _,
}
| Response::Redirect(_) => {
request.log()?;
msg.log()?;
}
Response::ClientError(_) | Response::ServerError(_) => {
request.log_err()?;
msg.log_err()?;
}
}
let mut writer = BufWriter::new(&mut stream);
writer.write_all(&Vec::from(response))?;
Ok(())
}
pub fn options() -> Result<Matches, Fail> {
let args: Vec<String> = env::args().collect();
let mut opts = Options::new();
opts.optopt("c", "config", "Use NAME as config file", "NAME");
opts.optflag("h", "help", "Print this help menu");
opts.parse(&args[1..])
}
pub fn usage() {
let ustr = "_PROGNAME_ _VERSION_\n\
The JeanGnie <jeang3nie@hitchhiker-linux.org>\n\
A Spartan protocol server\n\
\n\
USAGE:\n \
_PROGNAME_ <OPTIONS>\n\
\n\
OPTIONS:\n \
-h, --help\n \
Print help information\n\
\n\
-c, --config <config>\n \
Usae <config> as the config file";
let ustr = ustr
.replace("_PROGNAME_", env!("CARGO_PKG_NAME"))
.replace("_VERSION_", env!("CARGO_PKG_VERSION"));
println!("{ustr}");
}