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.11rnContent-type: text/html;
charset=UTF-8rnrnFirst 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:
"
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:
""
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: ""
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.
2
Answers
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 writeFCGI_STDOUT
just once and before doing theloop
. Then I create theresponseHeader
variable and read the data. And then, thanks to Jmb's answer, I read the padding after reading the response body and readresponseHeader[6]
and notresponseHeader[7]
(as I did before). So my code looks like this:And with that code, I get this output:
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 theFCGI_END_REQUEST
record)About the bounty
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 fromFCGI_STDIN
andFCGI_STDOUT
? According to Jmb I shouldn't read fromFCGI_STDIN
but if I do I get the same resultOr 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.
There are several issues with your code:
FCGI_STDOUT
messages in your reading loop. According to table A in the spec, you should never write such a message:FCGI_STDOUT
messages should only go from the "App" (i.e. php-fpm or whatever FCGI application you’re talking to) to the WS (i.e. your program).FCGI_STDOUT
messages are sent: you don’t need to do anything to request the output, instead the "App" will sendFCGI_STDOUT
messages spontaneously whenever they have something to say.FCGI_Record
: the content comes before the padding). In fact you’re not reading the content at all (you’re missing a call tosocket.read_exact (&mut responseBody)
).According to your traces,
php-fpm
is sending you a singleFCGI_STDOUT
message that contains 78 bytes of data followed by 2 bytes of padding. The data starts with88, 45, 80, 111, 119, 101, 114, 101, 100, 45
which translates to "X-Powered-". But you skip the first two bytes as if they were padding, you never read the message body, and then you read the next 8 bytes as if they were a new message header. When you attempt to extract a type from these 8 bytes, you get an invalid value and your program errors out.Fixed code (untested):