缘起
在探索到CGI到底为何物时,google SimpleCGI的时候,看到这篇文章,粗看了几眼,觉得不错,有几个感兴趣的topic:CGI,FastCGI,Nginx。
正文
For the most part,lack of CGI support in Nginx is not an issue and actually has an important side-benefit: because Nginx cannot directly execute external programs (CGI), a malicious person can't trick your system into uploading and executing an arbitrary script.
大体而言,Nginx中缺乏对CGI的支持不是问题,实际上还带来一些好处:因为Nginx不直接执行外部程序(CGI),心怀不轨的人不能通过上传并执行任意脚本来攻击你的系统。
There are still ways, of course (uploading a PHP script into a directory where you've got a directive to execute PHP FastCGI scripts for example), but it does in fact make it a bit more difficult (or conversely, easier to secure).
当然,依然存在一些攻击系统的方法,比如上传PHP脚本到某个目录,但你定向到给目录时,执行PHP FastCGI脚本,但这显然更加困难一些。
Still, sometimes you have a one-off simple CGI program you need to support and here's a recipe for exposing your CGI to Nginx as a FastCGI instead:
有时,需要支持一次性的CGI脚本。这里介绍一个将CGI程序转换为FastCGI然后传递给Nginx的方法:
Let's say this is our CGI program, written in Perl:
下面介绍一下这个Perl编写的CGI程序:
#!/usr/bin/perl
print "Content-type: text/html\n\n";
print "<html><body>Hello, world.</body></html>";
Make sure that the web server can read the file and it is marked as executable.
确保web服务器能够读取并将其看作可执行文件。
Next, we need a Perl wrapper that will run as a FastCGI and run this for us:
接下来,需要一个运行为FastCGI的Perl包装器来运行这个程序:
This one is still pretty ugly, but forwards stdio around correctly (not necessarily efficiently or prettily), and also reads and spits back out STDERR into fcgi STDERR. I am not entirely confident there is no race condition in the event that an app STDERRS out without any output data, but it works for my purposes. This is for the most part, not my own work; 99.9% of this came from the various improvements on the spawner script worked on in the mailing list and wiki here.. I just added some more pipe handling to enable communicating through STDERR, and a select loop to properly recieve both data streams. -- bryon@spideroak.com
下面的这个包装器相当的简陋,但在stdio上大体正确(不需要高效或完美),也可以将标准STDERR读取和输出为fcgi的STDERR。不确定代码中在应用中没有任何输出数据的STDERRS中是否存在竞争条件,这个包装器很好满足了我的目标。包装器中99.9%的部分来自邮件列表和wiki,我(原作者)只添加了一些管道处理从而确保能够通过STDERR进行通信,以及一个接受合适的数据流的选择循环。
#!perl
use FCGI;
use Socket;
use FCGI::ProcManager;
sub shutdown { FCGI::CloseSocket($socket); exit; }
sub restart { FCGI::CloseSocket($socket); &main; }
use sigtrap 'handler', \&shutdown, 'normal-signals';
use sigtrap 'handler', \&restart, 'HUP';
require 'syscall.ph';
use POSIX qw(setsid);
END() { }
BEGIN() { }
{
no warnings;
*CORE::GLOBAL::exit = sub { die "fakeexit\nrc=" . shift() . "\n"; };
};
eval q{exit};
if ($@) {
exit unless $@ =~ /^fakeexit/;
}
&main;
sub daemonize() {
chdir '/' or die "Can't chdir to /: $!";
defined( my $pid = fork ) or die "Can't fork: $!";
exit if $pid;
setsid() or die "Can't start a new session: $!";
umask 0;
}
sub main {
$proc_manager = FCGI::ProcManager->new( {n_processes => 5} );
$socket = FCGI::OpenSocket( "/var/run/nginx/cgiwrap-dispatch.sock", 10 )
; #use UNIX sockets - user running this script must have w access to the 'nginx' folder!!
$request =
FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%req_params, $socket,
&FCGI::FAIL_ACCEPT_ON_INTR );
$proc_manager->pm_manage();
if ($request) { request_loop() }
FCGI::CloseSocket($socket);
}
sub request_loop {
while ( $request->Accept() >= 0 ) {
$proc_manager->pm_pre_dispatch();
#processing any STDIN input from WebServer (for CGI-POST actions)
$stdin_passthrough = '';
{ no warnings; $req_len = 0 + $req_params{'CONTENT_LENGTH'}; };
if ( ( $req_params{'REQUEST_METHOD'} eq 'POST' ) && ( $req_len != 0 ) ) {
my $bytes_read = 0;
while ( $bytes_read < $req_len ) {
my $data = '';
my $bytes = read( STDIN, $data, ( $req_len - $bytes_read ) );
last if ( $bytes == 0 || !defined($bytes) );
$stdin_passthrough .= $data;
$bytes_read += $bytes;
}
}
#running the cgi app
if (
( -x $req_params{SCRIPT_FILENAME} ) && #can I execute this?
( -s $req_params{SCRIPT_FILENAME} ) && #Is this file empty?
( -r $req_params{SCRIPT_FILENAME} ) #can I read this file?
) {
pipe( CHILD_RD, PARENT_WR );
pipe( PARENT_ERR, CHILD_ERR );
my $pid = open( CHILD_O, "-|" );
unless ( defined($pid) ) {
print("Content-type: text/plain\r\n\r\n");
print "Error: CGI app returned no output - Executing $req_params{SCRIPT_FILENAME} failed !\n";
next;
}
$oldfh = select(PARENT_ERR);
$| = 1;
select(CHILD_O);
$| = 1;
select($oldfh);
if ( $pid > 0 ) {
close(CHILD_RD);
close(CHILD_ERR);
print PARENT_WR $stdin_passthrough;
close(PARENT_WR);
$rin = $rout = $ein = $eout = '';
vec( $rin, fileno(CHILD_O), 1 ) = 1;
vec( $rin, fileno(PARENT_ERR), 1 ) = 1;
$ein = $rin;
$nfound = 0;
while ( $nfound = select( $rout = $rin, undef, $ein = $eout, 10 ) ) {
die "$!" unless $nfound != -1;
$r1 = vec( $rout, fileno(PARENT_ERR), 1 ) == 1;
$r2 = vec( $rout, fileno(CHILD_O), 1 ) == 1;
$e1 = vec( $eout, fileno(PARENT_ERR), 1 ) == 1;
$e2 = vec( $eout, fileno(CHILD_O), 1 ) == 1;
if ($r1) {
while ( $bytes = read( PARENT_ERR, $errbytes, 4096 ) ) {
print STDERR $errbytes;
}
if ($!) {
$err = $!;
die $!;
vec( $rin, fileno(PARENT_ERR), 1 ) = 0
unless ( $err == EINTR or $err == EAGAIN );
}
}
if ($r2) {
while ( $bytes = read( CHILD_O, $s, 4096 ) ) {
print $s;
}
if ( !defined($bytes) ) {
$err = $!;
die $!;
vec( $rin, fileno(CHILD_O), 1 ) = 0
unless ( $err == EINTR or $err == EAGAIN );
}
}
last if ( $e1 || $e2 );
}
close CHILD_RD;
close PARENT_ERR;
waitpid( $pid, 0 );
} else {
foreach $key ( keys %req_params ) {
$ENV{$key} = $req_params{$key};
}
# cd to the script's local directory
if ( $req_params{SCRIPT_FILENAME} =~ /^(.*)\/[^\/] +$/ ) {
chdir $1;
}
close(PARENT_WR);
#close(PARENT_ERR);
close(STDIN);
close(STDERR);
#fcntl(CHILD_RD, F_DUPFD, 0);
syscall( &SYS_dup2, fileno(CHILD_RD), 0 );
syscall( &SYS_dup2, fileno(CHILD_ERR), 2 );
#open(STDIN, "<&CHILD_RD");
exec( $req_params{SCRIPT_FILENAME} );
die("exec failed");
}
} else {
print("Content-type: text/plain\r\n\r\n");
print "Error: No such CGI app - $req_params{SCRIPT_FILENAME} may not exist or is not executable by this process.\n";
}
}
}
Save the above script as /usr/local/bin/cgiwrap-fcgi.pl.
将上述文件保存为/usr/local/bin/cgiwrap-fcgi.pl。