| Slauth-Register-Mailman documentation | Contained in the Slauth-Register-Mailman distribution. |
Slauth::Register::Mailman - Slauth module for user self-registration from Mailman list data
in Slauth configuration:
%config = ( "global" => { [...] "register" => "Slauth::Register::Mailman", "mailman_bin" => "/home/mailman/slauth-bin", [...] } };
TBA
Slauth
See the Slauth project web site at http://www.slauth.org/
Project mail lists are at http://www.slauth.org/mailman/listinfo
Ian Kluft, <ikluft@localdomain>
Copyright (C) 2004-2005 by Ian Kluft
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available.
| Slauth-Register-Mailman documentation | Contained in the Slauth-Register-Mailman distribution. |
# Slauth registration interface using Mailman lists package Slauth::Register::Mailman; use strict; #use warnings FATAL => 'all', NONFATAL => 'redefine'; use Slauth::Config; use Slauth::Storage::User_DB; use Slauth::Storage::Session_DB; use Slauth::Storage::Confirm_DB; use IO::Pipe; use CGI::Carp qw(fatalsToBrowser); # globals our $VERSION = "0.01"; sub debug { $Slauth::Config::debug; } sub new { my $class = shift; my $self = {}; bless $self, $class; $self->initialize(); return $self; } sub initialize { my $self = shift; $self->{short_name} = "mailman"; $self->{long_name} = "Registration using Mailman mail list subscription info"; $self->{req_params} = [ qw( addr login ) ]; } # short-form name for use in URLs # TODO: move this to a new parent class Slauth::Register sub short_name { my $self = shift; $self->{short_name} }; # long-form name for use as a one-liner description # TODO: move this to a new parent class Slauth::Register sub long_name { my $self = shift; $self->{long_name} }; # list of required form paramaters # TODO: move this to a new parent class Slauth::Register sub req_params { my $self = shift; @{$self->{req_params}} }; # HTML registration form sub html_form { my ( $self, $web ) = @_; $web->tag( "text", "<form action=\"%self_url%\" method=\"POST\">\n" ."Please enter your e-mail address as it's\n" ."subscribed on our mail lists.\n" ."<br>\n" ."(<b>required</b> - this must match your subscription)\n" ."<br>\n" ."<input type=text name=addr size=70>\n" ."<p>\n" ."Choose a one-word login name.\n" ."<br>\n" ."(<b>required</b> - this will be your web login name)\n" ."<br>\n" ."<input type=text name=login size=20>\n" ."<p>\n" ."Enter your Mailman password from any of our lists " ."that you're subscribed to\n" ."<br>\n" #."(<b>required</b> - we'll later make this optional\n" #."and have it e-mail a confirmation to you if you omit\n" #."your password. But that isn't done yet.)\n" ."(<b>optional</b> - if you omit your password then a " ."confirmation will be mailed to you.)\n" ."<br>\n" ."<input type=password name=pw size=30>\n" ."<p>\n" ."<input type=submit name=submit>\n" ."</form>\n" ); } # process form submission sub process_form { my ( $self, $web ) = @_; my ( $text ); my $realm = $web->get( "realm" ); my $config = $web->get( "config" ); debug and print STDERR "process_form: init\n"; $web->tag( "subtitle", $self->long_name()); my $login = $web->param("login"); my $pw = $web->param("pw"); my $addr = $web->param("addr"); if (( defined $pw ) and length( $pw ) > 0 ) { $self->check_subs( $web, $addr, $pw ); if (( defined $self->{subs}{"exit-code"}) and $self->{subs}{"exit-code"} != 0 ) { $text = "An error has occurred.\n"; $text .= "The verification software has failed.\n"; $text .= "This may require admin attention.\n"; $text .= "Please report this and try again later.\n"; } elsif ( $self->{subs}{status} ne "success" ) { $text = "An error has occurred.\n"; $text .= "A failure occurred during verification.\n"; $text .= "This may require admin attention.\n"; $text .= "Please report this and try again later.\n"; } elsif ( $self->{subs}{"search-status"} ne "success" ) { $text = "The software has denied your request.\n"; $text .= "The info you provided was incorrect.\n"; $text .= "If you need help, please ask for it.\n"; } else { # success - create user and session, set user's cookie # create the new user record my $user_db = Slauth::Storage::User_DB->new( $config ); if ( $user_db->error ) { $text = "An error has occurred.\n"; $text .= "The storage subsystem failed.\n"; $text .= "Your user record wasn't stored.\n"; $text .= "This may require admin attention.\n"; $text .= "Please report this and try again.\n"; goto DONE; } my $name = ""; if ( defined $self->{subs}{name}) { my @names = split /\n/, $self->{subs}{name}; $name = $names[0]; } my @groups = split ( /\s+/, $self->{subs}{subscriptions}); $user_db->write_record ( $login, $pw, $name, $addr, @groups ); $web->new_session( $login ); return; } } else { # handle e-mail verification my $confirm_db = Slauth::Storage::Confirm_DB->new( $config ); my $confirm_hash = $confirm_db->write_record( $login, $addr, $config ); my $admin_addr = $web->get( "admin_addr" ); if ( !open ( mail_pipe, "| /usr/lib/sendmail $addr" )) { $text = "An error has occurred.\n"; $text .= "The confirmation e-mail could not be sent\n"; $text .= "to the address due to a local error on\n"; $text .= "this server ($!).\n"; goto DONE; } print mail_pipe "To: $addr\n"; print mail_pipe "From: $admin_addr\n"; print mail_pipe "Subject: $realm web access confirmation\n"; print mail_pipe "\n"; print mail_pipe "This message was sent to you because " ."this address was entered in a web form\n"; print mail_pipe "to request web access at $realm.\n"; print mail_pipe "We need to confirm that the owner of this " ."address really made that request.\n"; print mail_pipe "\n"; print mail_pipe "If you did not make the request, you may " ."safely ignore and discard this\n"; print mail_pipe "message. However, we would like to know " ."about cases of network abuse.\n"; print mail_pipe "\n"; print mail_pipe "In order to complete your web access, " ."please visit this URL:\n"; print mail_pipe " " ."http://" .$web->tag("server_name") .$web->tag("script_name") .$web->tag("path_info") ."/confirm/" .$web->{cgi}->escape( Slauth::Storage::Confirm_DB::md5tourlsafe( $confirm_hash)) ."\n"; if ( ! close mail_pipe ) { $text = "An error has occurred.\n"; $text .= "The confirmation e-mail could not be sent\n"; $text .= "to the address due to a local error on\n"; $text .= "this server ($!).\n"; goto DONE; } $text .= "Thank you. A confirmation e-mail has been sent " ."to your address.\n"; $text .= "Select the link in the e-mail to activate your " ."web access.\n"; } DONE: #$text .= "parameters received:<br>\n"; #my @vars = $web->param; #foreach my $key ( @vars ) { # $text .= $key." = ".$web->param($key)."<br>\n"; #} #$text .= "<br>pw-check output:<br>\n"; #$text .= "<blockquote><pre>\n"; #$text .= join ( "\n", %{$self->{subs}})."\n"; #$text .= "</pre></blockquote>\n"; $web->tag("text", $text ); } # process additional path info as menu selections sub process_path { my ( $self, $web, @subpath ) = @_; my ( $text ); my $config = $web->get( "config" ); if ( $subpath[0] eq "confirm" ) { my $path_hash = Slauth::Storage::Confirm_DB::urlsafetomd5($subpath[1]); my ( $confirm_login, $confirm_hash, $confirm_salt, $confirm_email, $confirm_time ) = Slauth::Storage::Confirm_DB::get_confirm($path_hash, $config ); if ( ! defined $confirm_login ) { $text = "No confirmation record was found.\n"; goto DONE; } my $now = time; if ( $confirm_time > $now + 60*10 or $now > $confirm_time + 60*60*24*2 ) { $text = "This confirmation has expired.\n"; $text .= "You will need to register again.\n"; Slauth::Storage::Confirm_DB::delete_confirm($path_hash, $config ); goto DONE; } $self->check_subs( $web, $confirm_email, "***unauthenticated query***" ); if (( defined $self->{subs}{"exit-code"}) and $self->{subs}{"exit-code"} != 0 ) { $text = "An error has occurred.\n"; $text .= "The verification software has failed.\n"; $text .= "This may require admin attention.\n"; $text .= "Please report this and try again later.\n"; } elsif ( $self->{subs}{status} ne "success" ) { $text = "An error has occurred.\n"; $text .= "A failure occurred during verification.\n"; $text .= "This may require admin attention.\n"; $text .= "Please report this and try again later.\n"; } elsif ( $self->{subs}{"search-status"} ne "unauthenticated success" ) { $text = "The software has denied your request.\n"; $text .= "The info you provided was incorrect.\n"; $text .= "If you need help, please ask for it.\n"; } else { my $new_pw = `/usr/local/bin/pwrand --length=8 --allalpha`; chomp $new_pw; my $user_db = Slauth::Storage::User_DB->new( $config ); my @groups = split ( /\s+/, $self->{subs}{subscriptions}); $user_db->write_record($confirm_login, $new_pw, "", $confirm_email, @groups ); Slauth::Storage::Confirm_DB::delete_confirm($path_hash, $config ); $web->tag("text", "A web password has been auto-generated " ."for you: $new_pw<p><br>\n" ); $web->new_session( $confirm_login ); return; } } else { $text = "The function '".$subpath[0]."' was not recognized.\n"; } DONE: $web->tag("text", $text ); } # verify Mailman subscription info # # Note: for security design purposes, it was considered too great a risk # to store Mailman password information within mod_perl/Apache. Therefore # the model in use here is to contact an external program which runs under # the Mailman group ID. The parameters piped (not via the command line or # environment, which can be exposed via /proc) to that program are only the # subscription address and password. If the password is correct for any # list subscription in that domain, then it responds affirmatively and # with the list of subscriptions for use in access group memberships. sub check_subs { my ( $self, $web, $addr, $pw ) = @_; my $mailman_bin = $web->get( "mailman_bin" ); my $realm = $web->get( "realm" ); debug and print STDERR "check_subs: init\n"; my $pipe_to_script = new IO::Pipe; my $pipe_from_script = new IO::Pipe; if ( my $pid = fork ) { # Parent process - we are still running inside the web server debug and print STDERR "check_subs: in parent\n"; $pipe_to_script->writer(); $pipe_to_script->autoflush(1); $pipe_from_script->reader(); debug and print STDERR "check_subs: output to script\n"; print $pipe_to_script $realm."\n"; print $pipe_to_script $addr."\n"; print $pipe_to_script $pw."\n"; close $pipe_to_script; # gulp the response debug and print STDERR "check_subs: read from script\n"; my %response; while ( <$pipe_from_script> ) { chomp; debug and print STDERR "check_subs: got '$_'\n"; if ( /^([a-z0-9_-]+):\s*(.*)/i ) { if ( !defined $response{$1}) { $response{$1} = $2; } else { $response{$1} .= "\n".$2; } } } close $pipe_from_script; $response{date} = localtime; waitpid ( $pid, 0 ); debug and print STDERR "check_subs: result code ".($? >> 8)."\n"; if ( $? >> 8 != 0 ) { $response{"exit-code"} = ($? >> 8); } $self->{subs} = \%response; } elsif( defined $pid ) { # Child process - we are in a disposable copy of the web server # We must circumvent any defenses mod_perl has made to core # functions to defend the web server from being undermined by # Perl code. So we use CORE::* functions in order to access # the original Perl functions. Since we just forked, we must # finish exec'ing this process or exit so that there isn't # an unwanted copy of Apache running around. debug and print STDERR "check_subs: entering child\n"; # prepare Apache for upcoming exec # (This is in an eval so commsnd-line testing still works.) eval "require Apache::RequestUtil; " ."require Apache::SubProcess; " ."my \$r = Apache->Request; " ."\$r->cleanup_for_exec()"; # replace STDIN with the pipe to the script # so that we can write to its input $pipe_to_script->reader; untie *main::STDIN; my $stdin = *main::STDIN{IO}; open ( $stdin, "<&=", $pipe_to_script ) or CORE::die "failed to reopen STDIN in child: $!\n"; debug and print STDERR "check_subs: STDIN fileno =" .$stdin->fileno."\n"; # replace STDOUT with the pipe from the script # so that we can read from its output $pipe_from_script->writer; untie *main::STDOUT; my $stdout = *main::STDOUT{IO}; open ( $stdout, ">&=", $pipe_from_script ) or CORE::die "failed to reopen STDOUT in child: $!\n"; debug and print STDERR "check_subs: STDOUT fileno =" .$stdout->fileno."\n"; # Make sure file descriptors will stay open across exec() # Clear the close-on-exec flag from the pipes require Fcntl; fcntl( $stdin, Fcntl::F_SETFD(), 0 ); fcntl( $stdout, Fcntl::F_SETFD(), 0 ); # exec the program or bust debug and print STDERR "check_subs: filenos =" .$stdin->fileno." " .$stdout->fileno."\n"; { CORE::exec "$mailman_bin/check-pw-wrapper", $stdin->fileno, $stdout->fileno; } debug and print STDERR "check_subs: exec failed\n"; print STDERR "failed to execute check-pw script: $!\n"; CORE::exit(1); # never gets here # croak will work because exit() isn't shooting blanks } else { # fork failed croak "fork failed - system process table may be full " ."or another resource constraint has been reached\n"; } } 1; __END__