# $File: //depot/libOurNet/BBS/lib/OurNet/BBS/Authen.pm $ $Author: autrijus $
# $Revision: #3 $ $Change: 3849 $ $DateTime: 2003/01/25 19:36:01 $
package OurNet::BBS::Authen;
$OurNet::BBS::Authen::VERSION = '0.4';
use strict;
no warnings 'deprecated';
use RPC::PlServer::Comm;
use fields qw/gnupg who pass login keyid user challenge/;
use enum qw/BITMASK:CIPHER_ NONE BASIC PGP/;
use enum qw/BITMASK:AUTH_ NONE CRYPT PGP/;
my $i = 0;
our $OP = {
# STATUS Operators
(map { ("STATUS_$_" => $i++ ) } (
qw/FAILED OK ACCEPTED FORBIDDEN IGNORED UNKNOWN_OP/,
qw/NO_USER NO_PUBKEY BAD_PUBKEY BAD_SIGNATURE/,
)),
# HASH Operators
(map { ("HASH_$_" => $i++ ) } (
qw/FETCH FIRSTKEY NEXTKEY DESTROY FETCHARRAY/,
qw/DEREFERENCE STORE DELETE EXISTS/,
)),
# ARRAY Operators
(map { ("ARRAY_$_" => $i++) } (
qw/FETCH DESTROY FETCHARRAY SHIFT UNSHIFT PUSH POP/,
qw/DEREFERENCE STORE DELETE EXISTS FETCHSIZE/,
)),
# OBJECT Operators (the usual ones)
(map { ("OBJECT_$_" => $i++ ) } (
qw/SPAWN DESTROY new refresh refresh_meta board id/,
qw/backend remove purge name ego writeok readok daemonize/,
qw/CACHE/,
)),
# CODE Operators
(map { ("CODE_$_" => $i++ ) } (
qw/EXECUTE/,
)),
};
our $OPREV = {
map { $OP->{$_} => substr($_, index($_, '_') + 1) }
keys %{$OP}
};
our $Pubkey;
$OP = { %{$OP}, reverse %{$OP} };
sub load_ok {
return ($^O ne 'MSWin32' and eval("use $_[-1]; 1"));
}
sub new {
my ($class, $who, $pass) = @_;
my $self = fields::new($class);
$self->{who} = $who or die "need recipients";
if (load_ok('GnuPG::Interface')) {
$self->{gnupg} = GnuPG::Interface->new;
$self->{gnupg}->options->hash_init(armor => 1, always_trust => 1);
$self->{gnupg}->options->meta_interactive(0);
$self->{gnupg}->options->push_recipients($who);
$self->{gnupg}->passphrase($self->{pass} = $pass) if defined $pass;
}
return $self;
}
sub export_key {
my $self = shift;
return scalar `gpg --armor --export $self->{keyid}`;
}
sub test {
my $self = shift;
return ($self->{gnupg} and $self->{gnupg}->test_default_key_passphrase);
}
# query for existing BCB ciphers
sub suites {
my ($self, @ciphers) = @_;
@ciphers = map { "Crypt::$_" } (
qw/Rijndael Twofish2 Twofish Blowfish IDEA DES_EDE3/,
qw/DES TEA GOST Rijndael_PP Blowfish_PP DES_PP/,
) unless @ciphers;
my @suites;
foreach my $cipher (@ciphers) {
no warnings;
local $@;
eval "use $cipher ()";
next if $@;
return $cipher if $#_;
push @suites, $cipher;
}
warn "\n[Authen] cannot find a block cipher suite from:\n@ciphers\n".
"secure connection will be disabled.\n" unless @suites;
return @suites;
}
# adjust security levels
sub adjust {
my ($self, $cipher_level, $auth_level, $keyid, $clientflag) = @_;
$cipher_level ||= (CIPHER_NONE | CIPHER_BASIC | CIPHER_PGP);
$auth_level ||= (AUTH_NONE | AUTH_CRYPT | AUTH_PGP);
if ($cipher_level & CIPHER_PGP or $auth_level & AUTH_PGP) {
if (!load_ok('GnuPG::Interface')) {
# pgp support broken, so...
$cipher_level &= ~CIPHER_PGP;
$auth_level &= ~AUTH_PGP;
}
elsif ($keyid) {
unless ($Pubkey = `gpg --armor --export $keyid`) {
$cipher_level &= ~CIPHER_PGP;
$auth_level &= ~AUTH_PGP;
}
}
elsif (!`gpg --version`) {
$cipher_level &= ~CIPHER_PGP;
$auth_level &= ~AUTH_PGP;
}
else {
$cipher_level &= ~CIPHER_PGP unless $clientflag;
$auth_level &= ~AUTH_PGP;
}
}
if ($auth_level & AUTH_CRYPT) {
unless (eval { crypt(' ', 'OurNet') } eq 'Ou6zLHZGLzASY') {
$auth_level &= ~AUTH_CRYPT;
}
}
return ($cipher_level, $auth_level);
}
sub setpass {
my ($self, $pass) = @_;
$self->{gnupg}->passphrase($self->{pass} = $pass);
}
sub gpg_setup {
my ($input, $output, $stderr) = (
IO::Handle->new(),
IO::Handle->new(),
IO::Handle->new(),
);
my $handles = GnuPG::Handles->new(
stdin => $input,
stdout => $output,
stderr => $stderr,
);
return ($input, $output, $stderr, $handles);
}
foreach my $method (qw/sign verify encrypt clearsign import_keys decrypt/) {
my $subname = $method;
no strict 'refs';
$subname =~ s/_keys/_key/;
*{__PACKAGE__."::$subname"} = sub {
my $self = shift;
if ($method eq 'decrypt' and not defined $self->{pass}) {
print "error: no passphrase for $self->{who}.\n";
exit;
}
my ($i, $o, $e, $h) = gpg_setup();
my $pid = $self->{gnupg}->$method(
handles => $h,
command_args => (
($method eq 'clearsign') ? (
['--default-key', $self->{keyid}],
) : ($method eq 'sign') ? (
['--default-key', $self->{keyid}],
) : ( '' ),
)
);
if (@_) {
print $i @_;
close $i;
}
local $/;
my $ret = ($method eq 'verify') ? <$e> : <$o>; # reading the output
wait; # clean up the finished GnuPG process
return $ret;
};
}
# fix win32 behaviours because GnuPG::Interface will simply hang
if ($^O eq 'MSWin32') {
*POSIX::STDERR_FILENO = sub { 2 };
*POSIX::STDOUT_FILENO = sub { 1 };
*POSIX::STDIN_FILENO = sub { 0 };
eval <<'.';
no warnings 'redefine';
sub import_key {
my ($self, $pubkey) = @_;
open my $FH, '| gpg --import --quiet --batch';
print $FH $pubkey;
close $FH;
return $pubkey;
}
sub encrypt {
my ($self, $message) = @_;
open my $FH, '>', 'encrypt' or die "$!";
print $FH $message;
close $FH;
return if system("gpg --yes --encrypt --quiet --batch --always-trust --armor -r $self->{who} -o encrypt.gpg encrypt");
local $/;
open $FH, 'encrypt.gpg' or die "$!";
$message = <$FH>;
close $FH;
unlink 'encrypt';
unlink 'encrypt.gpg';
return $message;
}
sub clearsign {
my ($self, $message) = @_;
open my $FH, '> encrypt' or die "$!";
print $FH $message;
close $FH;
return if system(
"gpg --yes --clearsign -u $self->{keyid} -o encrypt.gpg encrypt"
);
local $/;
open $FH, 'encrypt.gpg' or die "$!";
$message = <$FH>;
close $FH;
unlink 'encrypt';
unlink 'encrypt.gpg';
return $message;
}
.
}
1;
####################################################################### {{{
# The following section is a modified version of RPC::PlServer::Comm
# code, with following added features:
#
# - Utilize ciphers with built-in BCB supports (Twofish2, Rijndael).
# - Out-of-band communication via callbacks.
# - Message queues allowing several packets be transferred at once.
#
# Because this makes the new server's behaviour incompatible from
# existing PlRPC's, I choose to fork a specific version just for
# OurNet::BBS's purpose. I'll notify the author once this modification
# proves to be stable and useful enough.
#
# According to the Artistic License, the copyright information of
# RPC::PlServer::Comm is acknowledged here:
#
# PlRPC - Perl RPC, package for writing simple, RPC like clients and
# servers
#
# Copyright (c) 1997,1998 Jochen Wiedmann
#
# You may distribute under the terms of either the GNU General Public
# License or the Artistic License, as specified in the Perl README file.
#
# Author: Jochen Wiedmann
# Am Eisteich 9
# 72555 Metzingen
# Germany
#
# Email: joe@ispsoft.de
# Phone: +49 7123 14887
#
# The source code PlRPC is very possibly on your computer right now,
# since OurNet::BBS::Authen depend on that library to run. Nevertheless,
# you may obtain the PlRPC source via the Bundle::PlRPC package from
# CPAN at http://www.cpan.org/.
#
####################################################################### }}}
package RPC::PlServer::Comm;
use strict;
no warnings 'deprecated';
no warnings 'redefine';
my ($WholeCipher, $Blocksize);
our (%Callback, @CallQueue);
use constant OUT_OF_BAND => 2 ** 31; # out-of-band size indicator
sub Read($) {{
my $self = $_[0];
my $socket = $self->{'socket'};
my $result;
my($encodedSize, $readSize, $blockSize);
my $out_of_band = 0;
$readSize = 4;
$encodedSize = '';
while ($readSize > 0) {
my $result = $socket->read($encodedSize, $readSize,
length($encodedSize));
if (!$result) {
return undef if defined($result);
die "Error while reading socket: $!" if $!;
}
$readSize -= $result;
}
$encodedSize = unpack("N", $encodedSize);
# handles OOB (out of band) data
if ($encodedSize & OUT_OF_BAND) {
$encodedSize ^= OUT_OF_BAND;
$out_of_band = 1;
}
$readSize = $encodedSize;
if ($self->{'cipher'}) {
$blockSize = $Blocksize ||= $self->{'cipher'}->blocksize;
if (my $addSize = ($encodedSize % $blockSize)) {
$readSize += ($blockSize - $addSize);
}
}
my $msg = '';
my $rs = $readSize;
while ($rs > 0) {
my $result = $socket->read($msg, $rs, length($msg));
if (!$result) {
die "Unexpected EOF" if defined $result;
die "Error while reading socket: $!";
}
$rs -= $result;
}
if (my $cipher = $self->{'cipher'}) {
if ($WholeCipher) {
$msg = $cipher->decrypt($msg);
}
elsif (index('Crypt::Rijndael Crypt::Twofish2 ', ref($cipher).' ')>-1) {
$WholeCipher = 1;
$msg = $cipher->decrypt($msg);
}
else {
my $encodedMsg = $msg;
$msg = '';
for (my $i = 0; $i < $readSize; $i += $blockSize) {
$msg .= $cipher->decrypt(substr($encodedMsg, $i, $blockSize));
}
}
$msg = substr($msg, 0, $encodedSize) if $readSize != $encodedSize;
}
return Storable::thaw($msg) unless $out_of_band;
# OOB calback code
my $payload = Storable::thaw($msg);
my $coderef = shift(@$payload);
$coderef = $Callback{$coderef} or redo;
print "out-of-band data received: $coderef->(@$payload)\n"
if $OurNet::BBS::DEBUG;
$coderef->(map {
(ref($_) eq '__SPAWN__')
? OurNet::BBS::Client::_spawn(@{$_}[2, 3])
: $_
} @$payload) if UNIVERSAL::isa($coderef, 'CODE');
redo; # resume to the next chunk
}}
use constant IsBroken => ($^V le v5.6.1);
sub Write ($$) {{
my $self = $_[0];
my $out_of_band = scalar @CallQueue;
my $msg = $out_of_band ? shift(@CallQueue) : $_[1];
my $socket = $self->{'socket'};
# works around broken GC code prior to v5.7.0.
exit if IsBroken and (caller(1) eq 'RPC::PlClient::Object');
my $encodedMsg = Storable::nfreeze($msg);
my($encodedSize) = length($encodedMsg);
if (my $cipher = $self->{'cipher'}) {
my $size = $Blocksize ||= $cipher->blocksize;
if (my $addSize = length($encodedMsg) % $size) {
$encodedMsg .= chr(0) x ($size - $addSize);
}
if ($WholeCipher) {
$encodedMsg = $cipher->encrypt($encodedMsg);
}
elsif (index('Crypt::Rijndael Crypt::Twofish2 ', ref($cipher).' ')>-1) {
$WholeCipher = 1;
$encodedMsg = $cipher->encrypt($encodedMsg);
}
else {
$msg = '';
for (my $i = 0; $i < length($encodedMsg); $i += $size) {
$msg .= $cipher->encrypt(substr($encodedMsg, $i, $size));
}
$encodedMsg = $msg;
}
}
if ($out_of_band) {
print "Writting out-of-band data: $encodedSize bytes"
if $OurNet::BBS::DEBUG;
$encodedSize += OUT_OF_BAND;
}
if ($socket and !$socket->print(pack("N", $encodedSize), $encodedMsg) ||
!$socket->flush()) {
die "Error while writing socket: $!" if $!;
}
redo if $out_of_band;
}}
1;