Working LUA base FastCGI server

This commit is contained in:
DanyLE 2023-01-17 00:59:57 +01:00
parent 4e122bd245
commit 88ec016405
5 changed files with 402 additions and 282 deletions

10
Cargo.lock generated
View File

@ -70,15 +70,6 @@ dependencies = [
"vec_map", "vec_map",
] ]
[[package]]
name = "fastcgi"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4159a0f48bea0281602e508eb070d7d7ba1f6ac2480f9db1a60a39274aea1cc"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@ -108,7 +99,6 @@ name = "luad"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"fastcgi",
"libc", "libc",
"mlua", "mlua",
"nix", "nix",

View File

@ -6,7 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
fastcgi = "1.0.0"
mlua = { version = "0.8", features = ["lua54", "vendored"] } mlua = { version = "0.8", features = ["lua54", "vendored"] }
clap = "2.33" clap = "2.33"
nix = "0.26.1" nix = "0.26.1"
@ -19,6 +18,5 @@ libc = "0.2"
opt-level = 's' opt-level = 's'
# 's' for size # 's' for size
lto = true lto = true
# this strategy doesnot work on 1.3.9
# panic = 'abort' # panic = 'abort'
codegen-units = 1 codegen-units = 1

View File

@ -1,10 +1,12 @@
# TCP socket or Unix socket file # TCP socket or Unix socket file
socket = "unix:/tmp/lua.sock" socket = "unix:/tmp/lua1.sock"
# pid file # pid file
pidfile = "/tmp/luad.pid" pidfile = "/tmp/luad.pid"
# user name # user name
# user = "demo" user = "dany"
# group name # group name
# group = "demo" group = "dany"
debug = false

View File

@ -1,12 +1,12 @@
use mlua::prelude::*;
use libc; use libc;
use mlua::prelude::*;
use nix; use nix;
use std::io::{Error, ErrorKind, Read,Write}; use std::collections::HashMap;
use std::ffi::CString; use std::ffi::CString;
use std::fmt::Arguments; use std::fmt::Arguments;
use std::io::{Error, ErrorKind, Read, Write};
use std::os::fd::RawFd; use std::os::fd::RawFd;
use std::os::unix::io::{AsRawFd}; use std::os::unix::io::AsRawFd;
use std::collections::HashMap;
/// app author /// app author
pub const APP_AUTHOR: &str = "Dany LE <mrsang@iohub.dev>"; pub const APP_AUTHOR: &str = "Dany LE <mrsang@iohub.dev>";
@ -17,6 +17,14 @@ pub const APP_VERSION: &str = "0.1.0";
/// Application name /// Application name
pub const DAEMON_NAME: &str = "luad"; pub const DAEMON_NAME: &str = "luad";
fn is_debug_enable() -> bool {
match std::env::var("debug") {
Ok(value) => return value == "1" || value == "true",
Err(_) => {}
}
false
}
/// Drop user privileges /// Drop user privileges
/// ///
/// This function drop the privileges of the current user /// This function drop the privileges of the current user
@ -32,14 +40,14 @@ pub const DAEMON_NAME: &str = "luad";
/// ///
/// * `nix::Error` - The error from the nix package /// * `nix::Error` - The error from the nix package
pub fn privdrop(useropt: Option<&str>, groupopt: Option<&str>) -> Result<(), nix::Error> { pub fn privdrop(useropt: Option<&str>, groupopt: Option<&str>) -> Result<(), nix::Error> {
match groupopt{ match groupopt {
Some(group) => { Some(group) => {
INFO!("Dropping current process group to {}", group); INFO!("Dropping current process group to {}", group);
match nix::unistd::Group::from_name(group)? { match nix::unistd::Group::from_name(group)? {
Some(group) => nix::unistd::setgid(group.gid), Some(group) => nix::unistd::setgid(group.gid),
None => Err(nix::Error::last()), None => Err(nix::Error::last()),
}?; }?;
}, }
None => {} None => {}
} }
match useropt { match useropt {
@ -49,13 +57,25 @@ pub fn privdrop(useropt: Option<&str>, groupopt: Option<&str>) -> Result<(), nix
Some(user) => nix::unistd::setuid(user.uid), Some(user) => nix::unistd::setuid(user.uid),
None => Err(nix::Error::last()), None => Err(nix::Error::last()),
}? }?
}, }
None => {} None => {}
} }
Ok(()) Ok(())
} }
pub fn is_unix_socket(fd: libc::c_int) -> Result<bool, Error> {
unsafe {
let mut addr: libc::sockaddr_storage = std::mem::zeroed();
let mut addr_len: libc::socklen_t =
std::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
let ret = libc::getsockname(fd, &mut addr as *mut _ as *mut _, &mut addr_len);
if ret != 0 {
return Err(ERR!(format!("Unable to check socket: {}", fd)));
}
Ok(i32::from(addr.ss_family) == libc::AF_UNIX)
}
}
/// Utility function to catch common signal that /// Utility function to catch common signal that
/// cause the program to exit /// cause the program to exit
/// ///
@ -162,9 +182,19 @@ impl LOG {
/// ///
/// # Errors /// # Errors
/// ///
/// * `std error` - All errors related to formated and C string manipulation /// * `std error` - All errors related to formatted and C string manipulation
pub fn log(prefix: &str, level: &LogLevel, args: Arguments<'_>) -> Result<(), Error> { pub fn log(prefix: &str, level: &LogLevel, args: Arguments<'_>) -> Result<(), Error> {
use std::fmt::Write; use std::fmt::Write;
let sysloglevel = match level {
LogLevel::ERROR => libc::LOG_ERR,
LogLevel::WARN => libc::LOG_WARNING,
_ => {
if !is_debug_enable() {
return Ok(());
}
libc::LOG_NOTICE
}
};
let mut output = String::new(); let mut output = String::new();
if output.write_fmt(args).is_err() { if output.write_fmt(args).is_err() {
return Err(ERR!("Unable to create format string from arguments")); return Err(ERR!("Unable to create format string from arguments"));
@ -172,11 +202,6 @@ impl LOG {
let log_fmt = format!("{}(v{}){}%s\n", DAEMON_NAME, APP_VERSION, prefix); let log_fmt = format!("{}(v{}){}%s\n", DAEMON_NAME, APP_VERSION, prefix);
let fmt = CString::new(log_fmt.as_bytes())?; let fmt = CString::new(log_fmt.as_bytes())?;
let c_msg = CString::new(output.as_bytes())?; let c_msg = CString::new(output.as_bytes())?;
let sysloglevel = match level {
LogLevel::ERROR => libc::LOG_ERR,
LogLevel::WARN => libc::LOG_WARNING,
_ => libc::LOG_NOTICE,
};
unsafe { unsafe {
libc::syslog(sysloglevel, fmt.as_ptr(), c_msg.as_ptr()); libc::syslog(sysloglevel, fmt.as_ptr(), c_msg.as_ptr());
} }
@ -195,10 +220,9 @@ impl Drop for LOG {
} }
} }
/// Protocol goes here /// Protocol goes here
#[derive(Debug)] #[derive(Debug)]
enum FCGIHeaderType{ enum FCGIHeaderType {
BeginRequest, BeginRequest,
AbortRequest, AbortRequest,
EndRequest, EndRequest,
@ -213,7 +237,6 @@ enum FCGIHeaderType{
} }
impl FCGIHeaderType { impl FCGIHeaderType {
/// convert a u8 value to `FCGIHeaderType` value /// convert a u8 value to `FCGIHeaderType` value
/// ///
/// # Arguments /// # Arguments
@ -275,7 +298,7 @@ impl std::fmt::Display for FCGIHeaderType {
} }
} }
enum EndRequestStatus{ enum EndRequestStatus {
Complete, Complete,
// CantMaxMPXConn, // CantMaxMPXConn,
// Overloaded, // Overloaded,
@ -298,20 +321,18 @@ impl EndRequestStatus {
} }
} }
const FCGI_HEADER_LEN:usize = 8; const FCGI_HEADER_LEN: usize = 8;
const FCGI_VERSION:u8 = 1; const FCGI_VERSION: u8 = 1;
#[derive(Debug)] #[derive(Debug, PartialEq)]
#[derive(PartialEq)]
enum FCGIRole { enum FCGIRole {
Responder, Responder,
Authorizer, Authorizer,
Filter, Filter,
Unknown Unknown,
} }
impl FCGIRole { impl FCGIRole {
/// convert a u8 value to `FCGIRole` value /// convert a u8 value to `FCGIRole` value
/// ///
/// # Arguments /// # Arguments
@ -339,14 +360,13 @@ impl std::fmt::Display for FCGIRole {
} }
} }
#[derive(Debug)] #[derive(Debug)]
struct FCGIBeginRequestBody{ struct FCGIBeginRequestBody {
role: FCGIRole, role: FCGIRole,
flags: u8, flags: u8,
} }
impl FCGIBeginRequestBody { impl FCGIBeginRequestBody {
pub fn from_bytes(data: &[u8]) -> Self pub fn from_bytes(data: &[u8]) -> Self {
{
Self { Self {
role: FCGIRole::from_u16(((data[0] as u16) << 8) | (data[1] as u16)), role: FCGIRole::from_u16(((data[0] as u16) << 8) | (data[1] as u16)),
flags: data[2], flags: data[2],
@ -361,30 +381,27 @@ impl std::fmt::Display for FCGIBeginRequestBody {
} }
} }
#[derive(Debug)] #[derive(Debug)]
struct FcgiHeader { struct FcgiHeader {
version: u8, version: u8,
kind: FCGIHeaderType, kind: FCGIHeaderType,
id: u16, id: u16,
padding: u8, padding: u8,
length:u16, length: u16,
} }
impl FcgiHeader { impl FcgiHeader {
pub fn from_bytes(data: &[u8]) -> Self pub fn from_bytes(data: &[u8]) -> Self {
{
Self { Self {
version: data[0], version: data[0],
kind: FCGIHeaderType::from_u8(data[1]), kind: FCGIHeaderType::from_u8(data[1]),
id: ((data[2] as u16) << 8) | (data[3] as u16), id: ((data[2] as u16) << 8) | (data[3] as u16),
length: ((data[4] as u16) << 8) | (data[5]as u16), length: ((data[4] as u16) << 8) | (data[5] as u16),
padding: data[6] padding: data[6],
} }
} }
pub fn as_bytes(&self) -> Vec<u8> pub fn as_bytes(&self) -> Vec<u8> {
{
vec![ vec![
self.version, self.version,
self.kind.as_u8(), self.kind.as_u8(),
@ -393,7 +410,7 @@ impl FcgiHeader {
(self.length >> 8) as u8, (self.length >> 8) as u8,
(self.length & 0xFF) as u8, (self.length & 0xFF) as u8,
self.padding, self.padding,
0 0,
] ]
} }
} }
@ -408,12 +425,11 @@ impl std::fmt::Display for FcgiHeader {
} }
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
#[derive(PartialEq)]
enum FCGIRequestState { enum FCGIRequestState {
WaitForParams, WaitForParams,
WaitForStdin, WaitForStdin,
WaitForStdout WaitForStdout,
} }
impl std::fmt::Display for FCGIRequestState { impl std::fmt::Display for FCGIRequestState {
@ -431,32 +447,71 @@ struct FGCIRequest {
params: HashMap<String, String>, params: HashMap<String, String>,
id: u16, id: u16,
fd: RawFd, fd: RawFd,
data: Vec<u8>, data: Option<Vec<u8>>,
state: FCGIRequestState, state: FCGIRequestState,
} }
struct FCGIStdoutStream { struct FCGIOStream {
fd: RawFd, fd: RawFd,
id: u16 id: u16,
} }
impl FCGIStdoutStream { impl FCGIOStream {
fn _write(&self, buf: &[u8]) -> std::io::Result<usize> fn write_record(&mut self, buf: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {
{ fcgi_send_stdout(self, self.id, Some(buf))?;
let ret = unsafe { libc::write(self.fd, buf.as_ptr() as *const libc::c_void, buf.len()) }; Ok(())
if ret != buf.len() as isize }
{ fn write_variadic(
let msg = format!("Unable to write data to {}: only {} out of {} bytes have been written", self.fd, ret, buf.len()); &mut self,
return Err(ERR!(msg)); values: mlua::Variadic<LuaValue>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut output: Vec<u8> = Vec::new();
for value in values {
match value {
LuaNil => {}
LuaValue::Boolean(v) => output.extend(v.to_string().as_bytes()),
LuaValue::Integer(v) => output.extend(v.to_string().as_bytes()),
LuaValue::Number(v) => output.extend(v.to_string().as_bytes()),
LuaValue::String(v) => output.extend(v.as_bytes()),
LuaValue::LightUserData(_)
| LuaValue::Table(_)
| LuaValue::Function(_)
| LuaValue::Thread(_) => {
return Err(Box::new(ERR!("Unsupported data type")));
}
LuaValue::UserData(v) => {
if !v.is::<LuabyteArray>() {
return Err(Box::new(ERR!("Unsupported data type")));
}
let arr = v.borrow::<LuabyteArray>()?;
output.extend(arr.0.clone());
}
LuaValue::Error(e) => {
fcgi_send_stderr(self, self.id, Some(e.to_string().into()))?;
}
}
} }
Ok(ret as usize) if output.len() > 0 {
self.write_record(output)?;
}
Ok(())
} }
} }
impl Write for FCGIStdoutStream impl Write for FCGIOStream {
{
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self._write(buf) let ret = unsafe { libc::write(self.fd, buf.as_ptr() as *const libc::c_void, buf.len()) };
if ret != buf.len() as isize {
let msg = format!(
"Unable to write data to {}: only {} out of {} bytes have been written",
self.fd,
ret,
buf.len()
);
return Err(ERR!(msg));
}
Ok(ret as usize)
} }
fn flush(&mut self) -> std::io::Result<()> { fn flush(&mut self) -> std::io::Result<()> {
@ -464,120 +519,104 @@ impl Write for FCGIStdoutStream
} }
} }
impl mlua::UserData for FCGIStdoutStream { impl mlua::UserData for FCGIOStream {
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("write", |_, this:& FCGIStdoutStream, strings:mlua::Variadic<String>| { methods.add_method_mut(
let mut stream = FCGIStdoutStream "echo",
{ |_, this: &mut FCGIOStream, values: mlua::Variadic<_>| {
fd: this.fd, this.write_variadic(values)
id: this.id .map_err(|e| mlua::Error::external(ERR!(e.to_string())))
}; },
let mut output = String::new(); );
for string in strings{ methods.add_method_mut(
output.push_str(&string); "print",
} |_, this: &mut FCGIOStream, values: mlua::Variadic<_>| {
fcgi_send_stdout(&mut stream, this.id, Some(output.as_bytes().to_vec())) this.write_variadic(values)
.map_err(|e| mlua::Error::external(ERR!(e.to_string())))?; .map_err(|e| mlua::Error::external(ERR!(e.to_string())))
Ok(()) },
}); );
methods.add_method("raw_fd", |_, this: &FCGIOStream, ()| Ok(this.fd));
methods.add_method("id", |_, this: &FCGIOStream, ()| Ok(this.id));
} }
} }
fn lua_define_print(lua: &Lua, strings: mlua::Variadic<String>) -> LuaResult<()> { fn fcgi_execute_request_handle(rq: &mut FGCIRequest) -> Result<(), Box<dyn std::error::Error>> {
let global = lua.globals();
let server = global.get::<_, mlua::Table>("_SERVER")?;
let fd = server.get::<_,RawFd>("FD")?;
let id: u16 = server.get::<_,u16>("ID")?;
if fd <= 0 {
return Err(mlua::Error::external(ERR!("Invalid file descriptor")));
}
let mut data = String::new();
for string in strings{
data.push_str(&string);
}
// make the request
let mut stream = FCGIStdoutStream { fd, id};
let body = data.as_bytes().to_vec();
fcgi_send_stdout(&mut stream, id, Some(body))
.map_err(|e| mlua::Error::external(ERR!(e.to_string())))?;
Ok(())
}
//fn lua_define_io_write(lua: &Lua, strings: String) -> LuaResult<()> {
//}
fn fcgi_execute_request_handle(rq: & FGCIRequest) -> Result<(), Box<dyn std::error::Error>>
{
let lua = mlua::Lua::new(); let lua = mlua::Lua::new();
let global = lua.globals(); let global = lua.globals();
let request = lua.create_table()?; let request = lua.create_table()?;
request.set("ID", rq.id)?;
request.set("FD", rq.fd)?; for (k, v) in &rq.params {
for (k,v) in &rq.params{ request.set(String::from(k), String::from(v))?;
request.set(String::from(k),String::from(v))?; }
if let Some(data) = rq.data.take() {
let data_arr = LuabyteArray(data);
request.set("RAW_DATA", data_arr)?;
} }
// request params stored in _SERVER table // request params stored in _SERVER table
global.set("_SERVER", request)?; global.set("_SERVER", request)?;
// replace the print function
let func = lua.create_function(lua_define_print)?;
global.set("print", func)?;
// replace the io.stdout
let stdout = FCGIStdoutStream {
fd: rq.fd,
id: rq.id
};
let io = global.get::<_, mlua::Table>("io")?;
io.set("stdout", stdout)?;
let func = lua.create_function(lua_define_print)?;
io.set("write",func)?;
let path = rq.params.get("SCRIPT_FILENAME").ok_or(ERR!("No SCRIPT_FILENAME found"))?; // put the fcgio object
let fcgio = FCGIOStream {
fd: rq.fd,
id: rq.id,
};
global.set("fcgio", fcgio)?;
// support for byte array
let bytes = lua.create_table()?;
bytes.set(
"from_string",
lua.create_function(lua_new_bytes_from_string)?,
)?;
bytes.set("new", lua.create_function(lua_new_bytes)?)?;
global.set("bytes", bytes)?;
let path = rq
.params
.get("SCRIPT_FILENAME")
.ok_or(ERR!("No SCRIPT_FILENAME found"))?;
let source = std::fs::read_to_string(path)?; let source = std::fs::read_to_string(path)?;
INFO!("source: {}", &source);
lua.load(&source).exec()?; lua.load(&source).exec()?;
Ok(()) Ok(())
//global.set("hello", func)?;
//let source = std::fs::read_to_string(script).unwrap();
//lua.load(&source).exec().unwrap();
//lua.load("hello('world')").exec()?;
} }
fn fcgi_send_stderr<T: Read + Write + AsRawFd >(stream: &mut T,id: u16, eopt: Option<Box<dyn std::error::Error>>) -> Result<(), Box<dyn std::error::Error>> fn fcgi_send_stderr<T: Write>(
{ stream: &mut T,
id: u16,
eopt: Option<Box<dyn std::error::Error>>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut header = FcgiHeader { let mut header = FcgiHeader {
version: FCGI_VERSION, version: FCGI_VERSION,
kind: FCGIHeaderType::Stderr, kind: FCGIHeaderType::Stderr,
id: id, id: id,
length: 0, length: 0,
padding: 0 , padding: 0,
}; };
if let Some(error) = eopt{ if let Some(error) = eopt {
let err_str = error.to_string(); let err_str = error.to_string();
let str_len = err_str.len(); let str_len = err_str.len();
let mut padding = (8 - str_len % 8) as u8; let mut padding = (8 - str_len % 8) as u8;
if padding == 8 if padding == 8 {
{
padding = 0; padding = 0;
} }
let mut body = err_str.as_bytes().to_vec(); let mut body = err_str.as_bytes().to_vec();
let pad = vec![0;padding as usize]; let pad = vec![0; padding as usize];
header.length = str_len as u16; header.length = str_len as u16;
header.padding = padding; header.padding = padding;
body.extend(pad); body.extend(pad);
stream.write_all(&header.as_bytes())?; stream.write_all(&header.as_bytes())?;
stream.write_all(&body)?; stream.write_all(&body)?;
} } else {
else
{
stream.write_all(&header.as_bytes())?; stream.write_all(&header.as_bytes())?;
} }
Ok(()) Ok(())
} }
fn fcgi_send_stdout<T: Write >(stream: &mut T,id: u16, dopt: Option<Vec<u8>>) -> Result<(), Box<dyn std::error::Error>> fn fcgi_send_stdout<T: Write>(
{ stream: &mut T,
id: u16,
dopt: Option<Vec<u8>>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut header = FcgiHeader { let mut header = FcgiHeader {
version: FCGI_VERSION, version: FCGI_VERSION,
kind: FCGIHeaderType::Stdout, kind: FCGIHeaderType::Stdout,
@ -585,115 +624,106 @@ fn fcgi_send_stdout<T: Write >(stream: &mut T,id: u16, dopt: Option<Vec<u8>>) ->
length: 0, length: 0,
padding: 0, padding: 0,
}; };
if let Some(data) = dopt{ if let Some(data) = dopt {
header.length = data.len() as u16; header.length = data.len() as u16;
header.padding = (8 - header.length % 8) as u8; header.padding = (8 - header.length % 8) as u8;
if header.padding == 8 if header.padding == 8 {
{
header.padding = 0; header.padding = 0;
} }
let mut body = data; let mut body = data;
let pad = vec![0;header.padding as usize]; let pad = vec![0; header.padding as usize];
body.extend(pad); body.extend(pad);
stream.write_all(&header.as_bytes())?; stream.write_all(&header.as_bytes())?;
stream.write_all(&body)?; stream.write_all(&body)?;
} } else {
else
{
stream.write_all(&header.as_bytes())?; stream.write_all(&header.as_bytes())?;
} }
Ok(()) Ok(())
} }
fn fcgi_send_end_request<T: Read + Write + AsRawFd >(stream: &mut T,id:u16, status: EndRequestStatus) -> Result<(), Box<dyn std::error::Error>> fn fcgi_send_end_request<T: Read + Write + AsRawFd>(
{ stream: &mut T,
id: u16,
status: EndRequestStatus,
) -> Result<(), Box<dyn std::error::Error>> {
let header = FcgiHeader { let header = FcgiHeader {
version: FCGI_VERSION, version: FCGI_VERSION,
kind: FCGIHeaderType::EndRequest, kind: FCGIHeaderType::EndRequest,
id: id, id: id,
length: 8, length: 8,
padding: 0 , padding: 0,
}; };
let body = vec![ let body = vec![0, 0, 0, 0, status.as_u8(), 0, 0, 0];
0,0,0,0,
status.as_u8(),
0,0,0
];
stream.write_all(&header.as_bytes())?; stream.write_all(&header.as_bytes())?;
stream.write_all(&body)?; stream.write_all(&body)?;
Ok(()) Ok(())
} }
pub fn process_request<T: Read + Write + AsRawFd >(stream:&mut T)-> Result<(), Box<dyn std::error::Error>>{ pub fn process_request<T: Read + Write + AsRawFd>(
stream: &mut T,
) -> Result<(), Box<dyn std::error::Error>> {
let mut requests: HashMap<u16, FGCIRequest> = HashMap::new(); let mut requests: HashMap<u16, FGCIRequest> = HashMap::new();
loop{ loop {
let header = fcgi_read_header(stream)?; let header = fcgi_read_header(stream)?;
match header.kind { match header.kind {
FCGIHeaderType::BeginRequest => { FCGIHeaderType::BeginRequest => {
let body = FCGIBeginRequestBody::from_bytes(&fcgi_read_body(stream, &header)?); let body = FCGIBeginRequestBody::from_bytes(&fcgi_read_body(stream, &header)?);
INFO!("Begin Request: {:?}, with body {:?}", header, body); INFO!("Begin Request: {:?}, with body {:?}", header, body);
if body.role != FCGIRole::Responder if body.role != FCGIRole::Responder {
{
fcgi_send_end_request(stream, header.id, EndRequestStatus::UnknownRole)?; fcgi_send_end_request(stream, header.id, EndRequestStatus::UnknownRole)?;
return Err(Box::new(ERR!("Only Responder role is supported"))); return Err(Box::new(ERR!("Only Responder role is supported")));
} }
// check if we have already request of this kind // check if we have already request of this kind
if let Some(_) = requests.get(&header.id) if let Some(_) = requests.get(&header.id) {
{
WARN!("Request {} already exists, ignore this message", header.id); WARN!("Request {} already exists, ignore this message", header.id);
} } else {
else let rq: FGCIRequest = FGCIRequest {
{
let rq:FGCIRequest = FGCIRequest {
id: header.id, id: header.id,
params: HashMap::new(), params: HashMap::new(),
data: vec![0;0], data: None,
state: FCGIRequestState::WaitForParams, state: FCGIRequestState::WaitForParams,
fd: stream.as_raw_fd(), fd: stream.as_raw_fd(),
}; };
requests.insert(header.id, rq); requests.insert(header.id, rq);
} }
}, }
FCGIHeaderType::Params => { FCGIHeaderType::Params => {
if let Some(rq) = requests.get_mut(&header.id) if let Some(rq) = requests.get_mut(&header.id) {
{ if rq.state != FCGIRequestState::WaitForParams {
if rq.state != FCGIRequestState::WaitForParams WARN!(
{ "Should not receive a param record as the request is in {} state",
WARN!("Should not receive a param record as the request is in {} state", rq.state); rq.state
} );
else } else {
{ if header.length == 0 {
if header.length == 0 INFO!(
{ "All param records read, now wait for stdin data on request: {}",
INFO!("All param records read, now wait for stdin data on request: {}", header.id); header.id
);
rq.state = FCGIRequestState::WaitForStdin; rq.state = FCGIRequestState::WaitForStdin;
} } else {
else fcgi_decode_params(rq, &fcgi_read_body(stream, &header)?)?;
{
fcgi_decode_params(rq,&fcgi_read_body(stream, &header)?)?;
} }
} }
} } else {
else
{
WARN!("Uknow request {}, ignore param record", header.id); WARN!("Uknow request {}, ignore param record", header.id);
} }
}, }
FCGIHeaderType::Stdin => { FCGIHeaderType::Stdin => {
if let Some(rq) = requests.get_mut(&header.id) if let Some(rq) = requests.get_mut(&header.id) {
{ if rq.state != FCGIRequestState::WaitForStdin {
if rq.state != FCGIRequestState::WaitForStdin WARN!(
{ "Should not receive a stdin record as the request is in {} state",
WARN!("Should not receive a stdin record as the request is in {} state", rq.state); rq.state
} );
else } else {
{ if header.length == 0 {
if header.length == 0 INFO!(
{ "All stdin records read, now wait for stdout data on request: {}",
INFO!("All stdin records read, now wait for stdout data on request: {}", header.id); header.id
);
rq.state = FCGIRequestState::WaitForStdout; rq.state = FCGIRequestState::WaitForStdout;
if let Err(error) = fcgi_execute_request_handle(rq) if let Err(error) = fcgi_execute_request_handle(rq) {
{
// send stderror // send stderror
fcgi_send_stderr(stream, header.id, Some(error))?; fcgi_send_stderr(stream, header.id, Some(error))?;
} }
@ -702,37 +732,47 @@ pub fn process_request<T: Read + Write + AsRawFd >(stream:&mut T)-> Result<(), B
// send end connection // send end connection
fcgi_send_end_request(stream, header.id, EndRequestStatus::Complete)?; fcgi_send_end_request(stream, header.id, EndRequestStatus::Complete)?;
break; break;
} } else {
else
{
let body = fcgi_read_body(stream, &header)?; let body = fcgi_read_body(stream, &header)?;
rq.data.extend(body); if let None = rq.data {
rq.data = Some(Vec::new())
}
match rq.data.take() {
Some(mut data) => {
data.extend(body);
rq.data = Some(data);
}
None => {}
}
} }
} }
} } else {
else
{
WARN!("Uknow request {}, ignore stdin record", header.id); WARN!("Uknow request {}, ignore stdin record", header.id);
} }
} }
_ => { _ => {
WARN!("Unsupported record type: {} on request {}", header.kind, header.id); WARN!(
"Unsupported record type: {} on request {}",
header.kind,
header.id
);
} }
} }
} }
Ok(()) Ok(())
} }
fn fcgi_read_header<T: Read + Write + AsRawFd >(stream: &mut T) -> Result<FcgiHeader, Error> fn fcgi_read_header<T: Read + Write + AsRawFd>(stream: &mut T) -> Result<FcgiHeader, Error> {
{ let mut buf = vec![0; FCGI_HEADER_LEN];
let mut buf = vec![0;FCGI_HEADER_LEN];
stream.read_exact(&mut buf)?; stream.read_exact(&mut buf)?;
let header: FcgiHeader = FcgiHeader::from_bytes(&buf); let header: FcgiHeader = FcgiHeader::from_bytes(&buf);
Ok(header) Ok(header)
} }
fn fcgi_read_body<T: Read + Write + AsRawFd >(stream: &mut T, header: & FcgiHeader) -> Result<Vec<u8>, Error> fn fcgi_read_body<T: Read + Write + AsRawFd>(
{ stream: &mut T,
header: &FcgiHeader,
) -> Result<Vec<u8>, Error> {
let mut buf = vec![0; header.length as usize]; let mut buf = vec![0; header.length as usize];
stream.read_exact(&mut buf)?; stream.read_exact(&mut buf)?;
let mut pad: Vec<u8> = vec![0; header.padding as usize]; let mut pad: Vec<u8> = vec![0; header.padding as usize];
@ -741,45 +781,117 @@ fn fcgi_read_body<T: Read + Write + AsRawFd >(stream: &mut T, header: & FcgiHead
Ok(buf.to_vec()) Ok(buf.to_vec())
} }
fn fcgi_decode_strlen(data: &[u8]) -> usize fn fcgi_decode_strlen(data: &[u8]) -> usize {
{
let b0 = data[0]; let b0 = data[0];
if b0 >> 7 == 0 if b0 >> 7 == 0 {
{
b0 as usize b0 as usize
} } else {
else return (((data[0] as usize) & 0x7f) << 24)
{ + ((data[1] as usize) << 16)
return (((data[0] as usize) & 0x7f) << 24) + ((data[1] as usize) << 16) + ((data[2] as usize) << 8) + (data[3] as usize) + ((data[2] as usize) << 8)
+ (data[3] as usize);
} }
} }
fn fcgi_decode_params(rq: &mut FGCIRequest, data:& Vec<u8>) -> Result<(), Box<dyn std::error::Error>> fn fcgi_decode_params(
{ rq: &mut FGCIRequest,
data: &Vec<u8>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut index: usize = 1; let mut index: usize = 1;
let key_len = fcgi_decode_strlen(data); let key_len = fcgi_decode_strlen(data);
if key_len > 127 if key_len > 127 {
{
index = 4; index = 4;
} }
let value_len = fcgi_decode_strlen(&data[index..]); let value_len = fcgi_decode_strlen(&data[index..]);
//INFO!("Key len {}, value len {}", key_len, value_len); //INFO!("Key len {}, value len {}", key_len, value_len);
if value_len > 127 if value_len > 127 {
{
index += 4; index += 4;
} } else {
else
{
index += 1; index += 1;
} }
//INFO!("data: {:?}", data); //INFO!("data: {:?}", data);
//INFO!("key: {:?}", data[index..index + key_len].to_vec()); //INFO!("key: {:?}", data[index..index + key_len].to_vec());
//INFO!("Value: {:?}", data[index+key_len..index+key_len+value_len].to_vec()); //INFO!("Value: {:?}", data[index+key_len..index+key_len+value_len].to_vec());
let key = String::from_utf8(data[index..index+key_len].to_vec())?; let key = String::from_utf8(data[index..index + key_len].to_vec())?;
let value: String = String::from_utf8(data[index+key_len..index+key_len+value_len].to_vec())?; let value: String =
String::from_utf8(data[index + key_len..index + key_len + value_len].to_vec())?;
INFO!("PARAM: [{}] -> [{}]", key, value); INFO!("PARAM: [{}] -> [{}]", key, value);
let _ = rq.params.insert(key, value); let _ = rq.params.insert(key, value);
Ok(()) Ok(())
}
fn lua_new_bytes(_: &mlua::Lua, size: usize) -> LuaResult<LuabyteArray> {
let arr = LuabyteArray(vec![0; size]);
Ok(arr)
}
} fn lua_new_bytes_from_string(_: &mlua::Lua, string: String) -> LuaResult<LuabyteArray> {
let arr = LuabyteArray(string.as_bytes().to_vec());
Ok(arr)
}
struct LuabyteArray(Vec<u8>);
impl mlua::UserData for LuabyteArray {
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("size", |_, this: &LuabyteArray, ()| Ok(this.0.len()));
methods.add_method("write", |_, this: &LuabyteArray, path: String| {
match std::fs::File::create(&path) {
Ok(mut file) => {
if let Err(error) = file.write_all(&this.0) {
ERROR!("Unable to write byte array to file {}: {}", &path, error);
return Ok(0);
}
}
Err(error) => {
ERROR!("Unable open file {}: {}", path, error);
return Ok(0);
}
}
Ok(1)
});
methods.add_meta_method(mlua::MetaMethod::Index, |_, this, index: isize| {
if index < 1 || index > this.0.len() as isize {
let error = ERR!(format!(
"Index {} out of bound, array size is {}",
index,
this.0.len()
));
ERROR!("{}", error);
return Ok(None);
}
Ok(Some(this.0[index as usize - 1]))
});
methods.add_meta_method(
mlua::MetaMethod::ToString,
|_, this, ()| match String::from_utf8(this.0.clone()) {
Err(error) => {
let err = format!("Unable to convert byte array to string: {}", error);
ERROR!("{}", err);
return Ok(None);
}
Ok(string) => Ok(Some(string)),
},
);
methods.add_meta_method_mut(
mlua::MetaMethod::NewIndex,
|_, this, (index, value): (isize, u8)| {
if index < 1 || index > this.0.len() as isize {
let error = ERR!(format!(
"Index {} out of bound, array size is {}",
index,
this.0.len()
));
ERROR!("{}", error);
} else {
this.0[index as usize - 1] = value;
}
Ok(())
},
);
methods.add_meta_method_mut(mlua::MetaMethod::Len, |_, this, ()| Ok(this.0.len()));
}
}

View File

@ -2,7 +2,7 @@
//! //!
//! **Author**: "Dany LE <mrsang@iohub.dev>" //! **Author**: "Dany LE <mrsang@iohub.dev>"
//! //!
//! //!
#![warn( #![warn(
trivial_casts, trivial_casts,
trivial_numeric_casts, trivial_numeric_casts,
@ -13,20 +13,19 @@
clippy::pedantic, clippy::pedantic,
clippy::missing_docs_in_private_items clippy::missing_docs_in_private_items
)] )]
use clap;
use serde; use serde;
use toml; use toml;
use clap;
//use std::fs::File; //use std::fs::File;
use luad::*;
use std::io::Read;
use std::io::Write; use std::io::Write;
use std::net::TcpListener; use std::net::TcpListener;
use std::os::fd::FromRawFd;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::os::unix::net::UnixListener; use std::os::unix::net::UnixListener;
use std::panic;
use std::path::Path; use std::path::Path;
use std::os::fd::FromRawFd;
use std::thread; use std::thread;
use std::io::Read;
use luad::*;
/// Callback: clean up function /// Callback: clean up function
/// ///
@ -45,15 +44,16 @@ fn clean_up(n: i32) {
} }
} }
if n != 0 { if n != 0 {
panic!("{}", format!("The LUA fastCGI daemon is terminated by system signal: {}", n)); ERROR!(
"The LUA fastCGI daemon is terminated by system signal: {}",
n
);
std::process::exit(0);
} }
} }
fn handle_request<T: Read + Write + AsRawFd>(stream: &mut T) {
if let Err(error) = process_request(stream) {
fn handle_request<T: Read + Write + AsRawFd >(stream: &mut T) {
if let Err(error) = process_request(stream)
{
ERROR!("Unable to process request: {}", error); ERROR!("Unable to process request: {}", error);
} }
INFO!("Request on socket {} is processed", stream.as_raw_fd()); INFO!("Request on socket {} is processed", stream.as_raw_fd());
@ -64,21 +64,19 @@ fn handle_request<T: Read + Write + AsRawFd >(stream: &mut T) {
/// # Arguments /// # Arguments
/// ///
/// * `socket_opt` - The socket string that the server listens on /// * `socket_opt` - The socket string that the server listens on
fn serve(socket_opt: Option<&str>) { fn serve(config: &Config) {
// bind to a socket if any // bind to a socket if any
if let Some(socket_name) = socket_opt { if let Some(socket_name) = config.socket.as_deref() {
// test if the socket name is an unix domain socket // test if the socket name is an unix domain socket
if socket_name.starts_with("unix:") { if socket_name.starts_with("unix:") {
// e.g unix:/var/run/lighttpd/maint/efcgi.socket // e.g unix:/var/run/lighttpd/maint/efcgi.socket
INFO!("Use unix domain socket: {}", socket_name); INFO!("Use unix domain socket: {}", socket_name);
std::env::set_var("socket", socket_name); std::env::set_var("socket", socket_name);
clean_up(0);
let listener = UnixListener::bind(socket_name.replace("unix:", "")).unwrap(); let listener = UnixListener::bind(socket_name.replace("unix:", "")).unwrap();
on_exit(clean_up);
for client in listener.incoming() { for client in listener.incoming() {
let mut stream = client.unwrap(); let mut stream = client.unwrap();
let _= std::thread::spawn(move || { let _ = std::thread::spawn(move || {
handle_request(&mut stream); handle_request(&mut stream);
}); });
} }
@ -88,7 +86,7 @@ fn serve(socket_opt: Option<&str>) {
let listener = TcpListener::bind(socket_name).unwrap(); let listener = TcpListener::bind(socket_name).unwrap();
for client in listener.incoming() { for client in listener.incoming() {
let mut stream = client.unwrap(); let mut stream = client.unwrap();
let _= thread::spawn(move || { let _ = thread::spawn(move || {
handle_request(&mut stream); handle_request(&mut stream);
}); });
} }
@ -98,13 +96,27 @@ fn serve(socket_opt: Option<&str>) {
// to a socket. This is usually done by by the parent process (e.g. webserver) that launches efcgi // to a socket. This is usually done by by the parent process (e.g. webserver) that launches efcgi
INFO!("No socket specified! use stdin as listenning socket"); INFO!("No socket specified! use stdin as listenning socket");
let stdin = std::io::stdin(); let stdin = std::io::stdin();
let listener = unsafe{ UnixListener::from_raw_fd(stdin.as_raw_fd())}; let fd = stdin.as_raw_fd();
for client in listener.incoming() { if is_unix_socket(fd).unwrap() {
let mut stream = client.unwrap(); INFO!("Stdin is used as Unix domain socket");
let listener = unsafe { UnixListener::from_raw_fd(stdin.as_raw_fd()) };
for client in listener.incoming() {
let mut stream = client.unwrap();
let _= thread::spawn(move || { let _ = thread::spawn(move || {
handle_request(&mut stream); handle_request(&mut stream);
}); });
}
} else {
INFO!("Stdin is used as TCP Socket");
let listener = unsafe { TcpListener::from_raw_fd(stdin.as_raw_fd()) };
for client in listener.incoming() {
let mut stream = client.unwrap();
let _ = thread::spawn(move || {
handle_request(&mut stream);
});
}
} }
} }
} }
@ -115,19 +127,18 @@ struct Config {
pidfile: Option<String>, pidfile: Option<String>,
user: Option<String>, user: Option<String>,
group: Option<String>, group: Option<String>,
debug: bool,
} }
/// Main application entry /// Main application entry
/// ///
/// Run a `fastCGI` server /// Run a `fastCGI` server
fn main() { fn main() {
on_exit(clean_up);
let _log = LOG::init_log(); let _log = LOG::init_log();
let matches = clap::App::new(DAEMON_NAME) let matches = clap::App::new(DAEMON_NAME)
.author(APP_AUTHOR) .author(APP_AUTHOR)
.about("Lua general purpose socket handle daemon") .about("Lua FastCGI daemon")
.version(APP_VERSION) .version(APP_VERSION)
.arg( .arg(
clap::Arg::with_name("file") clap::Arg::with_name("file")
@ -139,29 +150,36 @@ fn main() {
.takes_value(true), .takes_value(true),
) )
.get_matches(); .get_matches();
let mut config = Config {
socket: None,
pidfile: None,
user: None,
group: None,
debug: false,
};
match matches.value_of("file") { match matches.value_of("file") {
Some(path) => { Some(path) => {
INFO!("Configuration file: {}", path); INFO!("Configuration file: {}", path);
let contents = std::fs::read_to_string(path).unwrap(); let contents = std::fs::read_to_string(path).unwrap();
let config: Config = toml::from_str(&contents).unwrap(); config = toml::from_str(&contents).unwrap();
if config.debug {
// write pid file std::env::set_var("luad_debug", "true");
match config.pidfile {
Some(pidfile) => {
let mut f = std::fs::File::create(&pidfile).unwrap();
write!(f, "{}", std::process::id()).unwrap();
INFO!("PID file created at {}", pidfile);
},
None => {}
} }
// drop user privilege if only user and group available in // drop user privilege if only user and group available in
// the configuration file, otherwise ignore // the configuration file, otherwise ignore
privdrop(config.user.as_deref(), config.group.as_deref()).unwrap(); privdrop(config.user.as_deref(), config.group.as_deref()).unwrap();
serve(config.socket.as_deref());
}, // write pid file
None => { match &config.pidfile {
serve(None); Some(pidfile) => {
let mut f = std::fs::File::create(&pidfile).unwrap();
write!(f, "{}", std::process::id()).unwrap();
INFO!("PID file created at {}", pidfile);
}
None => {}
}
} }
None => {}
} }
serve(&config);
} }