forked from github/kensanata.oddmuse
Compare commits
3 Commits
as/encode_
...
crypto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdb7559c2b | ||
|
|
6c3b148014 | ||
|
|
2f9fa9306f |
2
Makefile
2
Makefile
@@ -21,7 +21,7 @@ clean:
|
|||||||
rm -rf build
|
rm -rf build
|
||||||
|
|
||||||
build/wiki.pl: wiki.pl
|
build/wiki.pl: wiki.pl
|
||||||
perl -lne "s/(\\\$$q->a\({-href=>'http:\/\/www.oddmuse.org\/'}, 'Oddmuse'\))/\\\$$q->a({-href=>'http:\/\/git.savannah.gnu.org\/cgit\/oddmuse.git\/tag\/?id=$(VERSION_NO)'}, 'wiki.pl') . ' ($(VERSION_NO)), see ' . \$$1/; print" < $< > $@
|
perl build.pl $< $@ $(VERSION_NO)
|
||||||
|
|
||||||
build/%-utf8.pl: modules/translations/%-utf8.pl
|
build/%-utf8.pl: modules/translations/%-utf8.pl
|
||||||
perl -lne "s/(AddModuleDescription\('[^']+', '[^']+')\)/\$$1, 'translations\/', '$(VERSION_NO)')/; print" < $< > $@
|
perl -lne "s/(AddModuleDescription\('[^']+', '[^']+')\)/\$$1, 'translations\/', '$(VERSION_NO)')/; print" < $< > $@
|
||||||
|
|||||||
36
build.pl
Normal file
36
build.pl
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Copyright (C) 2015 Alex Schroeder <alex@gnu.org>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 3 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# This script processes wiki.pl, replacing the link to www.oddmuse.org with a
|
||||||
|
# link to the current version. It also changes the default of the random
|
||||||
|
# Challenge Token so that everybody gets a different one.
|
||||||
|
|
||||||
|
use Crypt::Random qw( makerandom );
|
||||||
|
|
||||||
|
my ($old_file, $new_file, $version) = @ARGV;
|
||||||
|
|
||||||
|
undef $/;
|
||||||
|
open(my $in, '<:utf8', $old_file) or die "Cannot read $old_file: $!";
|
||||||
|
$_ = <$in>;
|
||||||
|
close($in);
|
||||||
|
|
||||||
|
s!(\$q->a\({-href=>'http://www.oddmuse.org/'}, 'Oddmuse'\))!\$q->a\({-href=>'http://git.savannah.gnu.org/cgit/oddmuse.git/tag/?id=$version'}, 'wiki.pl'\) . ' ($version), see ' . $1!;
|
||||||
|
|
||||||
|
my $r = join('', map { sprintf('\x%x', makerandom( Size => 8, Uniform => 1, Strength => 1 )) } 1..16);
|
||||||
|
|
||||||
|
s!our \$TokenKey //= '(.*?)'!our \$TokenKey //= '$r'!;
|
||||||
|
|
||||||
|
open(my $out, '>:utf8', $new_file) or die "Cannot write $new_file: $!";
|
||||||
|
print $out $_;
|
||||||
|
close($out);
|
||||||
25
t/crypto.t
Normal file
25
t/crypto.t
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Copyright (C) 2015 Alex Schroeder <alex@gnu.org>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
require 't/test.pl';
|
||||||
|
package OddMuse;
|
||||||
|
use Test::More tests => 3;
|
||||||
|
|
||||||
|
$page = get_page('title=Test text="Editing a page"');
|
||||||
|
test_page_negative($page, "Editing a page");
|
||||||
|
test_page($page, "Token is missing");
|
||||||
|
test_page(update_page("Test", "Editing a page using update_page"),
|
||||||
|
"Editing a page using update_page");
|
||||||
|
print $redirect;
|
||||||
@@ -13,7 +13,9 @@
|
|||||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package OddMuse;
|
package OddMuse;
|
||||||
use lib '.';
|
use Crypt::CBC;
|
||||||
|
use Crypt::Cipher::AES;
|
||||||
|
use MIME::Base64;
|
||||||
use XML::LibXML;
|
use XML::LibXML;
|
||||||
use utf8;
|
use utf8;
|
||||||
use vars qw($raw);
|
use vars qw($raw);
|
||||||
@@ -90,8 +92,9 @@ sub update_page {
|
|||||||
$text = url_encode($text);
|
$text = url_encode($text);
|
||||||
$summary = url_encode($summary);
|
$summary = url_encode($summary);
|
||||||
$minor = $minor ? 'on' : 'off';
|
$minor = $minor ? 'on' : 'off';
|
||||||
|
my $token = GetChallengeToken('edit', $id);
|
||||||
my $rest = join(' ', @rest);
|
my $rest = join(' ', @rest);
|
||||||
$redirect = capture("perl wiki.pl 'Save=1' 'title=$page' 'summary=$summary' 'recent_edit=$minor' 'text=$text' 'pwd=$pwd' $rest");
|
$redirect = capture("perl wiki.pl 'Save=1' 'title=$page' 'summary=$summary' 'recent_edit=$minor' 'text=$text' 'token=$token' 'pwd=$pwd' $rest");
|
||||||
$output = capture("perl wiki.pl action=browse id=$page $rest");
|
$output = capture("perl wiki.pl action=browse id=$page $rest");
|
||||||
if ($redirect =~ /^Status: 302 /) {
|
if ($redirect =~ /^Status: 302 /) {
|
||||||
# just in case a new page got created or NearMap or InterMap
|
# just in case a new page got created or NearMap or InterMap
|
||||||
|
|||||||
18
test.pl
Normal file
18
test.pl
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
use Crypt::CBC;
|
||||||
|
use Crypt::Cipher::AES;
|
||||||
|
use MIME::Base64;
|
||||||
|
|
||||||
|
my $key = 'my secret key'; # length has to be valid key size for this cipher
|
||||||
|
my $cipher = Crypt::CBC->new( -cipher=>'Cipher::AES', -key=>$key );
|
||||||
|
my $ciphertext = $cipher->encrypt("secret data");
|
||||||
|
my $code = encode_base64($ciphertext);
|
||||||
|
chomp $code;
|
||||||
|
print "$code\n";
|
||||||
|
|
||||||
|
my $cipher2 = Crypt::CBC->new( -cipher=>'Cipher::AES', -key=>$key );
|
||||||
|
my $plaintext = $cipher2->decrypt(decode_base64($code));
|
||||||
|
print $plaintext . "\n";
|
||||||
|
|
||||||
|
use Crypt::Random qw( makerandom );
|
||||||
|
my $r = join('', map { sprintf("\\x%x", makerandom( Size => 8, Uniform => 1, Strength => 1 )) } 1..16);
|
||||||
|
printf "$r\n";
|
||||||
51
wiki.pl
51
wiki.pl
@@ -35,6 +35,9 @@ use utf8; # in case anybody ever addes UTF8 characters to the source
|
|||||||
use CGI qw/-utf8/;
|
use CGI qw/-utf8/;
|
||||||
use CGI::Carp qw(fatalsToBrowser);
|
use CGI::Carp qw(fatalsToBrowser);
|
||||||
use File::Glob ':glob';
|
use File::Glob ':glob';
|
||||||
|
use Crypt::CBC;
|
||||||
|
use Crypt::Cipher::AES;
|
||||||
|
use MIME::Base64;
|
||||||
local $| = 1; # Do not buffer output (localized for mod_perl)
|
local $| = 1; # Do not buffer output (localized for mod_perl)
|
||||||
|
|
||||||
# Options:
|
# Options:
|
||||||
@@ -91,7 +94,8 @@ our $AdminPass //= ''; # Whitespace separated passwords.
|
|||||||
our $EditPass //= ''; # Whitespace separated passwords.
|
our $EditPass //= ''; # Whitespace separated passwords.
|
||||||
our $PassHashFunction //= ''; # Name of the function to create hashes
|
our $PassHashFunction //= ''; # Name of the function to create hashes
|
||||||
our $PassSalt //= ''; # Salt will be added to any password before hashing
|
our $PassSalt //= ''; # Salt will be added to any password before hashing
|
||||||
|
# Key to encrypt challenge token. Use make prepare to create a new one.
|
||||||
|
our $TokenKey //= '\x40\x77\x79\xfc\xd9\x33\x21\xf0\x6e\xf7\xa1\x86\xbe\xc6\x5f\xed';
|
||||||
our $BannedHosts = 'BannedHosts'; # Page for banned hosts
|
our $BannedHosts = 'BannedHosts'; # Page for banned hosts
|
||||||
our $BannedCanRead = 1; # 1 = banned cannot edit, 0 = banned cannot read
|
our $BannedCanRead = 1; # 1 = banned cannot edit, 0 = banned cannot read
|
||||||
our $BannedContent = 'BannedContent'; # Page for banned content (usually for link-ban)
|
our $BannedContent = 'BannedContent'; # Page for banned content (usually for link-ban)
|
||||||
@@ -1326,6 +1330,7 @@ sub DoBrowseRequest {
|
|||||||
SetParam('action', 'search'); # make sure this gets a NOINDEX
|
SetParam('action', 'search'); # make sure this gets a NOINDEX
|
||||||
DoSearch();
|
DoSearch();
|
||||||
} elsif (GetParam('title', '') and not GetParam('Cancel', '')) {
|
} elsif (GetParam('title', '') and not GetParam('Cancel', '')) {
|
||||||
|
SetParam('action', 'edit'); # make sure this gets a NOINDEX
|
||||||
DoPost(GetParam('title', ''));
|
DoPost(GetParam('title', ''));
|
||||||
} else {
|
} else {
|
||||||
BrowseResolvedPage($id || $HomePage); # default action!
|
BrowseResolvedPage($id || $HomePage); # default action!
|
||||||
@@ -2483,12 +2488,34 @@ sub GetCommentForm {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub GetChallengeToken {
|
||||||
|
my ($action, $id) = @_;
|
||||||
|
my $token = join($FS, $Now, GetParam('username'), $q->remote_addr(), $action, $id);
|
||||||
|
my $cipher = Crypt::CBC->new( -cipher=>'Cipher::AES', -key=>$TokenKey );
|
||||||
|
my $ciphertext = $cipher->encrypt($token);
|
||||||
|
return encode_base64($ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub CheckToken {
|
||||||
|
my $code = UnquoteHtml(GetParam('token'));
|
||||||
|
ReportError(T('Token is missing.'), '403 FORBIDDEN') unless $code;
|
||||||
|
my $cipher = Crypt::CBC->new( -cipher=>'Cipher::AES', -key=>$TokenKey );
|
||||||
|
my ($ts, $name, $ip, $action, $id) = split(/$FS/, $cipher->decrypt(decode_base64($code)));
|
||||||
|
# FIXME add retry functionality
|
||||||
|
ReportError(T('Token mismatch on time.'), '403 FORBIDDEN', '', "$ts >= $Now - 60 * 60") unless $ts >= $Now - 60 * 60; # 1h
|
||||||
|
ReportError(T('Token mismatch on IP number.'), '403 FORBIDDEN', '', $q->remote_addr . "eq $ip") unless $q->remote_addr eq $ip;
|
||||||
|
ReportError(T('Token mismatch on action.'), '403 FORBIDDEN', '', GetParam('action') . "eq $action") unless GetParam('action') eq $action;
|
||||||
|
ReportError(T('Token mismatch on id.'), '403 FORBIDDEN') unless GetParam('id', GetParam('title')) eq $id;
|
||||||
|
}
|
||||||
|
|
||||||
sub GetFormStart {
|
sub GetFormStart {
|
||||||
my ($ignore, $method, $class) = @_;
|
my ($ignore, $method, $class, $action, $id) = @_;
|
||||||
$method ||= 'post';
|
$method ||= 'post';
|
||||||
$class ||= 'form';
|
$class ||= 'form';
|
||||||
return $q->start_multipart_form(-method=>$method, -action=>$FullUrl,
|
my $html = $q->start_multipart_form(-method=>$method, -action=>$FullUrl,
|
||||||
-accept_charset=>'utf-8', -class=>$class);
|
-accept_charset=>'utf-8', -class=>$class);
|
||||||
|
$html .= GetHiddenValue('token', GetChallengeToken($action, $id)) if $action;
|
||||||
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub GetSearchForm {
|
sub GetSearchForm {
|
||||||
@@ -3055,9 +3082,9 @@ sub DoEdit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub GetEditForm {
|
sub GetEditForm {
|
||||||
my ($page_name, $upload, $oldText, $revision) = @_;
|
my ($id, $upload, $oldText, $revision) = @_;
|
||||||
my $html = GetFormStart(undef, undef, $upload ? 'edit upload' : 'edit text') # protected by questionasker
|
my $html = GetFormStart(undef, undef, $upload ? 'edit upload' : 'edit text', 'edit', $id) # protected by questionasker
|
||||||
.$q->p(GetHiddenValue("title", $page_name),
|
.$q->p(GetHiddenValue("title", $id),
|
||||||
($revision ? GetHiddenValue('revision', $revision) : ''),
|
($revision ? GetHiddenValue('revision', $revision) : ''),
|
||||||
GetHiddenValue('oldtime', GetParam('oldtime', $Page{ts})), # prefer parameter over actual timestamp
|
GetHiddenValue('oldtime', GetParam('oldtime', $Page{ts})), # prefer parameter over actual timestamp
|
||||||
($upload ? GetUpload() : GetTextArea('text', $oldText)));
|
($upload ? GetUpload() : GetTextArea('text', $oldText)));
|
||||||
@@ -3075,9 +3102,9 @@ sub GetEditForm {
|
|||||||
($upload ? '' : ' ' . $q->submit(-name=>'Preview', -accesskey=>T('p'), -value=>T('Preview'))).
|
($upload ? '' : ' ' . $q->submit(-name=>'Preview', -accesskey=>T('p'), -value=>T('Preview'))).
|
||||||
' '.$q->submit(-name=>'Cancel', -value=>T('Cancel')));
|
' '.$q->submit(-name=>'Cancel', -value=>T('Cancel')));
|
||||||
if ($upload) {
|
if ($upload) {
|
||||||
$html .= $q->p(ScriptLink('action=edit;upload=0;id=' . UrlEncode($page_name), T('Replace this file with text'), 'upload'));
|
$html .= $q->p(ScriptLink('action=edit;upload=0;id=' . UrlEncode($id), T('Replace this file with text'), 'upload'));
|
||||||
} elsif ($UploadAllowed or UserIsAdmin()) {
|
} elsif ($UploadAllowed or UserIsAdmin()) {
|
||||||
$html .= $q->p(ScriptLink('action=edit;upload=1;id=' . UrlEncode($page_name), T('Replace this text with a file'), 'upload'));
|
$html .= $q->p(ScriptLink('action=edit;upload=1;id=' . UrlEncode($id), T('Replace this text with a file'), 'upload'));
|
||||||
}
|
}
|
||||||
$html .= $q->end_form();
|
$html .= $q->end_form();
|
||||||
return $html;
|
return $html;
|
||||||
@@ -3104,9 +3131,8 @@ sub DoDownload {
|
|||||||
ReportError(Ts('Files of type %s are not allowed.', $type), '415 UNSUPPORTED MEDIA TYPE');
|
ReportError(Ts('Files of type %s are not allowed.', $type), '415 UNSUPPORTED MEDIA TYPE');
|
||||||
}
|
}
|
||||||
print GetHttpHeader($type, $Page{ts}, undef, $encoding);
|
print GetHttpHeader($type, $Page{ts}, undef, $encoding);
|
||||||
require MIME::Base64;
|
|
||||||
binmode(STDOUT, ":pop:raw"); # need to pop utf8 for Windows users!?
|
binmode(STDOUT, ":pop:raw"); # need to pop utf8 for Windows users!?
|
||||||
print MIME::Base64::decode($data);
|
print decode_base64($data);
|
||||||
} else {
|
} else {
|
||||||
print GetHttpHeader('text/plain', $Page{ts});
|
print GetHttpHeader('text/plain', $Page{ts});
|
||||||
print $text;
|
print $text;
|
||||||
@@ -3569,6 +3595,7 @@ sub Replace {
|
|||||||
sub DoPost {
|
sub DoPost {
|
||||||
my $id = FreeToNormal(shift);
|
my $id = FreeToNormal(shift);
|
||||||
UserCanEditOrDie($id);
|
UserCanEditOrDie($id);
|
||||||
|
CheckToken();
|
||||||
# Lock before getting old page to prevent races
|
# Lock before getting old page to prevent races
|
||||||
RequestLockOrError(); # fatal
|
RequestLockOrError(); # fatal
|
||||||
OpenPage($id);
|
OpenPage($id);
|
||||||
@@ -3597,7 +3624,7 @@ sub DoPost {
|
|||||||
local $/ = undef; # Read complete files
|
local $/ = undef; # Read complete files
|
||||||
my $content = <$file>; # Apparently we cannot count on <$file> to always work within the eval!?
|
my $content = <$file>; # Apparently we cannot count on <$file> to always work within the eval!?
|
||||||
my $encoding = substr($content, 0, 2) eq "\x1f\x8b" ? 'gzip' : '';
|
my $encoding = substr($content, 0, 2) eq "\x1f\x8b" ? 'gzip' : '';
|
||||||
eval { require MIME::Base64; $_ = MIME::Base64::encode($content) };
|
$_ = encode_base64($content);
|
||||||
$string = "#FILE $type $encoding\n" . $_;
|
$string = "#FILE $type $encoding\n" . $_;
|
||||||
} else { # ordinary text edit
|
} else { # ordinary text edit
|
||||||
$string = AddComment($old, $comment) if $comment;
|
$string = AddComment($old, $comment) if $comment;
|
||||||
|
|||||||
Reference in New Issue
Block a user