skip to Main Content

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 (&paramsRequest).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 (&paramsRequest).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


  1. Chosen as BEST ANSWER

    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 (&paramsRequest).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.11rnContent-type: text/html; charset=UTF-8rnrnFirst 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)

    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 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.


  2. There are several issues with your code:

    • You’re writing several 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).
    • This stems from a misconception about how and when FCGI_STDOUT messages are sent: you don’t need to do anything to request the output, instead the "App" will send FCGI_STDOUT messages spontaneously whenever they have something to say.
    • You’re reading the padding and content in the wrong order (see the definition of FCGI_Record: the content comes before the padding). In fact you’re not reading the content at all (you’re missing a call to socket.read_exact (&mut responseBody)).

    According to your traces, php-fpm is sending you a single FCGI_STDOUT message that contains 78 bytes of data followed by 2 bytes of padding. The data starts with 88, 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):

    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 (&paramsRequest).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();
    
        // get the response
        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");
                    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();
            output.push_str(&String::from_utf8_lossy(&responseBody));
    
            // read the padding
            let mut pad = vec![0; responseHeader[7] as usize];
            socket.read_exact (&mut pad).unwrap();
        }
        
        println!("Output: {:?}", output);
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search