X-Git-Url: https://sipb.mit.edu/gitweb.cgi/ikiwiki.git/blobdiff_plain/773731743df52d2ef2d8ce93c095be981bb9bfc8..e4d9da55d923cdd78cd07959de44edf17a9a5fe5:/ikiwiki diff --git a/ikiwiki b/ikiwiki index 54589ec2e..51f324d13 100755 --- a/ikiwiki +++ b/ikiwiki @@ -1,4 +1,7 @@ #!/usr/bin/perl -T + +eval 'exec /usr/bin/perl -T -S $0 ${1+"$@"}' + if 0; # not running under some shell $ENV{PATH}="/usr/local/bin:/usr/bin:/bin"; use warnings; @@ -30,8 +33,9 @@ our %config=( #{{{ wrappermode => undef, srcdir => undef, destdir => undef, - templatedir => undef, + templatedir => "/usr/share/ikiwiki/templates", setup => undef, + adminuser => undef, ); #}}} GetOptions( #{{{ @@ -51,21 +55,28 @@ GetOptions( #{{{ "exclude=s@" => sub { $config{wiki_file_prune_regexp}=qr/$config{wiki_file_prune_regexp}|$_[1]/; }, + "adminuser=s@" => sub { push @{$config{adminuser}}, $_[1] }, + "templatedir=s" => sub { $config{templatedir}=possibly_foolish_untaint($_[1]) }, ) || usage(); if (! $config{setup}) { - usage() unless @ARGV == 3; + usage() unless @ARGV == 2; $config{srcdir} = possibly_foolish_untaint(shift); - $config{templatedir} = possibly_foolish_untaint(shift); $config{destdir} = possibly_foolish_untaint(shift); + checkoptions(); +} +#}}} + +sub checkoptions { #{{{ if ($config{cgi} && ! length $config{url}) { error("Must specify url to wiki with --url when using --cgi"); } -} -#}}} + $config{wikistatedir}="$config{srcdir}/.ikiwiki" + unless exists $config{wikistatedir}; +} #}}} sub usage { #{{{ - die "usage: ikiwiki [options] source templates dest\n"; + die "usage: ikiwiki [options] source dest\n"; } #}}} sub error { #{{{ @@ -339,6 +350,7 @@ sub finalize ($$$) { #{{{ if (length $config{cgiurl}) { $template->param(editurl => "$config{cgiurl}?do=edit&page=$page"); + $template->param(prefsurl => "$config{cgiurl}?do=prefs"); if ($config{svn}) { $template->param(recentchangesurl => "$config{cgiurl}?do=recentchanges"); } @@ -409,10 +421,11 @@ sub render ($) { #{{{ sub lockwiki () { #{{{ # Take an exclusive lock on the wiki to prevent multiple concurrent # run issues. The lock will be dropped on program exit. - if (! -d "$config{srcdir}/.ikiwiki") { - mkdir("$config{srcdir}/.ikiwiki"); + if (! -d $config{wikistatedir}) { + mkdir($config{wikistatedir}); } - open(WIKILOCK, ">$config{srcdir}/.ikiwiki/lockfile") || error ("cannot write to lockfile: $!"); + open(WIKILOCK, ">$config{wikistatedir}/lockfile") || + error ("cannot write to $config{wikistatedir}/lockfile: $!"); if (! flock(WIKILOCK, 2 | 4)) { debug("wiki seems to be locked, waiting for lock"); my $wait=600; # arbitrary, but don't hang forever to @@ -430,7 +443,7 @@ sub unlockwiki () { #{{{ } #}}} sub loadindex () { #{{{ - open (IN, "$config{srcdir}/.ikiwiki/index") || return; + open (IN, "$config{wikistatedir}/index") || return; while () { $_=possibly_foolish_untaint($_); chomp; @@ -446,10 +459,11 @@ sub loadindex () { #{{{ } #}}} sub saveindex () { #{{{ - if (! -d "$config{srcdir}/.ikiwiki") { - mkdir("$config{srcdir}/.ikiwiki"); + if (! -d $config{wikistatedir}) { + mkdir($config{wikistatedir}); } - open (OUT, ">$config{srcdir}/.ikiwiki/index") || error("cannot write to index: $!"); + open (OUT, ">$config{wikistatedir}/index") || + error("cannot write to $config{wikistatedir}/index: $!"); foreach my $page (keys %oldpagemtime) { print OUT "$oldpagemtime{$page} $pagesources{$page} $renderedfiles{$page} ". join(" ", @{$links{$page}})."\n" @@ -752,8 +766,7 @@ FILE: foreach my $file (@files) { } } #}}} -sub gen_wrapper (@) { #{{{ - my %config=(@_); +sub gen_wrapper () { #{{{ eval q{use Cwd 'abs_path'}; $config{srcdir}=abs_path($config{srcdir}); $config{destdir}=abs_path($config{destdir}); @@ -766,8 +779,9 @@ sub gen_wrapper (@) { #{{{ error("cannot create a wrapper that uses a setup file"); } - my @params=($config{srcdir}, $config{templatedir}, $config{destdir}, - "--wikiname=$config{wikiname}"); + my @params=($config{srcdir}, $config{destdir}, + "--wikiname=$config{wikiname}", + "--templatedir=$config{templatedir}"); push @params, "--verbose" if $config{verbose}; push @params, "--rebuild" if $config{rebuild}; push @params, "--nosvn" if !$config{svn}; @@ -777,6 +791,7 @@ sub gen_wrapper (@) { #{{{ push @params, "--historyurl=$config{historyurl}" if length $config{historyurl}; push @params, "--diffurl=$config{diffurl}" if length $config{diffurl}; push @params, "--anonok" if $config{anonok}; + push @params, "--adminuser=$_" foreach @{$config{adminuser}}; my $params=join(" ", @params); my $call=''; foreach my $p ($this, $this, @params) { @@ -875,26 +890,46 @@ sub userinfo_get ($$) { #{{{ my $field=shift; eval q{use Storable}; - my $userdata=eval{ Storable::lock_retrieve("$config{srcdir}/.ikiwiki/userdb") }; + my $userdata=eval{ Storable::lock_retrieve("$config{wikistatedir}/userdb") }; if (! defined $userdata || ! ref $userdata || - ! exists $userdata->{$user} || ! ref $userdata->{$user}) { + ! exists $userdata->{$user} || ! ref $userdata->{$user} || + ! exists $userdata->{$user}->{$field}) { return ""; } return $userdata->{$user}->{$field}; } #}}} -sub userinfo_set ($$) { #{{{ +sub userinfo_set ($$$) { #{{{ + my $user=shift; + my $field=shift; + my $value=shift; + + eval q{use Storable}; + my $userdata=eval{ Storable::lock_retrieve("$config{wikistatedir}/userdb") }; + if (! defined $userdata || ! ref $userdata || + ! exists $userdata->{$user} || ! ref $userdata->{$user}) { + return ""; + } + + $userdata->{$user}->{$field}=$value; + my $oldmask=umask(077); + my $ret=Storable::lock_store($userdata, "$config{wikistatedir}/userdb"); + umask($oldmask); + return $ret; +} #}}} + +sub userinfo_setall ($$) { #{{{ my $user=shift; my $info=shift; eval q{use Storable}; - my $userdata=eval{ Storable::lock_retrieve("$config{srcdir}/.ikiwiki/userdb") }; + my $userdata=eval{ Storable::lock_retrieve("$config{wikistatedir}/userdb") }; if (! defined $userdata || ! ref $userdata) { $userdata={}; } $userdata->{$user}=$info; my $oldmask=umask(077); - my $ret=Storable::lock_store($userdata, "$config{srcdir}/.ikiwiki/userdb"); + my $ret=Storable::lock_store($userdata, "$config{wikistatedir}/userdb"); umask($oldmask); return $ret; } #}}} @@ -905,7 +940,7 @@ sub cgi_signin ($$) { #{{{ eval q{use CGI::FormBuilder}; my $form = CGI::FormBuilder->new( - title => "$config{wikiname} signin", + title => "signin", fields => [qw(do page from name password confirm_password email)], header => 1, method => 'POST', @@ -932,7 +967,7 @@ sub cgi_signin ($$) { #{{{ $form->field(name => "confirm_password", type => "password", required => 0); $form->field(name => "email", required => 0); if ($q->param("do") ne "signin") { - $form->text("You need to log in before you can edit pages."); + $form->text("You need to log in first."); } if ($form->submitted) { @@ -1010,7 +1045,7 @@ sub cgi_signin ($$) { #{{{ } elsif ($form->submitted eq 'Register') { my $user_name=$form->field('name'); - if (userinfo_set($user_name, { + if (userinfo_setall($user_name, { 'email' => $form->field('email'), 'password' => $form->field('password'), 'regdate' => time @@ -1059,6 +1094,128 @@ sub cgi_signin ($$) { #{{{ } } #}}} +sub is_admin ($) { #{{{ + my $user_name=shift; + + return grep { $_ eq $user_name } @{$config{adminuser}}; +} #}}} + +sub glob_match ($$) { #{{{ + my $page=shift; + my $glob=shift; + + # turn glob into safe regexp + $glob=quotemeta($glob); + $glob=~s/\\\*/.*/g; + $glob=~s/\\\?/./g; + $glob=~s!\\/!/!g; + + $page=~/^$glob$/i; +} #}}} + +sub globlist_match ($$) { #{{{ + my $page=shift; + my @globlist=split(" ", shift); + + # check any negated globs first + foreach my $glob (@globlist) { + return 0 if $glob=~/^!(.*)/ && glob_match($page, $1); + } + + foreach my $glob (@globlist) { + return 1 if glob_match($page, $glob); + } + + return 0; +} #}}} + +sub page_locked ($$;$) { #{{{ + my $page=shift; + my $session=shift; + my $nonfatal=shift; + + my $user=$session->param("name"); + return if length $user && is_admin($user); + + foreach my $admin (@{$config{adminuser}}) { + my $locked_pages=userinfo_get($admin, "locked_pages"); + if (globlist_match($page, userinfo_get($admin, "locked_pages"))) { + return 1 if $nonfatal; + error(htmllink("", $page, 1)." is locked by ". + htmllink("", $admin, 1)." and cannot be edited."); + } + } + + return 0; +} #}}} + +sub cgi_prefs ($$) { #{{{ + my $q=shift; + my $session=shift; + + eval q{use CGI::FormBuilder}; + my $form = CGI::FormBuilder->new( + title => "preferences", + fields => [qw(do name password confirm_password email locked_pages)], + header => 0, + method => 'POST', + validate => { + confirm_password => { + perl => q{eq $form->field("password")}, + }, + email => 'EMAIL', + }, + required => 'NONE', + javascript => 0, + params => $q, + action => $q->request_uri, + template => (-e "$config{templatedir}/prefs.tmpl" ? + "$config{templatedir}/prefs.tmpl" : "") + ); + my @buttons=("Save Preferences", "Logout", "Cancel"); + + my $user_name=$session->param("name"); + $form->field(name => "do", type => "hidden"); + $form->field(name => "name", disabled => 1, + value => $user_name, force => 1); + $form->field(name => "password", type => "password"); + $form->field(name => "confirm_password", type => "password"); + $form->field(name => "locked_pages", size => 50, + comment => "(".htmllink("", "GlobList", 1).")"); + + if (! is_admin($user_name)) { + $form->field(name => "locked_pages", type => "hidden"); + } + + if (! $form->submitted) { + $form->field(name => "email", force => 1, + value => userinfo_get($user_name, "email")); + $form->field(name => "locked_pages", force => 1, + value => userinfo_get($user_name, "locked_pages")); + } + + if ($form->submitted eq 'Logout') { + $session->delete(); + print $q->redirect($config{url}); + return; + } + elsif ($form->submitted eq 'Cancel') { + print $q->redirect($config{url}); + return; + } + elsif ($form->submitted eq "Save Preferences" && $form->validate) { + foreach my $field (qw(password email locked_pages)) { + if (length $form->field($field)) { + userinfo_set($user_name, $field, $form->field($field)) || error("failed to set $field"); + } + } + $form->text("Preferences saved."); + } + + print $session->header(); + print misctemplate($form->title, $form->render(submit => \@buttons)); +} #}}} + sub cgi_editpage ($$) { #{{{ my $q=shift; my $session=shift; @@ -1160,8 +1317,10 @@ sub cgi_editpage ($$) { #{{{ push @page_locs, $dir.$page; } - @page_locs = grep { ! exists - $pagesources{lc($_)} } @page_locs; + @page_locs = grep { + ! exists $pagesources{lc($_)} && + ! page_locked($_, $session, 1) + } @page_locs; } $form->tmpl_param("page_select", 1); @@ -1170,6 +1329,7 @@ sub cgi_editpage ($$) { #{{{ $form->title("creating $page"); } elsif ($form->field("do") eq "edit") { + page_locked($page, $session); if (! defined $form->field('content') || ! length $form->field('content')) { my $content=""; @@ -1189,13 +1349,15 @@ sub cgi_editpage ($$) { #{{{ } else { # save page + page_locked($page, $session); + my $content=$form->field('content'); $content=~s/\r\n/\n/g; $content=~s/\r/\n/g; writefile("$config{srcdir}/$file", $content); my $message="web commit "; - if ($session->param("name")) { + if (length $session->param("name")) { $message.="by ".$session->param("name"); } else { @@ -1263,12 +1425,13 @@ sub cgi () { #{{{ my $oldmask=umask(077); my $session = CGI::Session->new("driver:db_file", $q, - { FileName => "$config{srcdir}/.ikiwiki/sessions.db" }); + { FileName => "$config{wikistatedir}/sessions.db" }); umask($oldmask); # Everything below this point needs the user to be signed in. if ((! $config{anonok} && ! defined $session->param("name") || - ! userinfo_get($session->param("name"), "regdate")) || $do eq 'signin') { + ! defined $session->param("name") || + ! userinfo_get($session->param("name"), "regdate")) || $do eq 'signin') { cgi_signin($q, $session); # Force session flush with safe umask. @@ -1282,6 +1445,9 @@ sub cgi () { #{{{ if ($do eq 'create' || $do eq 'edit') { cgi_editpage($q, $session); } + elsif ($do eq 'prefs') { + cgi_prefs($q, $session); + } else { error("unknown do parameter"); } @@ -1305,7 +1471,7 @@ sub setup () { # {{{ setup() if $config{setup}; lockwiki(); if ($config{wrapper}) { - gen_wrapper(%config); + gen_wrapper(); exit; } memoize('pagename');