From 88ec016405691d4c2b3f99a364e22492b08d5c5c Mon Sep 17 00:00:00 2001 From: DanyLE Date: Tue, 17 Jan 2023 00:59:57 +0100 Subject: [PATCH] Working LUA base FastCGI server --- Cargo.lock | 10 - Cargo.toml | 2 - config-example.toml | 8 +- src/lib.rs | 556 ++++++++++++++++++++++++++------------------ src/main.rs | 108 +++++---- 5 files changed, 402 insertions(+), 282 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f795ca7..5163618 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,15 +70,6 @@ dependencies = [ "vec_map", ] -[[package]] -name = "fastcgi" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4159a0f48bea0281602e508eb070d7d7ba1f6ac2480f9db1a60a39274aea1cc" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.1.19" @@ -108,7 +99,6 @@ name = "luad" version = "0.1.0" dependencies = [ "clap", - "fastcgi", "libc", "mlua", "nix", diff --git a/Cargo.toml b/Cargo.toml index a6184b9..48c8cde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -fastcgi = "1.0.0" mlua = { version = "0.8", features = ["lua54", "vendored"] } clap = "2.33" nix = "0.26.1" @@ -19,6 +18,5 @@ libc = "0.2" opt-level = 's' # 's' for size lto = true -# this strategy doesnot work on 1.3.9 # panic = 'abort' codegen-units = 1 \ No newline at end of file diff --git a/config-example.toml b/config-example.toml index 675025a..ea98fed 100644 --- a/config-example.toml +++ b/config-example.toml @@ -1,10 +1,12 @@ # TCP socket or Unix socket file -socket = "unix:/tmp/lua.sock" +socket = "unix:/tmp/lua1.sock" # pid file pidfile = "/tmp/luad.pid" # user name -# user = "demo" +user = "dany" # group name -# group = "demo" \ No newline at end of file +group = "dany" + +debug = false \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 1d99fe7..8a42632 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,12 @@ -use mlua::prelude::*; use libc; +use mlua::prelude::*; use nix; -use std::io::{Error, ErrorKind, Read,Write}; +use std::collections::HashMap; use std::ffi::CString; use std::fmt::Arguments; +use std::io::{Error, ErrorKind, Read, Write}; use std::os::fd::RawFd; -use std::os::unix::io::{AsRawFd}; -use std::collections::HashMap; +use std::os::unix::io::AsRawFd; /// app author pub const APP_AUTHOR: &str = "Dany LE "; @@ -17,6 +17,14 @@ pub const APP_VERSION: &str = "0.1.0"; /// Application name 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 /// /// 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 pub fn privdrop(useropt: Option<&str>, groupopt: Option<&str>) -> Result<(), nix::Error> { - match groupopt{ + match groupopt { Some(group) => { INFO!("Dropping current process group to {}", group); match nix::unistd::Group::from_name(group)? { Some(group) => nix::unistd::setgid(group.gid), None => Err(nix::Error::last()), }?; - }, + } None => {} } match useropt { @@ -49,13 +57,25 @@ pub fn privdrop(useropt: Option<&str>, groupopt: Option<&str>) -> Result<(), nix Some(user) => nix::unistd::setuid(user.uid), None => Err(nix::Error::last()), }? - }, + } None => {} } Ok(()) } - +pub fn is_unix_socket(fd: libc::c_int) -> Result { + unsafe { + let mut addr: libc::sockaddr_storage = std::mem::zeroed(); + let mut addr_len: libc::socklen_t = + std::mem::size_of::() 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 /// cause the program to exit /// @@ -162,9 +182,19 @@ impl LOG { /// /// # 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> { 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(); if output.write_fmt(args).is_err() { 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 fmt = CString::new(log_fmt.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 { libc::syslog(sysloglevel, fmt.as_ptr(), c_msg.as_ptr()); } @@ -195,10 +220,9 @@ impl Drop for LOG { } } - /// Protocol goes here #[derive(Debug)] -enum FCGIHeaderType{ +enum FCGIHeaderType { BeginRequest, AbortRequest, EndRequest, @@ -213,7 +237,6 @@ enum FCGIHeaderType{ } impl FCGIHeaderType { - /// convert a u8 value to `FCGIHeaderType` value /// /// # Arguments @@ -275,7 +298,7 @@ impl std::fmt::Display for FCGIHeaderType { } } -enum EndRequestStatus{ +enum EndRequestStatus { Complete, // CantMaxMPXConn, // Overloaded, @@ -298,20 +321,18 @@ impl EndRequestStatus { } } -const FCGI_HEADER_LEN:usize = 8; -const FCGI_VERSION:u8 = 1; +const FCGI_HEADER_LEN: usize = 8; +const FCGI_VERSION: u8 = 1; -#[derive(Debug)] -#[derive(PartialEq)] +#[derive(Debug, PartialEq)] enum FCGIRole { Responder, Authorizer, Filter, - Unknown + Unknown, } impl FCGIRole { - /// convert a u8 value to `FCGIRole` value /// /// # Arguments @@ -339,14 +360,13 @@ impl std::fmt::Display for FCGIRole { } } #[derive(Debug)] -struct FCGIBeginRequestBody{ +struct FCGIBeginRequestBody { role: FCGIRole, flags: u8, } impl FCGIBeginRequestBody { - pub fn from_bytes(data: &[u8]) -> Self - { + pub fn from_bytes(data: &[u8]) -> Self { Self { role: FCGIRole::from_u16(((data[0] as u16) << 8) | (data[1] as u16)), flags: data[2], @@ -361,30 +381,27 @@ impl std::fmt::Display for FCGIBeginRequestBody { } } - #[derive(Debug)] struct FcgiHeader { version: u8, kind: FCGIHeaderType, id: u16, padding: u8, - length:u16, + length: u16, } impl FcgiHeader { - pub fn from_bytes(data: &[u8]) -> Self - { + pub fn from_bytes(data: &[u8]) -> Self { Self { version: data[0], kind: FCGIHeaderType::from_u8(data[1]), id: ((data[2] as u16) << 8) | (data[3] as u16), - length: ((data[4] as u16) << 8) | (data[5]as u16), - padding: data[6] + length: ((data[4] as u16) << 8) | (data[5] as u16), + padding: data[6], } } - pub fn as_bytes(&self) -> Vec - { + pub fn as_bytes(&self) -> Vec { vec![ self.version, self.kind.as_u8(), @@ -393,7 +410,7 @@ impl FcgiHeader { (self.length >> 8) as u8, (self.length & 0xFF) as u8, self.padding, - 0 + 0, ] } } @@ -408,12 +425,11 @@ impl std::fmt::Display for FcgiHeader { } } -#[derive(Debug)] -#[derive(PartialEq)] +#[derive(Debug, PartialEq)] enum FCGIRequestState { WaitForParams, WaitForStdin, - WaitForStdout + WaitForStdout, } impl std::fmt::Display for FCGIRequestState { @@ -431,32 +447,71 @@ struct FGCIRequest { params: HashMap, id: u16, fd: RawFd, - data: Vec, + data: Option>, state: FCGIRequestState, } -struct FCGIStdoutStream { +struct FCGIOStream { fd: RawFd, - id: u16 + id: u16, } -impl FCGIStdoutStream { - fn _write(&self, buf: &[u8]) -> std::io::Result - { - 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)); +impl FCGIOStream { + fn write_record(&mut self, buf: Vec) -> Result<(), Box> { + fcgi_send_stdout(self, self.id, Some(buf))?; + Ok(()) + } + fn write_variadic( + &mut self, + values: mlua::Variadic, + ) -> Result<(), Box> { + let mut output: Vec = 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::() { + return Err(Box::new(ERR!("Unsupported data type"))); + } + let arr = v.borrow::()?; + 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 { - 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<()> { @@ -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) { - methods.add_method("write", |_, this:& FCGIStdoutStream, strings:mlua::Variadic| { - let mut stream = FCGIStdoutStream - { - fd: this.fd, - id: this.id - }; - let mut output = String::new(); - for string in strings{ - output.push_str(&string); - } - fcgi_send_stdout(&mut stream, this.id, Some(output.as_bytes().to_vec())) - .map_err(|e| mlua::Error::external(ERR!(e.to_string())))?; - Ok(()) - }); + methods.add_method_mut( + "echo", + |_, this: &mut FCGIOStream, values: mlua::Variadic<_>| { + this.write_variadic(values) + .map_err(|e| mlua::Error::external(ERR!(e.to_string()))) + }, + ); + methods.add_method_mut( + "print", + |_, this: &mut FCGIOStream, values: mlua::Variadic<_>| { + this.write_variadic(values) + .map_err(|e| mlua::Error::external(ERR!(e.to_string()))) + }, + ); + 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) -> LuaResult<()> { - 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> -{ +fn fcgi_execute_request_handle(rq: &mut FGCIRequest) -> Result<(), Box> { let lua = mlua::Lua::new(); let global = lua.globals(); let request = lua.create_table()?; - request.set("ID", rq.id)?; - request.set("FD", rq.fd)?; - for (k,v) in &rq.params{ - request.set(String::from(k),String::from(v))?; + + for (k, v) in &rq.params { + 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 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)?; - INFO!("source: {}", &source); lua.load(&source).exec()?; 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(stream: &mut T,id: u16, eopt: Option>) -> Result<(), Box> -{ +fn fcgi_send_stderr( + stream: &mut T, + id: u16, + eopt: Option>, +) -> Result<(), Box> { let mut header = FcgiHeader { version: FCGI_VERSION, kind: FCGIHeaderType::Stderr, id: id, length: 0, - padding: 0 , + padding: 0, }; - if let Some(error) = eopt{ + if let Some(error) = eopt { let err_str = error.to_string(); let str_len = err_str.len(); let mut padding = (8 - str_len % 8) as u8; - if padding == 8 - { + if padding == 8 { padding = 0; } 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.padding = padding; body.extend(pad); stream.write_all(&header.as_bytes())?; stream.write_all(&body)?; - } - else - { + } else { stream.write_all(&header.as_bytes())?; } Ok(()) } -fn fcgi_send_stdout(stream: &mut T,id: u16, dopt: Option>) -> Result<(), Box> -{ +fn fcgi_send_stdout( + stream: &mut T, + id: u16, + dopt: Option>, +) -> Result<(), Box> { let mut header = FcgiHeader { version: FCGI_VERSION, kind: FCGIHeaderType::Stdout, @@ -585,115 +624,106 @@ fn fcgi_send_stdout(stream: &mut T,id: u16, dopt: Option>) -> length: 0, padding: 0, }; - if let Some(data) = dopt{ + if let Some(data) = dopt { header.length = data.len() as u16; header.padding = (8 - header.length % 8) as u8; - if header.padding == 8 - { + if header.padding == 8 { header.padding = 0; } let mut body = data; - let pad = vec![0;header.padding as usize]; + let pad = vec![0; header.padding as usize]; body.extend(pad); stream.write_all(&header.as_bytes())?; stream.write_all(&body)?; - } - else - { + } else { stream.write_all(&header.as_bytes())?; } Ok(()) } -fn fcgi_send_end_request(stream: &mut T,id:u16, status: EndRequestStatus) -> Result<(), Box> -{ +fn fcgi_send_end_request( + stream: &mut T, + id: u16, + status: EndRequestStatus, +) -> Result<(), Box> { let header = FcgiHeader { version: FCGI_VERSION, kind: FCGIHeaderType::EndRequest, id: id, length: 8, - padding: 0 , + padding: 0, }; - let body = vec![ - 0,0,0,0, - status.as_u8(), - 0,0,0 - ]; + let body = vec![0, 0, 0, 0, status.as_u8(), 0, 0, 0]; stream.write_all(&header.as_bytes())?; stream.write_all(&body)?; Ok(()) } -pub fn process_request(stream:&mut T)-> Result<(), Box>{ +pub fn process_request( + stream: &mut T, +) -> Result<(), Box> { let mut requests: HashMap = HashMap::new(); - loop{ + loop { let header = fcgi_read_header(stream)?; match header.kind { FCGIHeaderType::BeginRequest => { let body = FCGIBeginRequestBody::from_bytes(&fcgi_read_body(stream, &header)?); 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)?; return Err(Box::new(ERR!("Only Responder role is supported"))); } // 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); - } - else - { - let rq:FGCIRequest = FGCIRequest { + } else { + let rq: FGCIRequest = FGCIRequest { id: header.id, params: HashMap::new(), - data: vec![0;0], + data: None, state: FCGIRequestState::WaitForParams, fd: stream.as_raw_fd(), }; requests.insert(header.id, rq); } - }, + } FCGIHeaderType::Params => { - if let Some(rq) = requests.get_mut(&header.id) - { - if rq.state != FCGIRequestState::WaitForParams - { - WARN!("Should not receive a param record as the request is in {} state", rq.state); - } - else - { - if header.length == 0 - { - INFO!("All param records read, now wait for stdin data on request: {}", header.id); + if let Some(rq) = requests.get_mut(&header.id) { + if rq.state != FCGIRequestState::WaitForParams { + WARN!( + "Should not receive a param record as the request is in {} state", + rq.state + ); + } else { + if header.length == 0 { + INFO!( + "All param records read, now wait for stdin data on request: {}", + header.id + ); rq.state = FCGIRequestState::WaitForStdin; - } - else - { - fcgi_decode_params(rq,&fcgi_read_body(stream, &header)?)?; + } else { + fcgi_decode_params(rq, &fcgi_read_body(stream, &header)?)?; } } - } - else - { + } else { WARN!("Uknow request {}, ignore param record", header.id); } - }, + } FCGIHeaderType::Stdin => { - if let Some(rq) = requests.get_mut(&header.id) - { - if rq.state != FCGIRequestState::WaitForStdin - { - WARN!("Should not receive a stdin record as the request is in {} state", rq.state); - } - else - { - if header.length == 0 - { - INFO!("All stdin records read, now wait for stdout data on request: {}", header.id); + if let Some(rq) = requests.get_mut(&header.id) { + if rq.state != FCGIRequestState::WaitForStdin { + WARN!( + "Should not receive a stdin record as the request is in {} state", + rq.state + ); + } else { + if header.length == 0 { + INFO!( + "All stdin records read, now wait for stdout data on request: {}", + header.id + ); rq.state = FCGIRequestState::WaitForStdout; - if let Err(error) = fcgi_execute_request_handle(rq) - { + if let Err(error) = fcgi_execute_request_handle(rq) { // send stderror fcgi_send_stderr(stream, header.id, Some(error))?; } @@ -702,37 +732,47 @@ pub fn process_request(stream:&mut T)-> Result<(), B // send end connection fcgi_send_end_request(stream, header.id, EndRequestStatus::Complete)?; break; - } - else - { + } else { 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!("Unsupported record type: {} on request {}", header.kind, header.id); + WARN!( + "Unsupported record type: {} on request {}", + header.kind, + header.id + ); } } } Ok(()) } -fn fcgi_read_header(stream: &mut T) -> Result -{ - let mut buf = vec![0;FCGI_HEADER_LEN]; +fn fcgi_read_header(stream: &mut T) -> Result { + let mut buf = vec![0; FCGI_HEADER_LEN]; stream.read_exact(&mut buf)?; let header: FcgiHeader = FcgiHeader::from_bytes(&buf); Ok(header) } -fn fcgi_read_body(stream: &mut T, header: & FcgiHeader) -> Result, Error> -{ +fn fcgi_read_body( + stream: &mut T, + header: &FcgiHeader, +) -> Result, Error> { let mut buf = vec![0; header.length as usize]; stream.read_exact(&mut buf)?; let mut pad: Vec = vec![0; header.padding as usize]; @@ -741,45 +781,117 @@ fn fcgi_read_body(stream: &mut T, header: & FcgiHead Ok(buf.to_vec()) } -fn fcgi_decode_strlen(data: &[u8]) -> usize -{ +fn fcgi_decode_strlen(data: &[u8]) -> usize { let b0 = data[0]; - if b0 >> 7 == 0 - { + if b0 >> 7 == 0 { b0 as usize - } - else - { - return (((data[0] as usize) & 0x7f) << 24) + ((data[1] as usize) << 16) + ((data[2] as usize) << 8) + (data[3] as usize) + } else { + return (((data[0] as usize) & 0x7f) << 24) + + ((data[1] as usize) << 16) + + ((data[2] as usize) << 8) + + (data[3] as usize); } } -fn fcgi_decode_params(rq: &mut FGCIRequest, data:& Vec) -> Result<(), Box> -{ +fn fcgi_decode_params( + rq: &mut FGCIRequest, + data: &Vec, +) -> Result<(), Box> { let mut index: usize = 1; let key_len = fcgi_decode_strlen(data); - if key_len > 127 - { + if key_len > 127 { index = 4; } let value_len = fcgi_decode_strlen(&data[index..]); //INFO!("Key len {}, value len {}", key_len, value_len); - if value_len > 127 - { + if value_len > 127 { index += 4; - } - else - { + } else { index += 1; } //INFO!("data: {:?}", data); //INFO!("key: {:?}", data[index..index + key_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 value: String = String::from_utf8(data[index+key_len..index+key_len+value_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())?; INFO!("PARAM: [{}] -> [{}]", key, value); let _ = rq.params.insert(key, value); Ok(()) +} +fn lua_new_bytes(_: &mlua::Lua, size: usize) -> LuaResult { + let arr = LuabyteArray(vec![0; size]); + Ok(arr) +} -} \ No newline at end of file +fn lua_new_bytes_from_string(_: &mlua::Lua, string: String) -> LuaResult { + let arr = LuabyteArray(string.as_bytes().to_vec()); + Ok(arr) +} + +struct LuabyteArray(Vec); + +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())); + } +} diff --git a/src/main.rs b/src/main.rs index a3e9500..bd70da0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ //! //! **Author**: "Dany LE " //! -//! +//! #![warn( trivial_casts, trivial_numeric_casts, @@ -13,20 +13,19 @@ clippy::pedantic, clippy::missing_docs_in_private_items )] +use clap; use serde; use toml; -use clap; //use std::fs::File; +use luad::*; +use std::io::Read; use std::io::Write; use std::net::TcpListener; +use std::os::fd::FromRawFd; use std::os::unix::io::AsRawFd; use std::os::unix::net::UnixListener; -use std::panic; use std::path::Path; -use std::os::fd::FromRawFd; use std::thread; -use std::io::Read; -use luad::*; /// Callback: clean up function /// @@ -45,15 +44,16 @@ fn clean_up(n: i32) { } } 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(stream: &mut T) { - if let Err(error) = process_request(stream) - { +fn handle_request(stream: &mut T) { + if let Err(error) = process_request(stream) { ERROR!("Unable to process request: {}", error); } INFO!("Request on socket {} is processed", stream.as_raw_fd()); @@ -64,21 +64,19 @@ fn handle_request(stream: &mut T) { /// # Arguments /// /// * `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 - 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 - if socket_name.starts_with("unix:") { + if socket_name.starts_with("unix:") { // e.g unix:/var/run/lighttpd/maint/efcgi.socket INFO!("Use unix domain socket: {}", socket_name); std::env::set_var("socket", socket_name); + clean_up(0); let listener = UnixListener::bind(socket_name.replace("unix:", "")).unwrap(); - on_exit(clean_up); for client in listener.incoming() { let mut stream = client.unwrap(); - let _= std::thread::spawn(move || { + let _ = std::thread::spawn(move || { handle_request(&mut stream); }); } @@ -88,7 +86,7 @@ fn serve(socket_opt: Option<&str>) { let listener = TcpListener::bind(socket_name).unwrap(); for client in listener.incoming() { let mut stream = client.unwrap(); - let _= thread::spawn(move || { + let _ = thread::spawn(move || { 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 INFO!("No socket specified! use stdin as listenning socket"); let stdin = std::io::stdin(); - let listener = unsafe{ UnixListener::from_raw_fd(stdin.as_raw_fd())}; - for client in listener.incoming() { - let mut stream = client.unwrap(); + let fd = stdin.as_raw_fd(); + if is_unix_socket(fd).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 || { - handle_request(&mut stream); - }); + let _ = thread::spawn(move || { + 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, user: Option, group: Option, + debug: bool, } - - /// Main application entry /// /// Run a `fastCGI` server fn main() { + on_exit(clean_up); let _log = LOG::init_log(); - let matches = clap::App::new(DAEMON_NAME) .author(APP_AUTHOR) - .about("Lua general purpose socket handle daemon") + .about("Lua FastCGI daemon") .version(APP_VERSION) .arg( clap::Arg::with_name("file") @@ -139,29 +150,36 @@ fn main() { .takes_value(true), ) .get_matches(); - + let mut config = Config { + socket: None, + pidfile: None, + user: None, + group: None, + debug: false, + }; match matches.value_of("file") { Some(path) => { INFO!("Configuration file: {}", path); let contents = std::fs::read_to_string(path).unwrap(); - let config: Config = toml::from_str(&contents).unwrap(); - - // write pid file - 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 => {} + config = toml::from_str(&contents).unwrap(); + if config.debug { + std::env::set_var("luad_debug", "true"); } // drop user privilege if only user and group available in // the configuration file, otherwise ignore privdrop(config.user.as_deref(), config.group.as_deref()).unwrap(); - serve(config.socket.as_deref()); - }, - None => { - serve(None); + + // write pid file + 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 => {} + } } + None => {} } + serve(&config); }