Changeset 219
- Timestamp:
- 10/03/06 22:22:28 (2 years ago)
- Files:
-
- trunk/MANIFEST (modified) (2 diffs)
- trunk/axkit (modified) (1 diff)
- trunk/demo/webshell (added)
- trunk/demo/webshell/webshell.css (added)
- trunk/demo/webshell/webshell.xsl (added)
- trunk/etc/axkit.conf.sample (modified) (1 diff)
- trunk/lib/AxKit2.pm (modified) (1 diff)
- trunk/lib/AxKit2/Client.pm (modified) (2 diffs)
- trunk/lib/AxKit2/Config.pm (modified) (1 diff)
- trunk/lib/AxKit2/Console.pm (modified) (13 diffs)
- trunk/lib/AxKit2/HTTPHeaders.pm (modified) (1 diff)
- trunk/lib/AxKit2/Plugin.pm (modified) (3 diffs)
- trunk/lib/AxKit2/Processor.pm (modified) (4 diffs)
- trunk/lib/AxKit2/Transformer/TAL.pm (modified) (1 diff)
- trunk/lib/AxKit2/Transformer/XSLT.pm (modified) (1 diff)
- trunk/lib/AxKit2/Utils.pm (modified) (2 diffs)
- trunk/plugins/authenticate (modified) (9 diffs)
- trunk/plugins/demo/moewiki (modified) (2 diffs)
- trunk/plugins/demo/webshell (added)
- trunk/plugins/development (added)
- trunk/plugins/stats (modified) (4 diffs)
- trunk/plugins/uri_to_file (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/MANIFEST
r214 r219 65 65 demo/webmail/main.tal 66 66 demo/webmail/xmlmail-display.xsl 67 demo/webshell/webshell.css 68 demo/webshell/webshell.xsl 67 69 demo/xpathscript/demo.xps 68 70 demo/xpathscript/index.xml … … 128 130 plugins/demo/serve_xsp 129 131 plugins/demo/webmail 132 plugins/demo/webshell 133 plugins/development 130 134 plugins/dir_to_xml 131 135 plugins/error_xml trunk/axkit
r103 r219 22 22 $::DEBUG = 0; 23 23 my $configfile = './etc/axkit.conf'; 24 my $test = 0; 24 25 25 26 Getopt::Long::GetOptions( 26 'd|debug+' => \$::DEBUG,27 'd|debug+' => \$::DEBUG, 27 28 'c|configfile=s' => \$configfile, 29 't|test+' => \$test, 28 30 ); 29 31 30 AxKit2->run($configfile );32 AxKit2->run($configfile,$test); trunk/etc/axkit.conf.sample
r205 r219 138 138 # </Location> 139 139 140 # WebShell DEMO 141 # Note that this may pose a security risk, so only enable this if you 142 # plan to actually use/test it, and ensure you have set up decent passwords 143 # <Location /webshell> 144 # DocumentRoot demo/webshell 145 # Plugin authenticate 146 # AuthType Digest 147 # AuthFile etc/passwd 148 # 149 # Plugin demo/webshell 150 # </Location> 151 140 152 </Server> trunk/lib/AxKit2.pm
r205 r219 33 33 my $class = shift; 34 34 my $configfile = shift; 35 my $test = shift; 35 36 36 37 my $config = AxKit2::Config->new($configfile); 38 exit(0) if $test; 37 39 38 40 local $SIG{'PIPE'} = "IGNORE"; # handled manually trunk/lib/AxKit2/Client.pm
r216 r219 182 182 my @rc = $self->run_hooks('message', @_); 183 183 $self->log(LOGWARN,"Unhandled message: $_[0]") if shift(@rc) != OK; 184 return @ _if wantarray;185 return $ _[0];184 return @rc if wantarray; 185 return $rc[0]; 186 186 } 187 187 … … 360 360 sub hook_xmlresponse_end { 361 361 my ($self, $ret, $out, $input, $hd) = @_; 362 $out ||= $input, $ret = OK if $input->was_used; 362 363 if ($ret == DECLINED) { 363 364 return $self->run_hooks('response', $hd); trunk/lib/AxKit2/Config.pm
r214 r219 375 375 my ($package, $symbol, $referent, $attr, $data, $phase) = @_; 376 376 377 if ($symbol eq 'ANON') { # yuck. eval'ed sub names sometimes don't find their way here, so we work around. 378 no strict 'refs'; 379 foreach my $key (keys %{*{$package."::"}}) { 380 my $sub = $package->can($key); 381 # tricky logic: either $package->can() is our referent, or (probably due to the same bug/feature) 382 # $package->can() is undefined but a symbol table entry exists. 383 next unless $key =~ m/^conf_/ && ($sub? $sub eq $referent : *{$package."::".$key}); 384 $symbol = \*{$package."::".$key}; 385 last; 386 } 387 } 377 AxKit2::Utils::_attribute_symbol($package, $symbol, $referent, qr(^conf_)); 378 388 379 ref($symbol) or _die "Attribute '${attr}' on invalid symbol $symbol in package $package"; 389 380 my $name = *{$symbol}; trunk/lib/AxKit2/Console.pm
r205 r219 126 126 } 127 127 128 my %helptext; 129 my @packages; 130 131 sub import { 132 my $package = caller; 133 no strict 'refs'; 134 push @{$package.'::ISA'}, 'AxKit2::Console::Attributes'; 135 push @packages, $package; 136 } 137 128 138 sub process_line { 129 139 my AxKit2::Console $self = shift; … … 132 142 $line =~ s/\r?\n//; 133 143 my ($cmd, @params) = split(/ +/, $line); 134 my $meth = "c md_" . lc($cmd);144 my $meth = "console_" . lc($cmd); 135 145 if (my $lookup = $self->can($meth)) { 136 146 $lookup->($self, @params); … … 138 148 } 139 149 else { 150 foreach my $package (@packages) { 151 no strict 'refs'; 152 if (my $lookup = $package->can($meth)) { 153 $lookup->($self, @params); 154 return $self->write($PROMPT); 155 } 156 } 140 157 # No such method - i.e. unrecognized command 141 158 return $self->write("command '$cmd' unrecognised\n$PROMPT"); … … 143 160 } 144 161 145 my %helptext;146 147 162 $helptext{help} = "HELP [CMD] - Get help on all commands or a specific command"; 148 163 149 sub c md_help {164 sub console_help { 150 165 my $self = shift; 151 166 my ($subcmd) = @_; … … 163 178 164 179 $helptext{quit} = "QUIT - Exit the console"; 165 sub c md_quit {180 sub console_quit { 166 181 my $self = shift; 167 182 $self->close; … … 169 184 170 185 $helptext{list} = "LIST [LIMIT] - List current connections, specify limit or negative limit to shrink list"; 171 sub c md_list {186 sub console_list { 172 187 my $self = shift; 173 188 my ($count) = @_; … … 202 217 203 218 $helptext{kill} = "KILL (\$IP | \$REF) - Disconnect all connections from \$IP or connection reference \$REF"; 204 sub c md_kill {219 sub console_kill { 205 220 my $self = shift; 206 221 my ($match) = @_; … … 214 229 foreach my $fd (keys %$descriptors) { 215 230 my $pob = $descriptors->{$fd}; 216 if ($pob->isa(" Qpsmtpd::PollServer")) {231 if ($pob->isa("AxKit2::Connection")) { 217 232 if ($is_ip) { 218 233 next unless $pob->connection->remote_ip; # haven't even started yet … … 238 253 239 254 $helptext{dump} = "DUMP \$REF - Dump a connection using Data::Dumper"; 240 sub c md_dump {255 sub console_dump { 241 256 my $self = shift; 242 257 my ($ref) = @_; … … 262 277 263 278 $helptext{leaks} = "LEAKS [DUMP] - Run Devel::GC::Helper to list leaks with optional Dumper output"; 264 sub c md_leaks {279 sub console_leaks { 265 280 my $self = shift; 266 281 my $dump = shift || ''; … … 293 308 } 294 309 295 $helptext{stats} = "STATS - Show status and statistics"; 296 sub cmd_stats { 297 my $self = shift; 298 299 my $output = "Current Status as of " . gmtime() . " GMT\n\n"; 300 301 if (defined &AxKit2::Plugin::stats::get_stats) { 302 # Stats plugin is loaded 303 $output .= AxKit2::Plugin::stats->get_stats; 304 } 305 306 my $descriptors = Danga::Socket->DescriptorMap; 307 308 my $current_connections = 0; 309 my $current_dns = 0; 310 foreach my $fd (keys %$descriptors) { 311 my $pob = $descriptors->{$fd}; 312 if ($pob->isa("AxKit2::Connection")) { 313 $current_connections++; 314 } 315 } 316 317 $output .= "Current Connections: $current_connections\n"; 318 319 $self->write($output); 320 } 321 322 sub cmd_shutdown { 310 $helptext{shutdown} = 'SHUTDOWN - terminates the running server'; 311 sub console_shutdown { 323 312 my $self = shift; 324 313 Danga::Socket->SetPostLoopCallback(sub { 0 }); … … 366 355 } 367 356 357 package AxKit2::Console::Attributes; 358 359 use Attribute::Handlers; 360 361 sub Help : ATTR(CODE) { 362 my ($package, $symbol, $referent, $attr, $data, $phase) = @_; 363 364 AxKit2::Utils::_attribute_symbol($package, $symbol, $referent, qr(^console_)); 365 366 ref($symbol) or die "Attribute '${attr}' on invalid symbol $symbol in package $package"; 367 my $name = *{$symbol}; 368 369 $name =~ s/.*::console_// or die "Attribute '${attr}' on invalid sub $name"; 370 $name = lc($name); 371 372 $helptext{$name} = $data; 373 } 374 368 375 1; 369 376 … … 378 385 The console allows you to see and change some internal values. Connect to 379 386 localhost at the port specified in the configuration option C<< ConsolePort >> 380 and read the help. 381 382 Someone has to write some more details about this. 387 and type C<HELP>. 388 389 =head1 COMMANDS 390 391 While plugins can add more commands, there are a few built-in commands that 392 are always available. 393 394 =head2 C<HELP> [ I<command> ] 395 396 Shows a list of available commands, or help for a specific command. 397 398 =head2 C<QUIT> 399 400 Quits the console. 401 402 =head2 C<LIST> [ I<limit> ] 403 404 Lists current connections. I<limit> is the number of connections shown. 405 If I<limit> is negative, shows entries from the beginning of the list, 406 otherwise the latest connections are shown. 407 408 =head2 C<KILL> I<address> | I<reference> 409 410 Forcefully closes a connection. You can either specify an IP address, or 411 a connection reference as shown in C<LIST> 412 413 =head2 C<DUMP> I<reference> 414 415 Prints a dump of a connection using Data::Dumper. 416 417 =head2 C<LEAKS> [ C<DUMP> ] 418 419 Run Devel::GC::Helper to list leaks. If C<DUMP> is given, also dumps 420 the result via Data::Dumper. 421 422 =head2 C<SHUTDOWN> 423 424 Terminates the AxKit2 server. 425 426 =head1 API 427 428 To provide additional commands for the console, do something like this: 429 430 use AxKit2::Console; 431 432 $CONSOLE_HELP{mycmd} = "MYCMD - example for the docs"; 433 sub console_mycmd { 434 my $console = shift; 435 $console->write("You called 'MYCMD' with args: ".join(", ",@_)."\n"); 436 AxKit2::Client->log(LOGINFO,"MYCMD called."); 437 } 383 438 384 439 =cut trunk/lib/AxKit2/HTTPHeaders.pm
r216 r219 392 392 *uri = \&request_uri; 393 393 394 =head2 C<< $obj->full_uri >> 395 396 Gets the request URI including host name and port. This URI can be used 397 to build links to the currently running code, or for redirects. 398 399 Please be aware that it can yield wrong results in some reverse-proxy setups. Moreover, 400 this method works like C<< $obj->uri >> when no C<Host>-Header is present. 401 402 =cut 403 404 sub full_uri { 405 my AxKit2::HTTPHeaders $self = shift; 406 # FIXME: handle HTTP/0.9 requests, allow configurable prefix for reverse-proxy situations, 407 # using something like Apache's ServerName directive. 408 return $self->request_uri if !$self->header('Host'); 409 return "http://".$self->header('Host').$self->request_uri; 410 } 411 394 412 # Parse the Cookie header. 395 413 sub parse_cookies { trunk/lib/AxKit2/Plugin.pm
r217 r219 22 22 23 23 use AxKit2::Constants; 24 use Attribute::Handlers;25 24 26 25 # more or less in the order they will fire … … 121 120 } 122 121 123 124 my %_HANDLER_ATTRIB;125 126 sub Stacked : ATTR(CODE) {127 my ($package, $symbol, $referent) = @_;128 $_HANDLER_ATTRIB{$referent} = 1;129 }130 131 122 sub dispatch_message { 132 123 my $self = shift; 133 124 my $message = shift; 125 $message =~ s/-/_/g; 134 126 if (my $sub = $self->can("message_$message")) { 135 $sub->($self,@_), return DECLINED if ($_HANDLER_ATTRIB{$sub}); 136 return OK, $sub->($self,@_); 127 my @rc = $sub->($self,@_); 128 return DECLINED if (!@rc); 129 return OK, @rc; 137 130 } 138 131 return DECLINED; … … 332 325 333 326 By default, the first message handler that is found is run and it's return value returned. 334 If you want your handler to stack, i.e., that other handlers are run after it, specify the 335 attribute C<Stacked> on the sub: 336 337 sub message_login : Stacked { 338 my ($self, $user) = @_; 339 # log it somewhere special, but do not interfere with regular login processing. 340 open(my $fh, '>>', 'userlog'); print $fh time()." ".$user."\n"; close($fh); 341 } 342 343 Of course, the return value of stacked handlers is ignored. 327 If you want that other handlers are run after it, return an empty list C<< () >>. 344 328 345 329 If you need even more complex processing, see C<< hook_handler >> below. trunk/lib/AxKit2/Processor.pm
r169 r219 63 63 sub dom { 64 64 my $self = shift; 65 @_ and $self->{used} = 1; 65 66 @_ and $self->{input} = shift; 66 67 … … 82 83 return $self->{input} = $parser->parse_string($input); 83 84 } 85 } 86 87 # Has this object been used by user code? 88 sub was_used { 89 my $self = shift; 90 return $self->{used}; 84 91 } 85 92 … … 121 128 if ($AxKit2::Processor::DumpIntermediate) { 122 129 mkdir("/tmp/axtrace"); 123 open(my $fh, "> /tmp/axtrace/trace.$pos");130 open(my $fh, ">:raw", "/tmp/axtrace/trace.$pos"); 124 131 print $fh ($dom || $self->dom)->toString; 125 132 } … … 129 136 } 130 137 131 return $self->new($self->client, $self->path, $dom, $outfunc); 138 $self->{output} = $outfunc; 139 return $self; 132 140 } 133 141 trunk/lib/AxKit2/Transformer/TAL.pm
r169 r219 53 53 54 54 return $results, sub { $self->output(@_) }; 55 } 56 57 sub RELOAD { 58 undef %cache; 55 59 } 56 60 trunk/lib/AxKit2/Transformer/XSLT.pm
r169 r219 62 62 } 63 63 64 sub RELOAD { 65 undef %cache; 66 } 67 64 68 sub fixup_params { 65 69 my @results; trunk/lib/AxKit2/Utils.pm
r169 r219 27 27 xml_escape 28 28 bytelength 29 html_to_dom 29 30 ); 30 31 … … 121 122 } 122 123 124 use XML::LibXML; 125 my $parser = new XML::LibXML(); 126 $parser->expand_xinclude(0); 127 128 sub html_to_dom { 129 my $dom = $parser->parse_html_string(shift); 130 $dom->setEncoding('UTF-8'); 131 return $dom; 132 } 133 134 # It looks as if eval'ed sub names sometimes don't find their way into attribute handlers, so we work around. 135 sub _attribute_symbol { 136 my ($package, $symbol, $referent, $match) = @_; 137 if ($symbol eq 'ANON') { 138 no strict 'refs'; 139 foreach my $key (keys %{*{$package."::"}}) { 140 my $sub = $package->can($key); 141 # tricky logic: either $package->can() is our referent, or (probably due to the same bug/feature) 142 # $package->can() is undefined but a symbol table entry exists. 143 next unless $key =~ m/$match/ && ($sub? $sub eq $referent : *{$package."::".$key}); 144 $symbol = \*{$package."::".$key}; 145 last; 146 } 147 } 148 $_[1] = $symbol; 149 } 150 123 151 1; trunk/plugins/authenticate
r218 r219 129 129 If you use Digest authentication, you also gain a free session ID for all 130 130 authenticated users without needing cookies or URL parameters. It is generated 131 at login and can be retrieved through C<< $ client->headers_in->header('Digest-Session') >>.131 at login and can be retrieved through C<< $plugin->send('session-id') >>. 132 132 If you use that mechanism, you can even log out users using the logout function. 133 133 … … 274 274 pages will result in a new login prompt. 275 275 276 =head2 C<< session-id >> 277 278 Returns the current session id, if any. 279 276 280 =head1 MESSAGES SENT 277 281 278 =head2 C<< login >>282 =head2 C<< login I<username>, I<session-id> >> 279 283 280 284 This message is sent when a user successfully starts a new login session. 281 285 282 =head2 C<< login_failed >>286 =head2 C<< login_failed I<username> >> 283 287 284 288 This message is sent whenever a real login failure is detected. Your application code … … 287 291 =cut 288 292 293 sub message_session_id { 294 my ($self) = @_; 295 return $self->notes('session') || (); 296 } 297 289 298 sub message_logout { 290 299 my ($self, $session) = @_; 291 $session = $self-> client->headers_in->header('Digest-Session') unless defined $session;300 $session = $self->notes('session') unless defined $session; 292 301 return unless defined $session; 293 302 delete $_SESSIONS{$session}; … … 299 308 300 309 my $timestamp = time(); 301 if ( exists $_SESSIONS{$session}) {310 if (defined $session && exists $_SESSIONS{$session}) { 302 311 $_SESSIONS{$session}[0] = 1; 303 312 $_SESSIONS{$session}[1] = int($timestamp); … … 305 314 } else { 306 315 undef $session; 316 $user = ''; 307 317 } 308 318 … … 324 334 sub send_digest_forbidden { 325 335 my ($self, $msg) = @_; 326 $self->log(LOGWARN,"Invalid access! Possible hacking attempt .$msg");336 $self->log(LOGWARN,"Invalid access! Possible hacking attempt: $msg"); 327 337 $self->client->headers_out->header_remove('Authentication-Info'); 328 338 … … 407 417 return $self->send_digest_forbidden("replay attack") if $param{qop} && $param{nc} ne sprintf("%08x",$$s[0]); 408 418 # Timestamp invalid: replay attack. 409 return $self->send_digest_forbidden("invalid timestamp ") if $timestamp != $$s[1];419 return $self->send_digest_forbidden("invalid timestamp $timestamp (expected ${$s}[1])") if $timestamp != $$s[1]; 410 420 # Username mismatch: attempt to take over different user's session 411 421 return $self->send_digest_forbidden("user mismatch in server nonce") if $param{username} ne $user; … … 456 466 # tremendous performance impact. I assume the major browsers don't check it anyway, so let it be for now. 457 467 458 $ hd->header('Digest-Session',$session);468 $self->notes('session',$session); 459 469 if (!defined $_SESSIONS{$session}[0]) { 460 470 # Successful login, establish server-side state. 461 471 $_SESSIONS{$session} = [ -1 ]; 462 $self->send('login',$param{username} );472 $self->send('login',$param{username},$session); 463 473 # Need another round-trip, since we want to tie the session id to the user name but avoid extra server-side state. 464 474 return $self->send_digest_challenge($session,$param{username}); … … 469 479 # Change nonce once per minute to reduce replay attack time window. 470 480 return $self->send_digest_challenge($session,$param{username}) if $timestamp < time()-60; 471 $self->log(LOGINFO, "Identified user as $param{username} via Digest");481 $self->log(LOGINFO, "Identified user as $param{username} (session ".unpack('H*',$self->notes('session')).") via Digest"); 472 482 return OK; 473 483 } trunk/plugins/demo/moewiki
r200 r219 25 25 =cut 26 26 27 use XML::LibXML; 28 my $parser = new XML::LibXML(); 29 $parser->expand_xinclude(0); 27 use AxKit2::Utils qw(html_to_dom); 30 28 31 29 sub hook_xmlresponse { … … 42 40 $input->dom("<html><body>\nThis page has just been created.\n</body></html>"); 43 41 } elsif ($client->param('text')) { 44 my $dom = $parser->parse_html_string($client->param('text')); 45 $dom->setEncoding('UTF-8'); 42 my $dom = html_to_dom($client->param('text')); 46 43 open(FH,'>:utf8',$file.'.xml'); 47 44 print FH $dom->toStringC14N(); 48 45 close(FH); 49 my $uri = $client->headers_in-> request_uri;46 my $uri = $client->headers_in->full_uri; 50 47 $uri =~ s/\?.*$//; 51 warn("URI: $uri"); 52 $self->client->headers_out->header('Location', "http://".$client->headers_in->header('Host').$uri); 48 $self->client->headers_out->header('Location', $uri); 53 49 return REDIRECT; 54 50 } trunk/plugins/stats
r103 r219 33 33 34 34 This module provides statistics on the server since it was last restarted. 35 It adds the console command C<STATS>. 35 36 36 37 The following stats are provided: … … 63 64 64 65 use Time::HiRes qw(time); 66 use AxKit2::Console; 65 67 66 68 our $START_TIME = time; … … 69 71 our $ERRS = 0; 70 72 71 sub get_stats { 72 my $class = shift; 73 my $uptime = $class->uptime; 74 my ($rate, $unit) = $class->delivery_rate; 75 return sprintf(" Uptime: %s\n". 76 " Total Requests: % 10d\n". 77 " OK Requests: % 10d\n". 78 " Errors: % 10d\n". 79 " Delivery Rate: %0.2f reqs/%s\n\n", 80 $uptime, $REQS, $REQS_OK, $ERRS, $rate, $unit); 73 sub console_stats : Help("STATS - Show status and statistics") { 74 my $self = shift; 75 76 my $uptime = &uptime; 77 my ($rate, $unit) = &delivery_rate; 78 79 my $output = "Current Status as of " . gmtime() . " GMT\n\n" . 80 sprintf(" Uptime: %s\n". 81 " Total Requests: % 10d\n". 82 " OK Requests: % 10d\n". 83 " Errors: % 10d\n". 84 " Delivery Rate: %0.2f reqs/%s\n\n", 85 $uptime, $REQS, $REQS_OK, $ERRS, $rate, $unit); 86 87 my $descriptors = Danga::Socket->DescriptorMap; 88 89 my $current_connections = 0; 90 my $current_dns = 0; 91 foreach my $fd (keys %$descriptors) { 92 my $pob = $descriptors->{$fd}; 93 if ($pob->isa("AxKit2::Connection")) { 94 $current_connections++; 95 } 96 } 97 98 $output .= "Current Connections: $current_connections\n"; 99 100 $self->write($output); 81 101 } 82 102 … … 128 148 129 149 sub delivery_rate { 130 my $class = shift;131 150 my $unit = 'sec'; 132 151 my $per_sec = ($REQS / (time() - $START_TIME)); trunk/plugins/uri_to_file
r205 r219 104 104 return DECLINED unless $self->client->notes('need_redirect'); 105 105 106 my $uri = $self->client->headers_in-> request_uri;106 my $uri = $self->client->headers_in->full_uri; 107 107 108 108 no warnings 'uninitialized'; … … 111 111 # send redirect 112 112 $self->log(LOGINFO, "redirect to $uri"); 113 $self->client->headers_out->header('Location', "http://".$self->client->headers_in->header('Host').$uri);113 $self->client->headers_out->header('Location', $uri); 114 114 return REDIRECT; 115 115 }
