I am currently having a problem terminating a FastCGI request. Currently this is the code I have:
use std::os::unix::net::{UnixStream};
use std::io::{Read, Write};
use std::str;
fn main() {
const FCGI_VERSION_1: u8 = 1;
const FCGI_BEGIN_REQUEST:u8 = 1;
const FCGI_END_REQUEST: u8 = 3;
const FCGI_STDIN: u8 = 5;
const FCGI_STDOUT: u8 = 6;
const FCGI_STDERR: u8 = 7;
const FCGI_RESPONDER: u16 = 1;
const FCGI_PARAMS: u8 = 4;
let socket_path = "/run/php-fpm/php-fpm.sock";
let mut socket = match UnixStream::connect(socket_path) {
Ok(sock) => sock,
Err(e) => {
println!("Couldn't connect: {e:?}");
return
}
};
let requestId: u16 = 1;
let role: u16 = FCGI_RESPONDER;
let beginRequest = vec![
// FCGI_Header
FCGI_VERSION_1, FCGI_BEGIN_REQUEST,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
0x00, 0x08, // This is the size of `FCGI_BeginRequestBody`
0, 0,
// FCGI_BeginRequestBody
(role >> 8) as u8, (role & 0xFF) as u8,
0, // Flags
0, 0, 0, 0, 0, // Reserved
];
socket.write_all(&beginRequest).unwrap();
// write the FCGI_PARAMS
let param1_name = "SCRIPT_FILENAME".as_bytes();
let param1_value = "/var/www/public/index.php".as_bytes();
let lengths1 = [ param1_name.len() as u8, param1_value.len() as u8 ];
let params1_len: u16 = (param1_name.len() + param1_value.len() + lengths1.len()) as u16;
let param2_name = b"REQUEST_METHOD";
let param2_value = b"GET";
let lengths2 = [ param2_name.len() as u8, param2_value.len() as u8 ];
let params2_len: u16 = (param2_name.len() + param2_value.len() + lengths2.len()) as u16;
let params_len = params1_len + params2_len;
let paramsRequest = vec![
FCGI_VERSION_1, FCGI_PARAMS,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
(params_len >> 8) as u8, (params_len & 0xFF) as u8,
0, 0,
];
socket.write_all (¶msRequest).unwrap();
socket.write_all (&lengths1).unwrap();
socket.write_all (param1_name).unwrap();
socket.write_all (param1_value).unwrap();
socket.write_all (&lengths2).unwrap();
socket.write_all (param2_name).unwrap();
socket.write_all (param2_value).unwrap();
// get the response
let requestHeader = vec![
FCGI_VERSION_1, FCGI_STDIN,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
0, 0,
0, 0,
];
socket.write_all(&requestHeader).unwrap();
// read the response
let mut responseHeader = [0u8; 8];
socket.read_exact (&mut responseHeader).unwrap();
// read the padding
let mut pad = vec![0; responseHeader[7] as usize];
socket.read_exact (&mut pad).unwrap();
// read the body
let responseLength = ((responseHeader[4] as usize) << 8) | (responseHeader[5] as usize);
let mut responseBody = Vec::new();
responseBody.resize (responseLength, 0);
socket.read_exact (&mut responseBody).unwrap();
println!("Output: {:?}", std::str::from_utf8(&responseBody));
}
What my code does (in short) is make a FastCGI request to a .php
file using the php-fpm
service. And that code works (up to a point...) well, actually I have the expected output which is this:
Ok("X-Powered-By: PHP/8.1.11\r\nContent-type: text/html; charset=UTF-8\r\n\r\nFirst file")
By the way, my php file only has the following content:
<?php
echo "First file";
?>
So far, so good. But now what I want to do is receive the record FCGI_END_REQUEST. And for that, according to answers to previous questions (questions made by me, like this) what I have to do is make a loop and read all the content of FCGI_STDIN
until I receive the record FCGI_END_REQUEST
. What according to me (if I'm wrong please tell me) would be to do this:
let mut output: String = String::new();
loop {
// get the response
let requestHeader = vec![
FCGI_VERSION_1, FCGI_STDOUT,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
0, 0,
0, 0,
];
socket.write_all(&requestHeader).unwrap();
// read the response
let mut responseHeader = [0u8; 8];
socket.read_exact (&mut responseHeader).unwrap();
if responseHeader[1] != FCGI_STDOUT && responseHeader[1] != FCGI_STDERR{
if responseHeader[1] == FCGI_END_REQUEST {
println!("FCGI_END_REQUEST");
break;
} else {
println!("NOT FCGI_END_REQUEST");
break;
}
}
// read the padding
let mut pad = vec![0; responseHeader[7] as usize];
socket.read_exact (&mut pad).unwrap();
// read the body
let responseLength = ((responseHeader[4] as usize) << 8) | (responseHeader[5] as usize);
let mut responseBody = Vec::new();
responseBody.resize (responseLength, 0);
let format = format!("{}", String::from_utf8_lossy(&responseBody));
output.push_str(format.as_str());
}
println!("Output: {:?}", output);
This code is replacing all the first code I showed from the "get the response" comment to the end of the file. What I do is loop reading FCGI_STDIN
and when it comes across a record other than FCGI_STDOUT
or FCGI_STDERR
it prints if FCGI_END_REQUEST
was the type of record that was found. So now this is my full code:
use std::os::unix::net::{UnixStream};
use std::io::{Read, Write};
use std::str;
fn main() {
const FCGI_VERSION_1: u8 = 1;
const FCGI_BEGIN_REQUEST:u8 = 1;
const FCGI_END_REQUEST: u8 = 3;
const FCGI_STDIN: u8 = 5;
const FCGI_STDOUT: u8 = 6;
const FCGI_STDERR: u8 = 7;
const FCGI_RESPONDER: u16 = 1;
const FCGI_PARAMS: u8 = 4;
let socket_path = "/run/php-fpm/php-fpm.sock";
let mut socket = match UnixStream::connect(socket_path) {
Ok(sock) => sock,
Err(e) => {
println!("Couldn't connect: {e:?}");
return
}
};
let requestId: u16 = 1;
let role: u16 = FCGI_RESPONDER;
let beginRequest = vec![
// FCGI_Header
FCGI_VERSION_1, FCGI_BEGIN_REQUEST,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
0x00, 0x08, // This is the size of `FCGI_BeginRequestBody`
0, 0,
// FCGI_BeginRequestBody
(role >> 8) as u8, (role & 0xFF) as u8,
0, // Flags
0, 0, 0, 0, 0, // Reserved
];
socket.write_all(&beginRequest).unwrap();
// write the FCGI_PARAMS
let param1_name = "SCRIPT_FILENAME".as_bytes();
let param1_value = "/var/www/public/index.php".as_bytes();
let lengths1 = [ param1_name.len() as u8, param1_value.len() as u8 ];
let params1_len: u16 = (param1_name.len() + param1_value.len() + lengths1.len()) as u16;
let param2_name = b"REQUEST_METHOD";
let param2_value = b"GET";
let lengths2 = [ param2_name.len() as u8, param2_value.len() as u8 ];
let params2_len: u16 = (param2_name.len() + param2_value.len() + lengths2.len()) as u16;
let params_len = params1_len + params2_len;
let paramsRequest = vec![
FCGI_VERSION_1, FCGI_PARAMS,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
(params_len >> 8) as u8, (params_len & 0xFF) as u8,
0, 0,
];
socket.write_all (¶msRequest).unwrap();
socket.write_all (&lengths1).unwrap();
socket.write_all (param1_name).unwrap();
socket.write_all (param1_value).unwrap();
socket.write_all (&lengths2).unwrap();
socket.write_all (param2_name).unwrap();
socket.write_all (param2_value).unwrap();
let mut output: String = String::new();
loop {
// get the response
let requestHeader = vec![
FCGI_VERSION_1, FCGI_STDOUT,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
0, 0,
0, 0,
];
socket.write_all(&requestHeader).unwrap();
// read the response
let mut responseHeader = [0u8; 8];
socket.read_exact (&mut responseHeader).unwrap();
if responseHeader[1] != FCGI_STDOUT && responseHeader[1] != FCGI_STDERR{
if responseHeader[1] == FCGI_END_REQUEST {
println!("FCGI_END_REQUEST");
break;
} else {
println!("NOT FCGI_END_REQUEST: {}", responseHeader[1]);
break;
}
}
// read the padding
let mut pad = vec![0; responseHeader[7] as usize];
socket.read_exact (&mut pad).unwrap();
// read the body
let responseLength = ((responseHeader[4] as usize) << 8) | (responseHeader[5] as usize);
let mut responseBody = Vec::new();
responseBody.resize (responseLength, 0);
let format = format!("{}", String::from_utf8_lossy(&responseBody));
output.push_str(format.as_str());
}
println!("Output: {:?}", output);
}
The problem is that the output of this program is this:
NOT FCGI_END_REQUEST Output: "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
So what am I doing wrong? What's wrong with my code?
I did what I was told in the comments, which is to print the value of responseHeader[1]
. So change println!("NOT FCGI_END_REQUEST");
to println!("NOT FCGI_END_REQUEST: {}", responseHeader[1]);
. And I get this output:
NOT FCGI_END_REQUEST: 45 Output: "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
Based on what I have understood from what has been said in the chat. I'm going to add a println!("{:?}", foo);
after each socket.read_exact (&mut responseHeader)
. So my loop would look like this:
loop {
// get the response
let requestHeader = vec![
FCGI_VERSION_1, FCGI_STDOUT,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
0, 0,
0, 0,
];
socket.write_all(&requestHeader).unwrap();
// read the response
let mut responseHeader = [0u8; 8];
socket.read_exact (&mut responseHeader).unwrap();
// "debug"
println!("{:?}", responseHeader);
if responseHeader[1] != FCGI_STDOUT && responseHeader[1] != FCGI_STDERR{
if responseHeader[1] == FCGI_END_REQUEST {
println!("FCGI_END_REQUEST");
break;
} else {
println!("NOT FCGI_END_REQUEST: {}", responseHeader[1]);
break;
}
}
// read the padding
let mut pad = vec![0; responseHeader[6] as usize];
socket.read_exact (&mut pad).unwrap();
// "debug";
println!("{:?}", pad);
// read the body
let responseLength = ((responseHeader[4] as usize) << 8) | (responseHeader[5] as usize);
let mut responseBody = Vec::new();
responseBody.resize (responseLength, 0);
let format = format!("{}", String::from_utf8_lossy(&responseBody));
output.push_str(format.as_str());
}
With that code. I get this output:
[1, 6, 0, 1, 0, 78, 2, 0]
[88, 45]
[80, 111, 119, 101, 114, 101, 100, 45]
NOT FCGI_END_REQUEST: 111
Output: "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
Now, also from what has been said in the chat I understand that there are some other problems with my code, but I have not understood exactly what they mean and how to correct them.
I already found a way to get FCGI_END_REQUEST
(or so I think). I did it with part of Jmb's answer and with something he said in the chat. The first thing I did was write FCGI_STDOUT
just once and before doing the loop
. Then I create the responseHeader
variable and read the data. And then, thanks to Jmb's answer, I read the padding after reading the response body and read responseHeader[6]
and not responseHeader[7]
(as I did before). So my code looks like this:
use std::os::unix::net::{UnixStream};
use std::io::{Read, Write};
use std::str;
fn main() {
const FCGI_VERSION_1: u8 = 1;
const FCGI_BEGIN_REQUEST:u8 = 1;
const FCGI_END_REQUEST: u8 = 3;
const FCGI_STDIN: u8 = 5;
const FCGI_STDOUT: u8 = 6;
const FCGI_STDERR: u8 = 7;
const FCGI_RESPONDER: u16 = 1;
const FCGI_PARAMS: u8 = 4;
let socket_path = "/run/php-fpm/php-fpm.sock";
let mut socket = match UnixStream::connect(socket_path) {
Ok(sock) => sock,
Err(e) => {
println!("Couldn't connect: {e:?}");
return
}
};
let requestId: u16 = 1;
let role: u16 = FCGI_RESPONDER;
let beginRequest = vec![
// FCGI_Header
FCGI_VERSION_1, FCGI_BEGIN_REQUEST,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
0x00, 0x08, // This is the size of `FCGI_BeginRequestBody`
0, 0,
// FCGI_BeginRequestBody
(role >> 8) as u8, (role & 0xFF) as u8,
0, // Flags
0, 0, 0, 0, 0, // Reserved
];
socket.write_all(&beginRequest).unwrap();
// write the FCGI_PARAMS
let param1_name = "SCRIPT_FILENAME".as_bytes();
let param1_value = "/home/davebook-arch/projects/so/index.php".as_bytes();
let lengths1 = [ param1_name.len() as u8, param1_value.len() as u8 ];
let params1_len: u16 = (param1_name.len() + param1_value.len() + lengths1.len()) as u16;
let param2_name = b"REQUEST_METHOD";
let param2_value = b"GET";
let lengths2 = [ param2_name.len() as u8, param2_value.len() as u8 ];
let params2_len: u16 = (param2_name.len() + param2_value.len() + lengths2.len()) as u16;
let params_len = params1_len + params2_len;
let paramsRequest = vec![
FCGI_VERSION_1, FCGI_PARAMS,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
(params_len >> 8) as u8, (params_len & 0xFF) as u8,
0, 0,
];
socket.write_all (¶msRequest).unwrap();
socket.write_all (&lengths1).unwrap();
socket.write_all (param1_name).unwrap();
socket.write_all (param1_value).unwrap();
socket.write_all (&lengths2).unwrap();
socket.write_all (param2_name).unwrap();
socket.write_all (param2_value).unwrap();
let mut stdout: String = String::new();
// get the response
let requestHeader = vec![
FCGI_VERSION_1, FCGI_STDOUT,
(requestId >> 8) as u8, (requestId & 0xFF) as u8,
0, 0,
0, 0,
];
socket.write_all(&requestHeader).unwrap();
loop {
// read the response header
let mut responseHeader = [0u8; 8];
socket.read_exact (&mut responseHeader).unwrap();
if responseHeader[1] != FCGI_STDOUT && responseHeader[1] != FCGI_STDERR{
if responseHeader[1] == FCGI_END_REQUEST {
println!("FCGI_END_REQUEST: {:?}", responseHeader);
break;
} else {
println!("NOT FCGI_END_REQUEST: {}", responseHeader[1]);
break;
}
}
// read the body
let responseLength = ((responseHeader[4] as usize) << 8) | (responseHeader[5] as usize);
let mut responseBody = vec![0; responseLength];
socket.read_exact (&mut responseBody).unwrap();
stdout.push_str(&String::from_utf8_lossy(&responseBody));
// read the padding
let mut pad = vec![0; responseHeader[6] as usize];
socket.read_exact (&mut pad).unwrap();
}
println!("Output: {:?}", stdout);
}
And with that code, I get this output:
FCGI_END_REQUEST: [1, 3, 0, 1, 0, 8, 0, 0]
Output: "X-Powered-By: PHP/8.1.11\r\nContent-type: text/html; charset=UTF-8\r\n\r\nFirst file"
With that I really think I am getting the FCGI_END_REQUEST
record (I highly doubt that I am confusing the output and that it is not really the FCGI_END_REQUEST
record)
I'm really willing to give bounty to someone who gives an answer on things like:
Things I do wrong (or things that can be improved)
Why do I get FCGI_END_REQUEST
both reading from FCGI_STDIN
and FCGI_STDOUT
? According to Jmb I shouldn't read from FCGI_STDIN
but if I do I get the same result
Or basically, I'm willing to give a reward for a more detailed answer. If when the reward ends there is no more detailed answer I will accept my answer as correct.