X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=cgi-auth-flexible.git;a=blobdiff_plain;f=cgi-auth-flexible.pm;h=a6fb33cf4660e08f4031332aaa66e374840d0486;hp=e8b493dbcbeed9920cab70c0548b42328d73c3b1;hb=4e254e72c7879e561dc7c9a2521f00cb2954eb68;hpb=ac57fc9f2d0e7f51609bb85c7691a3bcaab74322 diff --git a/cgi-auth-flexible.pm b/cgi-auth-flexible.pm index e8b493d..a6fb33c 100644 --- a/cgi-auth-flexible.pm +++ b/cgi-auth-flexible.pm @@ -47,6 +47,7 @@ use Digest; use Digest::HMAC; use Digest::SHA; use Data::Dumper; +use File::Copy; #---------- public utilities ---------- @@ -173,6 +174,198 @@ sub gen_plain_login_link ($$) { ''); } +sub gen_srcdump_link_html ($$$$) { + my ($c,$r,$anchor,$specval) = @_; + my %params = ($r->{S}{srcdump_param_name} => [ $specval ]); + return ''. + $anchor.""; +} +sub gen_plain_licence_link_html ($$) { + my ($c,$r) = @_; + gen_srcdump_link_html($c,$r, 'GNU Affero GPL', 'licence'); +} +sub gen_plain_source_link_html ($$) { + my ($c,$r) = @_; + gen_srcdump_link_html($c,$r, 'Source available', 'source'); +} + +sub gen_plain_footer_html ($$) { + my ($c,$r) = @_; + return ('
', + ("Powered by Free / Libre / Open Source Software". + " according to the ".$r->_ch('gen_licence_link_html')."."), + $r->_ch('gen_source_link_html').".", + '
'); +} + +#---------- licence and source code ---------- + +sub srcdump_dump ($$$) { + my ($c,$r, $thing) = @_; + die if $thing =~ m/\W/ || $thing !~ m/\w/; + my $path = $r->_get_path('srcdump'); + my $ctf = new IO::File "$path/$thing.ctype", 'r' + or die "$path/$thing.ctype $!"; + my $ct = <$ctf>; + chomp $ct or die "$path/$thing ?"; + $ctf->close or die "$path/$thing $!"; + my $df = new IO::File "$path/$thing.data", 'r' + or die "$path/$thing.data $!"; + $r->_ch('dump', $ct, $df); +} + +sub dump_plain ($$$$) { + my ($c, $r, $ct, $df) = @_; + $r->_print($c->header('-type' => $ct)); + my $buffer; + for (;;) { + my $got = read $df, $buffer, 65536; + die $! unless defined $got; + return if !$got; + $r->_print($buffer); + } +} + +sub srcdump_process_dir ($$$$$$) { + my ($c, $v, $dumpdir, $incdir, $tarballcounter, + $needlicence, $dirsdone) = @_; + return () if $v->_ch('srcdump_system_dir', $incdir); + my $upwards = $incdir; + for (;;) { + $upwards =~ s#/+$##; + last unless $upwards =~ m#[^/]#; + foreach my $try (@{ $v->{S}{srcdump_vcs_dirs} }) { +#print STDERR "TRY $incdir $upwards $try\n"; + if (!stat "$upwards/$try") { + $!==&ENOENT or die "check $upwards/$try $!"; + next; + } +#print STDERR "VCS $incdir $upwards $try\n"; + return if $dirsdone->{$upwards}++; +#print STDERR "VCS $incdir $upwards $try GO\n"; + $try =~ m/\w+/ or die; + return $v->_ch(('srcdump_byvcs_'.lc $&), + $dumpdir, $upwards, $tarballcounter); + } + $upwards =~ s#/*[^/]+##; + } + return $v->_ch('srcdump_novcs', $dumpdir, $incdir, $tarballcounter); +} + +sub srcdump_novcs ($$$$$) { + my ($c, $v, $dumpdir, $dir, $tarballcounter) = @_; + my $script = 'find -type f -perm +004'; + foreach my $excl (@{ $v->{S}{srcdump_excludes} }) { + $script .= " \\! -name '$excl'"; + } + $script .= " -print0"; + return srcdump_dir_cpio($c,$v,$dumpdir,$dir,$tarballcounter,$script); +} + +sub srcdump_byvcs_git ($$$$$) { + my ($c, $v, $dumpdir, $dir, $tarballcounter) = @_; +#print STDERR "BYVCS GIT $dir\n"; + return srcdump_dir_cpio($c,$v,$dumpdir,$dir,$tarballcounter," + git ls-files -z + git ls-files -z --others --exclude-from=.gitignore + find .git -print0 + "); +} + +sub srcdump_dir_cpio ($$$$$) { + my ($c,$v,$dumpdir,$dir,$tarballcounter,$script) = @_; + my $outfile = "$dumpdir/$$tarballcounter.tar"; +#print STDERR "CPIO $dir >$script<\n"; + my $pid = fork(); + defined $pid or die $!; + if (!$pid) { + $SIG{__DIE__} = sub { + print STDERR "CGI::Auth::Flexible srcdump error: $@\n"; + exit 127; + }; + open STDOUT, ">", $outfile or die "$outfile $!"; + chdir $dir or die "chdir $dir: $!"; + exec '/bin/bash','-ec'," + set -o pipefail + ( + $script + ) | ( + cpio -Hustar -o --quiet -0 -R 1000:1000 || \ + cpio -Hustar -o --quiet -0 + ) + "; + die $!; + } + $!=0; (waitpid $pid, 0) == $pid or die "$!"; + die "$dir ($script) $outfile $?" if $?; + print STDERR + "CGI::Auth::Flexible srcdump_dir_cpio saved $dir into $outfile\n" + or die $!; + $$tarballcounter++; + return $outfile; +} + +sub srcdump_dirscan_prepare ($$) { + my ($c, $v) = @_; + my $dumpdir = $v->_get_path('srcdump'); + mkdir $dumpdir or $!==&EEXIST or die "mkdir $dumpdir $!"; + my $lockf = new IO::File "$dumpdir/generate.lock", 'w+' + or die "$dumpdir/generate.lock $!"; + flock $lockf, LOCK_EX or die "$dumpdir/generate.lock $!"; + my $needlicence = "$dumpdir/licence.tmp"; + unlink $needlicence or $!==&ENOENT or die "rm $needlicence $!"; + if (defined $v->{S}{srcdump_licence_path}) { + copy($v->{S}{srcdump_licence_path}, $needlicence) + or die "$v->{S}{srcdump_licence_path} $!"; + $needlicence = undef; + } + unlink <"$dumpdir/[a-z][a-z][a-z].tar">; + my $srctarballcounter = 'aaa'; + my %dirsdone; + my @srcfiles = ("$dumpdir/licence.data"); + foreach my $incdir ($v->_ch('srcdump_includedirs')) { + if ($incdir eq '.' && $v->{S}{srcdump_filter_cwd}) { + my @bad = grep { !m#^/# } values %INC; + die "filtering . from srcdump dirs and \@INC but already". + " included @bad " if @bad; + @INC = grep { $_ ne '.' } @INC; + next; + } + if (!stat "$incdir/.") { + next if $!==&ENOENT; + die "stat $incdir $!"; + }; + if (defined $needlicence) { + foreach my $try (@{ $v->{S}{srcdump_licence_files} }) { + last if copy("$incdir/$try", $needlicence); + $!==&ENOENT or die "copy $incdir/$try $!"; + } + } + push @srcfiles, $v->_ch('srcdump_process_dir', $dumpdir, $incdir, + \$srctarballcounter, \$needlicence, \%dirsdone); + $dirsdone{$incdir}++; + } + $!=0; + my $r = system qw(tar -zvvc -f), "$dumpdir/source.tmp", '--', @srcfiles; + die "tar $r $!" if $r; + die "licence file not found" unless defined $needlicence; + srcdump_install($c,$v, $dumpdir, 'licence', 'text/plain'); + srcdump_install($c,$v, $dumpdir, 'source', 'application/octet-stream'); + close $lockf or die $!; +} + +sub srcdump_install ($$$$$) { + my ($c,$v, $dumpdir, $which, $ctype) = @_; + rename "$dumpdir/$which.tmp", "$dumpdir/$which.data" + or die "$dumpdir/$which.data $!"; + my $ctf = new IO::File "$dumpdir/$which.tmp", 'w' + or die "$dumpdir/$which.tmp $!"; + print $ctf $ctype, "\n" or die $!; + close $ctf or die $!; + rename "$dumpdir/$which.tmp", "$dumpdir/$which.ctype" + or die "$dumpdir/$which.ctype $!"; +} + #---------- verifier object methods ---------- sub new_verifier { @@ -183,6 +376,7 @@ sub new_verifier { assocdb_dbh => undef, # must have AutoCommit=0, RaiseError=1 assocdb_path => 'caf-assocs.db', keys_path => 'caf-keys', + srcdump_path => 'caf-srcdump', assocdb_dsn => undef, assocdb_user => '', assocdb_password => '', @@ -197,6 +391,7 @@ sub new_verifier { dummy_param_name_prefix => 'caf__', cookie_name => "caf_assocsecret", password_param_name => 'password', + srcdump_param_name => 'caf_srcdump', username_param_names => [qw(username)], form_entry_size => 60, logout_param_names => [qw(caf_logout)], @@ -207,6 +402,7 @@ sub new_verifier { get_path_info => sub { $_[0]->path_info() }, get_cookie => sub { $_[0]->cookie($_[1]->{S}{cookie_name}) }, get_method => sub { $_[0]->request_method() }, + check_https => sub { !!$_[0]->https() }, get_url => sub { $_[0]->url(); }, is_login => sub { defined $_[1]->_rp('password_param_name') }, login_ok => \&login_ok_password, @@ -220,12 +416,32 @@ sub new_verifier { get_cookie_domain => \&get_cookie_domain, encrypted_only => 1, gen_start_html => sub { $_[0]->start_html($_[2]); }, + gen_footer_html => \&gen_plain_footer_html, + gen_licence_link_html => \&gen_plain_licence_link_html, + gen_source_link_html => \&gen_plain_source_link_html, gen_end_html => sub { $_[0]->end_html(); }, gen_login_form => \&gen_plain_login_form, gen_login_link => \&gen_plain_login_link, gen_postmainpage_form => \&gen_postmainpage_form, + srcdump_dump => \&srcdump_dump, + srcdump_prepare => \&srcdump_dirscan_prepare, + srcdump_licence_path => undef, + srcdump_licence_files => [qw(AGPLv3 CGI/Auth/Flexible/AGPLv3)], + srcdump_includedirs => sub { return @INC; }, + srcdump_filter_cwd => 1, + srcdump_system_dir => sub { $_[2] =~ m#^/etc/|^/usr/(?!local/)#; }, + srcdump_process_dir => \&srcdump_process_dir, + srcdump_vcs_dirs => [qw(.git .hg .svn CVS)], + srcdump_byvcs_git => \&srcdump_byvcs_git, + srcdump_byvcs_hg => \&srcdump_byvcs_hg, + srcdump_byvcs_svn => \&srcdump_byvcs_svn, + srcdump_byvcs_cvs => \&srcdump_byvcs_cvs, + srcdump_novcs => \&srcdump_novcs, + srcdump_excludes => [qw(*~ *.bak *.tmp), '#*#'], + dump => \&dump_plain, gettext => sub { gettext($_[2]); }, print => sub { print $_[2] or die $!; }, + debug => sub { }, # like print; msgs contain trailing \n }, Dbh => undef, }; @@ -236,6 +452,7 @@ sub new_verifier { } bless $verifier, $class; $verifier->_dbopen(); + $verifier->_ch('srcdump_prepare'); return $verifier; } @@ -270,6 +487,7 @@ sub _dbopen ($) { RaiseError => 1, ShowErrorStatement => 1, }); + umask $u; die "$dsn $! ?" unless $dbh; } $v->{Dbh} = $dbh; @@ -356,11 +574,21 @@ sub _rp ($$@) { my $p = scalar $r->_ch('get_param',$pn) } +sub _debug ($@) { + my ($r,@args) = @_; + $r->_ch('debug',@args); +} + sub _get_path ($$) { - my ($v,$keybase) = @_; - my $leaf = $v->{S}{"${keybase}_path"}; - my $dir = $v->{S}{dir}; + my ($r,$keybase) = @_; + my $leaf = $r->{S}{"${keybase}_path"}; + return $r->_absify_path($leaf); +} + +sub _absify_path ($$) { + my ($v,$leaf) = @_; return $leaf if $leaf =~ m,^/,; + my $dir = $v->{S}{dir}; die "relying on cwd by default ?! set dir" unless defined $dir; return "$dir/$leaf"; } @@ -496,8 +724,25 @@ my @ca = (-name => $r->{S}{cookie_name}, sub _check_divert_core ($) { my ($r) = @_; - my $meth = $r->_ch('get_method'); + my $srcdump = $r->_rp('srcdump_param_name'); + if ($srcdump) { + die if $srcdump =~ m/\W/; + return ({ Kind => 'SRCDUMP-'.uc $srcdump, + Message => undef, + CookieSecret => undef, + Params => { } }); + } + my $cooks = $r->_ch('get_cookie'); + + if ($r->{S}{encrypted_only} && !$r->_ch('check_https')) { + return ({ Kind => 'REDIRECT-HTTPS', + Message => $r->_gt("Redirecting to secure server..."), + CookieSecret => undef, + Params => { } }); + } + + my $meth = $r->_ch('get_method'); my $parmh = $r->_rp('assoc_param_name'); my $cookh = defined $cooks ? $r->hash($cooks) : undef; @@ -506,7 +751,7 @@ sub _check_divert_core ($) { ? $cooks : undef; my ($parmt) = $r->_identify($parmh, $parms); -#print STDERR "_c_d_c cookt=$cookt parmt=$parmt\n"; + $r->_debug("_c_d_c cookt=$cookt parmt=$parmt\n"); if ($r->_ch('is_logout')) { $r->_must_be_post(); @@ -520,8 +765,8 @@ sub _check_divert_core ($) { } if ($r->_ch('is_loggedout')) { die unless $meth eq 'GET'; - die unless $cookt; - die unless $parmt; + die if $cookt eq 'y'; + die if $parmt; return ({ Kind => 'SMALLPAGE-LOGGEDOUT', Message => $r->_gt("You have been logged out."), CookieSecret => '', @@ -530,7 +775,7 @@ sub _check_divert_core ($) { if ($r->_ch('is_login')) { $r->_must_be_post(); die unless $parmt; - if (!$cookt && $parmt eq 't') { + if (!$cookt && $parmt eq 'n') { return ({ Kind => 'SMALLPAGE-NOCOOKIE', Message => $r->_gt("You do not seem to have cookies". " enabled. You must enable cookies". @@ -719,7 +964,7 @@ sub check_divert ($) { my $dbh = $r->{Dbh}; $r->{Divert} = $r->_db_transaction(sub { $r->_check_divert_core(); }); $dbh->commit(); -#print STDERR Dumper($r->{Divert}); + $r->_debug(Data::Dumper->Dump([$r->{Divert}],[qw(divert)])); return $r->{Divert}; } @@ -767,6 +1012,11 @@ sub check_ok ($) { my $params = $divert->{Params}; my $cookie = $r->construct_cookie($cookiesecret); + if ($kind =~ m/^SRCDUMP-(\w+)$/) { + $r->_ch('srcdump_dump', (lc $1)); + return 0; + } + if ($kind =~ m/^REDIRECT-/) { # for redirects, we honour stored NextParams and SetCookie, # as we would for non-divert @@ -774,11 +1024,17 @@ sub check_ok ($) { $params->{$r->{S}{loggedout_param_names}[0]} = [ 1 ]; } elsif ($kind eq 'REDIRECT-LOGOUT') { $params->{$r->{S}{logout_param_names}[0]} = [ 1 ]; - } elsif ($kind eq 'REDIRECT-LOGGEDIN') { + } elsif ($kind =~ m/REDIRECT-(?:LOGGEDIN|HTTPS)/) { } else { die; } my $new_url = $r->url_with_query_params($params); + if ($kind eq 'REDIRECT-HTTPS') { + my $uri = URI->new($new_url); + die unless $uri->scheme eq 'http'; + $uri->scheme('https'); + $new_url = $uri->as_string(); + } $r->_ch('do_redirect',$new_url, $cookie); return 0; } @@ -806,8 +1062,9 @@ sub check_ok ($) { $r->_print($r->{Cgi}->header($r->_cgi_header_args($cookie)), $r->_ch('gen_start_html',$title), - (join "\n", @body), - $r->_ch('gen_end_html')); + (join "\n", (@body, + $r->_ch('gen_footer_html'), + $r->_ch('gen_end_html')))); return 0; } @@ -1006,6 +1263,8 @@ sub secret_cookie ($) { return $cookv; } +1; + __END__ =head1 NAME