added remove button
[pxelator] / lib / PXElator / httpd.pm
index 331569a..8473665 100644 (file)
@@ -17,117 +17,327 @@ use Carp qw/confess/;
 use File::Slurp;
 #use JSON;
 use IO::Socket::INET;
-use Module::Refresh;
+use Regexp::Common qw/net/;
+
+sub menu {qq{
+
+<div style="font-size: 80%; color: #888">
+<a href=/>home</a>
+<a href=/server>server</a>
+<a href=/brctl>brctl</a>
+<a href=/ip>ip</a>
+<a href=/nmap>nmap</a>
+<a href=/client>client</a>
+</div>
+
+}}
 
 our $port = 7777;
-our $debug = 1;
 
 use server;
+our $debug = server::debug;
 our $url = "http://$server::ip:$port";
 
 use html;
+our $static_pids;
+use progress_bar;
+use config;
+use client;
+use log;
+use x11;
+use amt;
+use boolean;
+use daemons;
+
+use kvm;
+use browser;
+use network;
+use ip;
+use wireshark;
+use syslogd;
+use nmap;
+
+use CouchDB;
 
 sub static {
        my ($client,$path) = @_;
 
-       $path = "tftp/$path";
+       my $full = "$server::base_dir/tftp/$path";
 
-       if ( ! -e $path ||  -d $path ) {
-               print $client "HTTP/1.0 404 $path not found\r\n";
-               return;
+       return if ! -f $full;
+
+       if ( my $pid = fork ) {
+               # parent
+               close($client);
+               $static_pids->{$pid} = $path;
+               return 1;
        }
 
-       my $type = 'text/plain';
+       my $type = 'application/octet-stream';
        $type = 'text/html' if $path =~ m{\.htm};
        $type = 'application/javascript' if $path =~ m{\.js};
+       $type = 'text/plain' if $path =~ m{\.txt};
 
-       print $client "HTTP/1.0 200 OK\r\nContent-Type: $type\r\nContent-Length: ", -s $path,"\r\n\r\n";
-       open(my $html, $path);
-       while(<$html>) {
-               print $client $_;
-       }
-       close($html);
+       my $size = -s $full || return;
 
-       return $path;
-}
+       print $client "HTTP/1.0 200 OK\r\nContent-Type: $type\r\nContent-Length: $size\r\nConnection: close\r\n\r\n";
 
-my $ok = "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n";
+       open(my $fh, $full);
 
-use boolean;
+       my $block = 1400; # try not to fragment packages (pxelinux seems to have problems with it)
+       my $buff;
+       my $pos = 0;
 
-use screen;
-use kvm;
-our $pids;
+       CouchDB::audit( 'static', { pid => $$, path => $path, type => $type, size => $size, block => $block, peerhost => $client->peerhost });
 
-$SIG{CHLD} = 'IGNORE';
+       progress_bar::start;
 
-sub start_stop {
-       my $daemon = shift;
-       my $pid = $pids->{$daemon};
+       while( my $len = read $fh, $buff, $block ) {
+               print $client $buff;
+               $client->flush;
+               $pos += $len;
+               progress_bar::tick( $path, $pos, $size );
+       }
+       close($fh);
+       close($client);
 
-       warn "start_stop $daemon pids: ",dump( $pids );
+       print STDERR "\n";
 
-       if ( $pid ) {
-               warn "kill 9 $pid";
-               kill 9, $pid;
-               delete $pids->{$daemon};
-               return qq|$daemon pid $pid stopped|;
-       } else {
-               if ( $pid = fork ) {
-                       # parent
-                       $pids->{$daemon} = $pid;
-                       warn "forked $daemon $pid";
-                       return qq|$daemon pid $pid started|;
-               } elsif ( defined $pid ) {
-                       # child
-                       my $eval = $daemon . '::start';
-                       warn "eval $eval";
-                       eval $eval;
-                       warn "can't start $daemon: $@" if $@;
-                       exit;
-               } else {
-                       die "fork error $!";
-               }
-       }
+       exit(0);
+}
+
+sub ok {
+       qq|HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n| . menu()
+}
+
+sub redirect {
+       my $to = shift;
+       $to ||= $url;
+       qq|HTTP/1.1 302 Found\r\nContent-type: text/html\r\nLocation: $to\r\n\r\n|
 }
 
 sub get_request {
        my ( $client, $path, $param ) = @_;
 
-       warn "get_request $client $path ",dump( $param );
+       server->refresh;
+
+       CouchDB::audit( 'request', { path => $path, param => $param, peerhost => $client->peerhost } );
 
        if ( my $found = static( $client,$path ) ) {
                warn "static $found" if $debug;
        } elsif ( $path eq '/' ) {
 
-               my $screen = $pids->{screen} ? qq|stop <tt>$pids->{screen}</tt>|        : 'start';
-               my $kvm    = $pids->{kvm}    ? qq|stop <tt>$pids->{kvm}</tt>|           :
-                                        $pids->{screen} ? qq|start|                                                    : qq|start screen first|;
+               my @rows;
+
+               my $debug_proc = '';
+
+warn "XXX pids = ", dump( $daemons::pids );
+
+               foreach my $name ( sort keys %$daemons::pids ) {
+                       my $pid = $daemons::pids->{$name}; # || next;
+
+                       my $html;
+
+                       my $proc = "/proc/$pid/status";
+
+                       if ( -e $proc ) {
+                               $html .= qq|<a href=/start_stop/$name>$pid</a>|;
+                               if ( $debug ) {
+                                       $html .= qq| <a name=$pid href=#proc-$pid>?</a>| if $name->can('start');
+
+                                       $debug_proc
+                                               .= qq|<a name=proc-$pid href=#$pid>$proc</a><pre style="font-size: 10%">|
+                                               .  read_file($proc)
+                                               .  qq|</pre>|
+                                               ;
+                               }
+
+                               if ( $name->can('fork_if_active') ) {
+                                       $html .= qq| <a href=/start_stop/$name/$_>$_</a>| foreach $name->fork_if_active;
+                               }
+
+                               if ( $name->can('actions') ) {
+                                       $html .= qq| <a href=/action/$name/$_>$_</a>| foreach $name->actions;
+                               }
+                       } else {
+                               if ( $pid =~ m{^\d+$} ) {
+                                       $html .= qq|$pid exited |
+                               } else {
+                                       $html .= qq|$pid |;
+                               }
+                               $html .= qq|<a href=/start_stop/$name>restart</a>| if $pid || $name->can('start');
+                               if ( $name->can('fork_actions') ) {
+                                       $html .= qq| <a href=/start_stop/$name/$_>$_</a>| foreach $name->fork_actions;
+                               }
+                       }
+
+                       die "no html generated" unless $html;
+
+                       push @rows, ( $name => $html );
+               }
+
+               my $below_table = '';
+
+               warn 'static_pids: ', dump( $static_pids ) if $debug;
+               foreach my $pid ( keys %$static_pids ) {
+                       my $path = $static_pids->{$pid};
+                       if ( -d "/proc/$pid" ) {
+                               push @rows, ( $path => qq|<a href=/kill/static/$pid>$pid</a>| );
+                       } elsif ( $param->{clean_completed_downloads} ) {
+                               delete $static_pids->{$pid}
+                       } else {
+                               push @rows, ( $path => "$pid competed" );
+                               $below_table = qq|<a href="/?clean_completed_downloads=1">clean completed downloads</a>|;
+                       }
+               }
+
+               print $client ok
+                       , html::table( 2, @rows )
+                       , $below_table
+                       , html::tabs( log::mac_changes )
+                       , $debug_proc
+                       ;
+
+       } elsif ( $path =~ m{^/server} ) {
+               print $client ok
+                       , html::table( 2,
+                               'debug' => qq|<a href=/our/debug/| . boolean::toggle($debug) . qq|>$debug</a>|,
+                                map {
+                                       ( $_, html::tt eval '$server::'.$_ )
+                                } ( 'ip', 'netmask', 'ip_from', 'ip_to', 'domain_name', 'base_dir', 'conf' )
+                       )
+                       ;
+       } elsif ( $path =~ m!^/client(?:/$RE{net}{IPv4}{-keep})?! ) {
+               my $ip = $1;
+
+               if ( $param->{action} eq 'remove' ) {
+                       client::remove( $param->{change_ip} );
+                       print $client redirect("$url/client");
+                       return;
+               } elsif ( $param->{action} eq 'change' ) {
+                       if ( my $new_ip = client::change_ip( $ip, $param->{change_ip} ) ) {
+                               print $client redirect("$url/client#$new_ip");
+                               return;
+                       }
+               }
+
+               if ( ! $ip ) {
+                       my $peer_ip = $client->peerhost;
+
+                       my $netmask = ip::to_int $server::netmask;
+                       my $network = ip::to_int $server::ip & $netmask;
+                       my ( $from, $to ) = ( $network | $server::ip_from, $network | $server::ip_to );
+                       my $ip_int  = ip::to_int $peer_ip;
+
+                       # show edit for clients in our dhcp range
+                       if ( $ip_int >= ( $network | $server::ip_from ) && $ip_int <= ( $network | $server::ip_to ) ) {
+                               $ip = $peer_ip;
+                       }
+               }
+
+               if ( $ip && $ip ne $server::ip ) {
+                       my $hostname = client::conf( $ip, 'hostname' => $param->{hostname} );
+
+                       my @table = (
+                               'ip' => qq|<input type=text name=change_ip value="$ip" onChange="document.getElementById('old_ip').style.display = '';"><span id=old_ip style="display: none; color: #888;">old: $ip<span>|,
+                               'hostname' => qq|<input type=text name=hostname value="$hostname">|,
+                       );
 
-               print $client $ok,
-               html::table( 2,
-                       'pid',          html::tt( $$ ),
-                       'ip',           html::tt( $server::ip ),
-                       'netmask',      html::tt( $server::netmask ),
-                       'debug',        qq|<a href=/our/debug/| . boolean::toggle($debug) . qq|>$debug</a>|,
-                       'screen',       qq|<a href=/screen>$screen</a>|,
-                       'kvm',          qq|<a href=/kvm>$kvm</a>|,
-               );
+                       my $deploy;
 
+                       if ( my $mac = client::mac_from_ip( $ip ) ) {
+                               $deploy = client::conf( $ip, 'deploy' => $param->{deploy} );
+                               push @table, (
+                                       'mac' => format::mac( $mac => 'html' ),
+                                       'deploy' => html::select( 'deploy', $deploy, config::available )
+                               );
+                               if ( my $pxelinux = config::for_ip( $ip ) ) {
+                                       $deploy = qq|<h2>PXElinux</h2>| . html::pre( $pxelinux );
+                               }
+                       }
+
+                       print $client ok
+                               , qq|<form method=get>|
+                               , html::table( 2, @table ),
+                               , qq|
+                                       <input type=submit name=action value=change>
+                                       <input type=submit name=action value=remove style="color: red">
+                                       </form>|
+                               , $deploy
+                               ;
+
+                       if ( my $amt = client::conf( $ip, 'amt' ) ) {
+                               print $client qq|<h2>AMT</h2>|, amt::info( $amt, $ip );
+                       }
+               } else {
+
+                       my $arp = {
+                               map {
+                                       my @c = split(/\s+/,$_);
+                                       if ( $#c == 5 ) {
+                                               client::save_ip_mac( $c[0], $c[3] );
+                                               ( uc $c[3] => [ $c[0] , $c[5] ] )
+                                       } else {
+                                       }
+                               } read_file('/proc/net/arp')
+                       };
+
+                       warn "# arp ",dump( $arp );
+
+                       print $client ok
+                               , qq|<h2>Clients on $server::ip</h2>|
+                               , html::table( -6,
+                                       'ip', 'mac', 'arp', 'hostname', 'deploy', 'conf',
+                                       map {
+                                               my $ip = $_;
+                                               my $conf = client::all_conf( $ip );
+                                               my $mac = delete $conf->{mac} || '';
+                                               (
+                                                       qq|<a name=$ip href=/client/$ip>$ip</a>|
+                                                       , format::mac( $mac => 'html' )
+                                                       , ( $arp->{$mac} ? $arp->{$mac}->[1] : '' )
+                                                       , delete $conf->{hostname}
+                                                       , delete $conf->{deploy}
+                                                       , ( %$conf ? html::pre_dump( $conf ) : qq|<a href=/nmap?scan=$ip>nmap</a>| )
+                                               );
+                                       } client::all_ips
+                               )
+                               ;
+               }
+       } elsif ( $path =~ m{^/brctl} ) {
+               print $client ok
+                       ,html::pre( `brctl show` )
+                       ;
+       } elsif ( $path =~ m{^/ip/?(\w+)?} ) {
+               print $client ok
+                       , join("\n", map { qq|<a href=/ip/$_>$_</a>| } ( qw/link addr route neigh ntable tunnel maddr mroute xfrm/ ))
+                       , ip::html( $1 )
+                       ;
+       } elsif ( $path =~ m{^/nmap} ) {
+               if ( my $scan = $param->{scan} ) {
+                       nmap::scan( $scan );
+                       print $client redirect("$url/client#$scan");
+               } else {
+                       print $client ok, qq|
+                               <form method=get>
+                               <input type=text name=scan>
+                               <input type=submit value=scan>
+                               </form>
+                       |;
+               }
        } elsif ( $path =~ m{^/our/(\w+)/(\S+)} ) {
                eval 'our $' . $1 . ' = ' . $2;
                warn $@ if $@;
-               print $client qq|HTTP/1.1 302 Found\r\nLocation: $url\r\nContent-type: text/html\r\n\r\n<big>$1 = $2</big><br>Location: <a href="$url">$url</a>|;
-       } elsif ( $path =~ m{^/(screen|kvm)} ) {
-               print $client $ok, start_stop($1);
-       } elsif ( $path =~ m{/boot} ) {
-               print $client qq{$ok
-#!gpxe
-imgfree
-login
-chain http://$server::ip:$httpd::port/
-
-                       };
+               print $client redirect($url), qq|<big>$1 = $2</big><br>Location: <a href="$url">$url</a>|;
+               server::debug( $debug ) if $1 eq 'debug';
+       } elsif ( $path =~ m{^/start_stop/(\S+)} ) {
+               print $client redirect, daemons::start_stop($1);
+       } elsif ( $path =~ m{^/action/([^/]+)/(.+)} ) {
+               $1->$2();
+               print $client redirect;
+       } elsif ( $path =~ m{^/kill/static/(\d+)} ) {
+               print $client redirect;
+               kill 1, $1 || kill 9, $2 && warn "killed $1";
        } else {
                print $client "HTTP/1.0 404 $path\r\nConnection: close\r\nContent-type: text/html\r\n\r\n<big>404 $path</big>";
                warn "404 $path";
@@ -137,8 +347,15 @@ chain http://$server::ip:$httpd::port/
 
 sub start {
 
+       warn 'network ', network::setup();
+
+       daemons::start_stop 'browser', $url;
+       daemons::start_stop $_ foreach ( qw/dhcpd tftpd dnsd syslogd/ );
+       daemons::start_stop 'kvm' unless $ENV{DEV}; # skip kvm statup when running on real device
+
        my $server = IO::Socket::INET->new(
                        Proto     => 'tcp',
+                       LocalAddr => $server::ip,
                        LocalPort => $httpd::port,
                        Listen    => SOMAXCONN,
                        Reuse     => 1
@@ -146,16 +363,12 @@ sub start {
 
        print "url $url\n";
 
-       system "/mnt/llin/rest/cvs/uzbl/uzbl -u $url &";
-
-       while (my $client = $server->accept()) {
-               $client->autoflush(1);
+       while (1) {
+               my $client = $server->accept() || next; # ALARM trickle us
                my $request = <$client>;
 
                warn "request $request\n" if $debug;
 
-               Module::Refresh->refresh;
-
                if ($request =~ m{^GET (/.*) HTTP/1.[01]}) {
                        my $path = $1;
                        my $param;
@@ -166,19 +379,13 @@ sub start {
                                }
                                warn "param: ",dump( $param ) if $debug;
                        }
-                       warn "path $path param: ",dump( $param );
                        get_request $client, $path, $param;
                } else {
                        print $client "HTTP/1.0 500 No method\r\nConnection: close\r\nContent-type: text/plain\r\n\r\n500 $request";
                        warn "500 $request";
                }
 
-               print $client qq{
-               <div style="font-size: 80%; color: #888">
-               <a href="">reload</a>
-               <a href="/">index</a>
-               </div>
-               } if $client->connected;
+               print $client menu() if $client->connected;
 
        }