📄 miniserv.pl
字号:
#!/usr/local/bin/perl# A very simple perl web server used by Webmin# Require needed libraries, including SSL if availableuse Socket;use POSIX;use Sys::Hostname;eval "use Net::SSLeay";if (!$@) { $use_ssl = 1; # These functions only exist for SSLeay 1.0 eval "Net::SSLeay::SSLeay_add_ssl_algorithms()"; eval "Net::SSLeay::load_error_strings()"; }# Find and read config fileif (@ARGV != 1) { die "Usage: miniserv.pl <config file>"; }if ($ARGV[0] =~ /^\//) { $conf = $ARGV[0]; }else { chop($pwd = `pwd`); $conf = "$pwd/$ARGV[0]"; }open(CONF, $conf) || die "Failed to open config file $conf : $!";while(<CONF>) { chop; if (/^#/ || !/\S/) { next; } /^([^=]+)=(.*)$/; $name = $1; $val = $2; $name =~ s/^\s+//g; $name =~ s/\s+$//g; $val =~ s/^\s+//g; $val =~ s/\s+$//g; $config{$name} = $val; }close(CONF);# Check vital config options%vital = ("port", 80, "root", "./", "server", "MiniServ/0.01", "index_docs", "index.html index.htm index.cgi", "addtype_html", "text/html", "addtype_txt", "text/plain", "addtype_gif", "image/gif", "addtype_jpg", "image/jpeg", "addtype_jpeg", "image/jpeg", "realm", "MiniServ" );foreach $v (keys %vital) { if (!$config{$v}) { if ($vital{$v} eq "") { die "Missing config option $v"; } $config{$v} = $vital{$v}; } }# init days and months for http_date@weekday = ( "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" );@month = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );# Change dir to the server rootchdir($config{'root'});$user_homedir = (getpwuid($<))[7];# Setup SSL if possible and if requestedif (!$config{'ssl'}) { $use_ssl = 0; }if ($use_ssl) { $ssl_ctx = Net::SSLeay::CTX_new() || die "Failed to create SSL context : $!"; }# Read MIME types file and add extra typesif ($config{"mimetypes"} ne "") { open(MIME, $config{"mimetypes"}); while(<MIME>) { chop; /^(\S+)\s+(.*)$/; $type = $1; @exts = split(/\s+/, $2); foreach $ext (@exts) { $mime{$ext} = $type; } } close(MIME); }foreach $k (keys %config) { if ($k !~ /^addtype_(.*)$/) { next; } $mime{$1} = $config{$k}; }# Read users fileif ($config{'userfile'}) { open(USERS, $config{'userfile'}); while(<USERS>) { if (/^([^:\s]+):([^:\s]+)/) { $users{$1} = $2; } } close(USERS); }# Open main socket$proto = getprotobyname('tcp');socket(MAIN, PF_INET, SOCK_STREAM, $proto) || die "Failed to open main socket : $!";setsockopt(MAIN, SOL_SOCKET, SO_REUSEADDR, pack("l", 1));$baddr = $config{"bind"} ? inet_aton($config{"bind"}) : INADDR_ANY;bind(MAIN, sockaddr_in($config{port}, $baddr)) || die "Failed to bind port $config{port} : $!";listen(MAIN, SOMAXCONN);if ($config{'listen'}) { # Open the socket that allows other webmin servers to find this one $proto = getprotobyname('udp'); if (socket(LISTEN, PF_INET, SOCK_DGRAM, $proto)) { setsockopt(LISTEN, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)); bind(LISTEN, sockaddr_in($config{'listen'}, INADDR_ANY)); listen(LISTEN, SOMAXCONN); } else { print STDERR "Failed to open listening socket : $!\n"; $config{'listen'} = 0; } }# Split from the controlling terminalif (fork()) { exit; }setsid();# write out the PID fileopen(PIDFILE, "> $config{'pidfile'}");printf PIDFILE "%d\n", getpid();close(PIDFILE);# Start the log-clearing process, if needed. This checks every minute# to see if the log has passed its reset time, and if so clears itif ($config{'logclear'}) { if (!($logclearer = fork())) { while(1) { $write_logtime = 0; if (open(LOGTIME, "$config{'logfile'}.time")) { <LOGTIME> =~ /(\d+)/; close(LOGTIME); if ($1 && $1+$config{'logtime'}*60*60 < time()){ # need to clear log $write_logtime = 1; unlink($config{'logfile'}); } } else { $write_logtime = 1; } if ($write_logtime) { open(LOGTIME, ">$config{'logfile'}.time"); print LOGTIME time(),"\n"; close(LOGTIME); } sleep(5*60); } exit; } push(@childpids, $logclearer); }# get the time zoneif ($config{'log'}) { local(@gmt, @lct, $days, $hours, $mins); @make_date_marr = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); @gmt = gmtime(time()); @lct = localtime(time()); $days = $lct[3] - $gmt[3]; $hours = ($days < -1 ? 24 : 1 < $days ? -24 : $days * 24) + $lct[2] - $gmt[2]; $mins = $hours * 60 + $lct[1] - $gmt[1]; $timezone = ($mins < 0 ? "-" : "+"); $mins = abs($mins); $timezone .= sprintf "%2.2d%2.2d", $mins/60, $mins%60; }# Run the main loop$SIG{'CHLD'} = 'reaper';$SIG{'HUP'} = 'trigger_restart';$SIG{'TERM'} = 'term_handler';$SIG{'PIPE'} = 'IGNORE';@deny = &to_ipaddress(split(/\s+/, $config{"deny"}));@allow = &to_ipaddress(split(/\s+/, $config{"allow"}));$p = 0;while(1) { # wait for a new connection, or a message from a child process undef($rmask); vec($rmask, fileno(MAIN), 1) = 1; if ($config{'passdelay'}) { for($i=0; $i<@passin; $i++) { vec($rmask, fileno($passin[$i]), 1) = 1; } } vec($rmask, fileno(LISTEN), 1) = 1 if ($config{'listen'}); local $sel = select($rmask, undef, undef, undef); if ($need_restart) { &restart_miniserv(); } next if ($sel <= 0); if (vec($rmask, fileno(MAIN), 1)) { # got new connection $acptaddr = accept(SOCK, MAIN); if (!$acptaddr) { next; } # create pipes if ($config{'passdelay'}) { $PASSINr = "PASSINr$p"; $PASSINw = "PASSINw$p"; $PASSOUTr = "PASSOUTr$p"; $PASSOUTw = "PASSOUTw$p"; $p++; pipe($PASSINr, $PASSINw); pipe($PASSOUTr, $PASSOUTw); select($PASSINw); $| = 1; select($PASSOUTw); $| = 1; } select(SOCK); $| = 1; select(STDOUT); # fork the subprocess if (!($handpid = fork())) { # setup signal handlers $SIG{'TERM'} = 'DEFAULT'; $SIG{'PIPE'} = 'DEFAULT'; #$SIG{'CHLD'} = 'DEFAULT'; $SIG{'HUP'} = 'IGNORE'; # Initialize SSL for this connection if ($use_ssl) { $ssl_con = Net::SSLeay::new($ssl_ctx); Net::SSLeay::set_fd($ssl_con, fileno(SOCK)); Net::SSLeay::use_RSAPrivateKey_file( $ssl_con, $config{'keyfile'}, &Net::SSLeay::FILETYPE_PEM); Net::SSLeay::use_certificate_file( $ssl_con, $config{'keyfile'}, &Net::SSLeay::FILETYPE_PEM); Net::SSLeay::accept($ssl_con) || exit; } # close useless pipes if ($config{'passdelay'}) { foreach $p (@passin) { close($p); } foreach $p (@passout) { close($p); } close($PASSINr); close($PASSOUTw); } close(MAIN); # Work out the hostname for this web server if (!$config{'host'}) { ($myport, $myaddr) = unpack_sockaddr_in(getsockname(SOCK)); $myname = gethostbyaddr($myaddr, AF_INET); if ($myname eq "") { $myname = inet_ntoa($myaddr); } $host = $myname; } else { $host = $config{'host'}; } $port = $config{'port'}; while(&handle_request($acptaddr)) { } close(SOCK); close($PASSINw); close($PASSOUTw); exit; } push(@childpids, $handpid); if ($config{'passdelay'}) { close($PASSINw); close($PASSOUTr); push(@passin, $PASSINr); push(@passout, $PASSOUTw); } close(SOCK); } if ($config{'listen'} && vec($rmask, fileno(LISTEN), 1)) { # Got UDP packet from another webmin server local $rcvbuf; local $from = recv(LISTEN, $rcvbuf, 1024, 0); local $fromip = inet_ntoa((unpack_sockaddr_in($from))[1]); if ((!@deny || !&ip_match($fromip, @deny)) && (!@allow || &ip_match($fromip, @allow))) { send(LISTEN, "$config{'host'}:$config{'port'}:". "$config{'ssl'}", 0, $from); } } # check for password-timeout messages from subprocesses for($i=0; $i<@passin; $i++) { if (vec($rmask, fileno($passin[$i]), 1)) { # this sub-process is asking about a password $infd = $passin[$i]; $outfd = $passout[$i]; if (<$infd> =~ /^(\S+)\s+(\S+)\s+(\d+)/) { # Got a delay request from a subprocess.. for # valid logins, there is no delay (to prevent # denial of service attacks), but for invalid # logins the delay increases with each failed # attempt. #print STDERR "got $1 $2 $3\n"; if ($3) { # login OK.. no delay print $outfd "0\n"; } else { # login failed.. $dl = $userdlay{$1} - int((time() - $userlast{$1})/50); $dl = $dl < 0 ? 0 : $dl+1; print $outfd "$dl\n"; $userdlay{$1} = $dl; } $userlast{$1} = time(); } else { # close pipe close($infd); close($outfd); $passin[$i] = $passout[$i] = undef; } } } @passin = grep { defined($_) } @passin; @passout = grep { defined($_) } @passout; }# handle_request(address)# Where the real work is donesub handle_request{$acptip = inet_ntoa((unpack_sockaddr_in($_[0]))[1]);$datestr = &http_date(time());# Read the HTTP request and headers($reqline = &read_line()) =~ s/\r|\n//g;if (!($reqline =~ /^(GET|POST)\s+(.*)\s+HTTP\/1\..$/)) { &http_error(400, "Bad Request"); }$method = $1; $request_uri = $page = $2;%header = ();while(1) { ($headline = &read_line()) =~ s/\r|\n//g; if ($headline eq "") { last; } ($headline =~ /^(\S+):\s+(.*)$/) || &http_error(400, "Bad Header"); $header{lc($1)} = $2; }if (defined($header{'host'})) { if ($header{'host'} =~ /^([^:]+):([0-9]+)$/) { $host = $1; $port = $2; } else { $host = $header{'host'}; $port = 80; } }if ($page =~ /^([^\?]+)\?(.*)$/) { # There is some query string information $page = $1; $querystring = $2; if ($querystring !~ /=/) { $queryargs = $querystring; $queryargs =~ s/\+/ /g; $queryargs =~ s/%(..)/pack("c",hex($1))/ge; $querystring = ""; } }# replace %XX sequences in page$page =~ s/%(..)/pack("c",hex($1))/ge;# check address against access listif (@deny && &ip_match($acptip, @deny) || @allow && !&ip_match($acptip, @allow)) { &http_error(403, "Access denied for $acptip"); next; }# check for the logout flag file, and if existant deny authentication onceif ($config{'logout'} && -r $config{'logout'}) { &write_data("HTTP/1.0 401 Unauthorized\r\n"); &write_data("Server: $config{server}\r\n"); &write_data("Date: $datestr\r\n"); &write_data("WWW-authenticate: Basic ". "realm=\"$config{realm}\"\r\n"); &write_data("Content-type: text/html\r\n"); &write_keep_alive(0); &write_data("\r\n"); &reset_byte_count(); &write_data("<title>Please Login</title>\n"); &write_data("<h1>Please Login</h1>\n"); &write_data("Please login to the server as a new user.<p>\n"); &log_request($acptip, undef, $reqline, 401, &byte_count()); unlink($config{'logout'}); return 0; }# Check for password if neededif (%users) { $validated = 0; if ($header{authorization} =~ /^basic\s+(\S+)$/i) { # authorization given.. ($authuser, $authpass) = split(/:/, &b64decode($1)); if ($authuser && $users{$authuser} && $users{$authuser} eq crypt($authpass, $users{$authuser})) { $validated = 1; } #print STDERR "checking $authuser $authpass -> $validated\n"; if ($config{'passdelay'}) { # check with main process for delay print $PASSINw "$authuser $acptip $validated\n"; <$PASSOUTr> =~ /(\d+)/; #print STDERR "sleeping for $1\n"; sleep($1); } } if (!$validated) { # No password given.. ask &write_data("HTTP/1.0 401 Unauthorized\r\n"); &write_data("Server: $config{'server'}\r\n"); &write_data("Date: $datestr\r\n"); &write_data("WWW-authenticate: Basic ". "realm=\"$config{'realm'}\"\r\n"); &write_data("Content-type: text/html\r\n"); &write_keep_alive(0); &write_data("\r\n"); &reset_byte_count(); &write_data("<title>Unauthorized</title>\n"); &write_data("<h1>Unauthorized</h1>\n"); &write_data("A password is required to access this\n"); &write_data("web server. Please try again. <p>\n"); &log_request($acptip, undef, $reqline, 401, &byte_count()); return 0; } }# Figure out what kind of page was requested$simple = &simplify_path($page, $bogus);if ($bogus) { &http_error(400, "Invalid path"); }$sofar = ""; $full = $config{"root"} . $sofar;$scriptname = $simple;foreach $b (split(/\//, $simple)) { if ($b ne "") { $sofar .= "/$b"; } $full = $config{"root"} . $sofar; @st = stat($full); if (!@st) { &http_error(404, "File not found"); } # Check if this is a directory if (-d $full) { # It is.. go on parsing next; } # Check if this is a CGI program if (&get_type($full) eq "internal/cgi") { $pathinfo = substr($simple, length($sofar)); $pathinfo .= "/" if ($page =~ /\/$/); $scriptname = $sofar; last; } }# Reached the end of the path OK.. see what we've gotif (-d $full) { # See if the URL ends with a / as it should if ($page !~ /\/$/) { # It doesn't.. redirect &write_data("HTTP/1.0 302 Moved Temporarily\r\n"); $portstr = $port == 80 && !$use_ssl ? "" : $port == 443 && $use_ssl ? "" : ":$port"; &write_data("Date: $datestr\r\n"); &write_data("Server: $config{server}\r\n"); $prot = $use_ssl ? "https" : "http"; &write_data("Location: $prot://$host$portstr$page/\r\n"); &write_keep_alive(0); &write_data("\r\n"); &log_request($acptip, $authuser, $reqline, 302, 0);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -