#!/bin/sh # vim: set cindent expandtab ts=4 sw=4: exec ${PERL-perl} -Swx $0 ${1+"$@"} #!/usr/bin/perl -w # dispatch.fcgi - a small script to make common cgi into fast # cgi progarme, reduce the forking overhead # # Author: He zhiqiang # Date: 27 Dec 2005 # Update: 30 May 2006 # # Support: Apache 2.x or lighttpd 1.3.x/1.4.x use vars qw(%cache $root %CHILDREN $FILENAME); use POSIX qw(setlocale LC_ALL setsid WNOHANG); use Getopt::Long; my $debug = 0; %cache = (); $FILENAME = "dispatch.fcgi"; BEGIN { $root = $ENV{SCRIPT_FILENAME} || $0; if ($root =~/^\./) { print "Please run dispatch.fcgi with full path\n"; print " example: /path/to/dispatch.fcgi\n"; exit (255); } $root =~ s#/*[^/]+$##; $root =~ s#/(extmail|extman)$/*$##; $root =~ m/^(.*)$/s; $root = $1; # untaint unshift @INC, "$1/extmail/libs"; require Ext::FCGI; } # initialize locale setlocale(LC_ALL, "C"); my %opt; Getopt::Long::Configure('no_ignore_case'); GetOptions(\%opt, 'help|h', 'port|p=i', 'child|c=i', 'server|s', 'uid|u=s', 'gid|g=s', 'pid=s', 'request|r=i', 'timeout=i') or exit(1); if($opt{help}) { print "usage: /path/to/dispatch.fcgi [*option*]\n\n"; print " -h, --help show this usage\n"; print " --port=PORT FCGI server bind port, eg:8888\n"; print " --child=NUMB number of children to prefork\n"; print " --request=NUMB number of requests a child to handle\n"; print " --timeout=NUMB seconds to wait for request timeout\n"; print " --server run as FCGI server, default off\n"; print " -u, --uid set real and effective user ID\n"; print " -g, --gid set real and effective group ID\n"; print " --pid=file the pid file of parent process\n"; exit (1); } if ($opt{server}) { $SIG{CHLD} = \&reap_child; $SIG{TERM} = \&kill_all; daemonize() if ($opt{server}); my $socket = FCGI::OpenSocket( ":$opt{port}", 5 ); my $request = FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket ); open (my $_fh, "< $0") or die $!; set_master(); if ($opt{uid} && $opt{gid}) { set_gid($opt{gid}); set_uid($opt{uid}); } while (1) { defined ( my $child = fcgi_fork() ) or die "$@"; if ($child == 0) { set_idle(); main_loop($request, $_fh); exit (0); } else { $CHILDREN{$child} = 1; } } close $_fh; } else { set_idle(); my $request = Ext::FCGI::Request(); main_loop($request); } # # main_loop - the core function for fcgi sub main_loop { my $request = shift; my $lock = shift; my $count = 0; while (Ext::FCGI::accept($request, $lock)>=0) { my $file = request_file(); set_busy(); print "content-type: text/html\r\n\r\n" if ($debug); my $last_alarm = alarm($opt{timeout}||120); # XXX begin eval() and timeout detection eval { local $SIG{ALRM} = sub { die "System Timeout or busy\n" }; if (cached($file)) { print "$file cached\n" if ($debug); compile($cache{$file}->{code}); } else { print "first time run $file\n" if ($debug); my $code = file2code($file); $cache{$file}->{code} = $code; $cache{$file}->{mtime} = -M $file; compile($code); } if ($@) { print "content-type: text/html\r\n\r\n"; print "Error: $@\n"; } }; # XXX end of timeout detection alarm($last_alarm); Ext::FCGI::request_cleanup; set_idle(); $count++; # exit main loop to end child process, free # memory and other resources last if $count >= ($opt{request}||100); } } # XXX end of main_loop # request_file - initialize file path and ENV sub request_file { my $file = $ENV{SCRIPT_FILENAME}; # we get PATH_INFO ? possible it's Apache if (my $path = $ENV{PATH_INFO}) { my $sname = $ENV{SCRIPT_NAME}; $sname =~ s#^/+##; # remove /extmail/cgi => extmail/cgi $path =~ s#^/+##; # remove /index.cgi => index.cgi $file = "$root/$sname/$path"; # or it's lighttpd, well we just guess :D } else { $file = $ENV{SCRIPT_NAME}; $file =~ s!^/!!; $file = "$root/$file"; } $ENV{SCRIPT_FILENAME} = $file; $file; } sub cached { my $file = shift; if ($cache{$file}) { my $mtime = $cache{$file}->{mtime}; if (-M $file >= $mtime) { return 1; } } else { return 0; } } sub compile { my $code = shift; $code =~ m/^(.*)$/s; eval $1; } sub file2code { my $file = shift; if (-r $file) { open (FD, "< $file") or die "$!\n"; local $/ = undef; my $code = ; close FD; return $code; } else { return "print \"content-type: text/html\r\n\r\nRequest file $file not exists\""; } } # # Multi process fastcgi server functions sub set_busy { $0 = "$FILENAME (busy)"; } sub set_idle { $0 = "$FILENAME (idle)"; } sub set_master { $0 = "$FILENAME (master)"; } sub fcgi_fork { sleep while((scalar keys %CHILDREN) >= $opt{child}); return fork; } sub reap_child { while( (my $pid = waitpid(-1, WNOHANG)) > 0 ) { next unless $pid; delete $CHILDREN{$pid}; } } sub kill_all { for my $pid (keys %CHILDREN) { next unless kill 0, $pid; # if it's alive kill 9, $pid; } 1 while waitpid(-1, WNOHANG) > 0; exit 0; } sub daemonize { open STDIN, '/dev/null' or die "mailgraph: can't read /dev/null: $!"; open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!"; defined(my $pid = fork) or die "Can't fork: $!"; if($pid) { # parent my $pidfile = $opt{pid} || "$0.pid"; open PIDFILE, "> $pidfile" or die "Can't write to $0.pid: $!\n"; print PIDFILE "$pid\n"; close(PIDFILE); exit; } # child setsid or die "Can't start a new session: $!"; open STDERR, '>&STDOUT' or die "Can't dup stdout: $!"; } # the following functions derive from suid-perl sub numberp { defined $_[0] && $_[0] =~ m/^-?\d+$/o; } sub group2gid { my $g = shift; return $g if numberp ($g); my $gid = getgrnam ($g); return $gid if defined $gid && numberp ($gid); die "no such group: $g"; } sub user2uid { my $u = shift; return $u if numberp ($u); my $uid = getpwnam ($u); return $uid if defined $uid && numberp ($uid); die "no such user: $u"; } sub set_gid { my $sgid = group2gid (shift); my $rgid = $( + 0; my $egid = $) + 0; $( = $sgid; $) = $sgid; die "cannot set rgid $sgid: $!\n" if ($( == $rgid && $rgid != $sgid); die "cannot set egid $sgid: $!\n" if ($) == $egid && $egid != $sgid); } sub set_uid { my $suid = user2uid (shift); my $ruid = $<; my $euid = $>; $< = $suid; $> = $suid; die "cannot set ruid $suid: $!\n" if ($< == $ruid && $ruid != $suid); die "cannot set euid $suid: $!\n" if ($> == $euid && $euid != $suid); } 1; __END__ I wrote this programe for extmail project, the mechanism derive from Embed::Persistent, using eval() and FCGI, it works :-)