summaryrefslogtreecommitdiffstats
path: root/web/server/h2o/libh2o/misc/fastcgi-cgi.pl
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xweb/server/h2o/libh2o/misc/fastcgi-cgi.pl256
1 files changed, 256 insertions, 0 deletions
diff --git a/web/server/h2o/libh2o/misc/fastcgi-cgi.pl b/web/server/h2o/libh2o/misc/fastcgi-cgi.pl
new file mode 100755
index 00000000..98ee3321
--- /dev/null
+++ b/web/server/h2o/libh2o/misc/fastcgi-cgi.pl
@@ -0,0 +1,256 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+use File::Basename qw(dirname);
+use File::Temp qw(tempfile);
+use Getopt::Long;
+use IO::Socket::UNIX;
+use Net::FastCGI;
+use Net::FastCGI::Constant qw(:common :type :flag :role :protocol_status);
+use Net::FastCGI::IO qw(:all);
+use Net::FastCGI::Protocol qw(:all);
+use POSIX qw(:sys_wait_h getcwd);
+use Socket qw(SOMAXCONN SOCK_STREAM);
+
+my $master_pid = $$;
+my %child_procs;
+
+$SIG{CHLD} = sub {};
+$SIG{HUP} = sub {};
+$SIG{TERM} = sub {
+ if ($$ == $master_pid) {
+ kill "TERM", $_
+ for sort keys %child_procs;
+ }
+ exit 0;
+};
+
+my $base_dir = getcwd;
+chdir "/"
+ or die "failed to chdir to /:$!";
+main();
+my $pass_authz;
+
+sub main {
+ my $sockfn;
+ my $max_workers = "inf" + 1;
+
+ GetOptions(
+ "listen=s" => \$sockfn,
+ "max-workers=i" => \$max_workers,
+ "pass-authz" => \$pass_authz,
+ "help" => sub {
+ print_help();
+ exit 0;
+ },
+ ) or exit 1;
+
+ my $listen_sock;
+ if (defined $sockfn) {
+ unlink $sockfn;
+ $listen_sock = IO::Socket::UNIX->new(
+ Listen => SOMAXCONN,
+ Local => $sockfn,
+ Type => SOCK_STREAM,
+ ) or die "failed to create unix socket at $sockfn:$!";
+ } else {
+ die "stdin is no a socket"
+ unless -S STDIN;
+ $listen_sock = IO::Socket::UNIX->new;
+ $listen_sock->fdopen(fileno(STDIN), "w")
+ or die "failed to open unix socket:$!";
+ }
+
+ while (1) {
+ my $wait_opt = 0;
+ if (keys %child_procs < $max_workers) {
+ if (my $sock = $listen_sock->accept) {
+ my $pid = fork;
+ die "fork failed:$!"
+ unless defined $pid;
+ if ($pid == 0) {
+ close $listen_sock;
+ handle_connection($sock);
+ exit 0;
+ }
+ $sock->close;
+ $child_procs{$pid} = 1;
+ }
+ $wait_opt = WNOHANG;
+ } else {
+ $wait_opt = 0;
+ }
+ my $kid = waitpid(-1, $wait_opt);
+ if ($kid > 0) {
+ delete $child_procs{$kid};
+ }
+ }
+}
+
+sub handle_connection {
+ my $sock = shift;
+ my ($type, $req_id, $content);
+ my $cur_req_id;
+ my $params = "";
+ my $input_fh;
+
+ # wait for FCGI_BEGIN_REQUEST
+ ($type, $req_id, $content) = fetch_record($sock);
+ die "expected FCGI_BEGIN_REQUEST, but got $type"
+ unless $type == FCGI_BEGIN_REQUEST;
+ my ($role, $flags) = parse_begin_request_body($content);
+ die "unexpected role:$role"
+ unless $role == FCGI_RESPONDER;
+ $cur_req_id = $req_id;
+
+ # accumulate FCGI_PARAMS
+ while (1) {
+ ($type, $req_id, $content) = fetch_record($sock);
+ last if $type != FCGI_PARAMS;
+ die "unexpected request id"
+ if $cur_req_id != $req_id;
+ $params .= $content;
+ }
+ my $env = parse_params($params);
+ die "SCRIPT_FILENAME not defined"
+ unless $env->{SCRIPT_FILENAME};
+ $env->{SCRIPT_FILENAME} = "$base_dir/$env->{SCRIPT_FILENAME}"
+ if $env->{SCRIPT_FILENAME} !~ m{^/};
+ delete $env->{HTTP_AUTHORIZATION}
+ unless $pass_authz;
+
+ # accumulate FCGI_STDIN
+ while (1) {
+ die "received unexpected record: $type"
+ if $type != FCGI_STDIN;
+ die "unexpected request id"
+ if $cur_req_id != $req_id;
+ last if length $content == 0;
+ if (!$input_fh) {
+ $input_fh = tempfile()
+ or die "failed to create temporary file:$!";
+ }
+ print $input_fh $content;
+ ($type, $req_id, $content) = fetch_record($sock);
+ }
+ if ($input_fh) {
+ flush $input_fh;
+ seek $input_fh, 0, 0
+ or die "seek failed:$!";
+ } else {
+ open $input_fh, "<", "/dev/null"
+ or die "failed to open /dev/null:$!";
+ }
+
+ # create pipes for stdout and stderr
+ pipe(my $stdout_rfh, my $stdout_wfh)
+ or die "pipe failed:$!";
+ pipe(my $stderr_rfh, my $stderr_wfh)
+ or die "pipe failed:$!";
+
+ # fork the CGI application
+ my $pid = fork;
+ die "fork failed:$!"
+ unless defined $pid;
+ if ($pid == 0) {
+ close $sock;
+ close $stdout_rfh;
+ close $stderr_rfh;
+ open STDERR, ">&", $stderr_wfh
+ or die "failed to dup STDERR";
+ open STDIN, "<&", $input_fh
+ or die "failed to dup STDIN";
+ open STDOUT, ">&", $stdout_wfh
+ or die "failed to dup STDOUT";
+ close $stderr_wfh;
+ close $input_fh;
+ close $stdout_wfh;
+ $ENV{$_} = $env->{$_}
+ for sort keys %$env;
+ chdir dirname($env->{SCRIPT_FILENAME});
+ exec $env->{SCRIPT_FILENAME};
+ exit 111;
+ }
+ close $stdout_wfh;
+ close $stderr_wfh;
+
+ # send response
+ while ($stdout_rfh || $stderr_rfh) {
+ my $rin = '';
+ vec($rin, fileno $stdout_rfh, 1) = 1
+ if $stdout_rfh;
+ vec($rin, fileno $stderr_rfh, 1) = 1
+ if $stderr_rfh;
+ vec($rin, fileno $sock, 1) = 1;
+ if (select($rin, undef, undef, undef) <= 0) {
+ next;
+ }
+ if ($stdout_rfh && vec($rin, fileno $stdout_rfh, 1)) {
+ transfer($sock, FCGI_STDOUT, $cur_req_id, $stdout_rfh)
+ or undef $stdout_rfh;
+ }
+ if ($stderr_rfh && vec($rin, fileno $stderr_rfh, 1)) {
+ transfer($sock, FCGI_STDERR, $cur_req_id, $stderr_rfh)
+ or undef $stderr_rfh;
+ }
+ if (vec($rin, fileno $sock, 1)) {
+ # atually means that the client has closed the connection, terminate the CGI process the same way apache does
+ kill 'TERM', $pid;
+ $SIG{ALRM} = sub {
+ kill 'KILL', $pid;
+ };
+ alarm 3;
+ last;
+ }
+ }
+
+ # close (closing without sending FCGI_END_REQUEST indicates to the client that the connection is not persistent)
+ close $sock;
+
+ # wait for child process to die
+ while (waitpid($pid, 0) != $pid) {
+ }
+}
+
+sub fetch_record {
+ my $sock = shift;
+ my ($type, $req_id, $content) = read_record($sock)
+ or die "failed to read FCGI record:$!";
+ die "unexpected request id:null"
+ if $req_id == FCGI_NULL_REQUEST_ID;
+ ($type, $req_id, $content);
+}
+
+sub transfer {
+ my ($sock, $type, $req_id, $fh) = @_;
+ my $buf;
+
+ while (1) {
+ my $ret = sysread $fh, $buf, 61440;
+ next if (!defined $ret) && $! == Errno::EINTR;
+ $buf = "" unless $ret; # send zero-length record to indicate EOS
+ last;
+ }
+ write_record($sock, $type, $req_id, $buf)
+ or die "failed to write FCGI response:$!";
+ return length $buf;
+}
+
+sub print_help {
+ # do not use Pod::Usage, since we are fatpacking this script
+ print << "EOT";
+Usage:
+ $0 [options]
+
+Options:
+ --listen=sockfn path to the UNIX socket. If specified, the program will
+ create a UNIX socket at given path replacing the existing
+ file (should it exist). If not, file descriptor zero (0)
+ will be used as the UNIX socket for accepting new
+ connections.
+ --max-workers=nnn maximum number of CGI processes (default: unlimited)
+ --pass-authz if set, preserves HTTP_AUTHORIZATION parameter
+
+EOT
+}