# $File: //depot/OurNet-BBS/BBS/MAPLE3/User.pm $ $Author: autrijus $
# $Revision: #16 $ $Change: 1607 $ $DateTime: 2001/08/30 03:01:38 $

package OurNet::BBS::MAPLE3::User;

use strict;
use fields qw/bbsroot id recno _ego _hash/;
use subs qw/writeok readok/;
use OurNet::BBS::Base (
    '$packstring' => 'iZ13Z14CZ20Z24IiiILLLLZ32iLZ60Z60Z60Z60Z120L',
    '$packsize'   => 512,
    '@packlist'   => [ qw(
	userno userid passwd signature realname username userlevel 
	numlogins numposts ufo firstlogin lastlogin staytime tcheck 
	lasthost numemail tvalid email address justify vmail ident 
	vtime
    ) ],
);

use constant IsWin32 => ($^O eq 'MSWin32');
use open (IsWin32 ? (IN => ':raw', OUT => ':raw') : ());

use enum 'BITMASK:PERM_',
    qw/BASIC CHAT PAGE POST VALID MBOX CLOAK XEMPT/,		# Basic
    qw/P9 P10 P11 P12 P13 P14 P15 P16/,				# Special
    qw/DENYPOST DENYCHAT DENYTALK DENYMAIL DENY5 DENY6 DENYLOGIN PURGE/,
    qw/BM SEECLOAK ADMIN3 ADMIN4 ACCOUNTS CHATROOM BOARD/;	# Admin

use constant PERM_SYSOP	     => 0x80000000; # enum bug: will overflow
use constant PERM_DEFAULT    => (PERM_BASIC|PERM_CHAT|PERM_PAGE|PERM_POST);
use constant PERM_ADMIN      => (PERM_BOARD | PERM_ACCOUNTS | PERM_SYSOP);
use constant PERM_ALLBOARD   => (PERM_SYSOP);
use constant PERM_LOGINCLOAK => (PERM_SYSOP | PERM_ACCOUNTS);
use constant PERM_SEEULEVELS => PERM_SYSOP;
use constant PERM_SEEBLEVELS => (PERM_SYSOP | PERM_BM);
use constant PERM_READMAIL   => PERM_BASIC;
use constant PERM_INTERNET   => PERM_VALID;
use constant PERM_FORWARD    => PERM_INTERNET;
use constant GEM_QUIT        => -2;
use constant GEM_VISIT       => -1;
use constant GEM_USER        => 0;
use constant GEM_RECYCLE     => 1;
use constant GEM_MANAGER     => 2;
use constant GEM_SYSOP       => 3;

use constant WRITEOK	=> ' username address realname email ';
use constant READOK	=> ' numlogin numposts justify lastlogin vmail'.
			   ' username userid mailbox ';

sub writeok {
    my ($self, $user, $op, $param) = @_;

    return (
	index(WRITEOK, $param->[0]) > -1
	and ($self->id eq $user->id)
    );
}

sub readok {
    my ($self, $user, $op, $param) = @_;

    return (
	index(READOK, $param->[0]) > -1
	or ($self->id eq $user->id)
    );
}

sub has_perm {
    no strict 'refs';
    return $_[0]->{userlevel} & &{$_[1]};
}

sub refresh_meta {
    my ($self, $key) = @_;
    return if defined $key and exists $self->{_hash}{$key};

    my $path = "$self->{bbsroot}/usr/".
              lc(substr($self->{id}, 0, 1)."/$self->{id}");

    local $/;

    unless (-d $path) {
        mkdir $path or die "cannot mkdir $path\n";

        open(my $USR, '>', "$path/.ACCT") or die "cannot open: $path/.ACCT";

        $self->{_hash}{userno} = (stat("$self->{bbsroot}/.USR"))[7] / 16;
        $self->{_hash}{userid} = $self->{id};
        $self->{_hash}{userlevel} = 15;
        $self->{_hash}{ufo} = 15;
        print $USR pack($packstring, @{$self->{_hash}}{@packlist});
        close $USR;

        open($USR, '>>', "$self->{bbsroot}/.USR")
	    or die "cannot open: $path/.USR";
        print $USR pack("LZ12", time(), $self->{id});
        close $USR;
    }

    if (!defined($key) or $self->contains($key)) {
	open my $USR, "$path/.ACCT" or die "cannot: open $path/.ACCT";
	@{$self->{_hash}}{@packlist} = unpack($packstring, <$USR>);
	close $USR;

	no warnings 'numeric';

	$self->{recno} ||= $self->{_hash}{userno} + 1;
	$self->{_hash}{uid} ||= $self->{recno} - 1;
	$self->{_hash}{name} ||= $self->{id};

	return 1;
    }
    else {
	die "malicious intent stopped cold" if index($key, '..') > -1;

	require OurNet::BBS::ScalarFile;
	tie $self->{_hash}{$key}, 'OurNet::BBS::ScalarFile',
	    "$path/$key";
    }

    return 1;
}

sub refresh_mailbox {
    my $self = shift;
    my $PATH_USR = 'usr'; # XXX should be in inivars

    return $self->{_hash}{mailbox} ||= $self->module('ArticleGroup')->new({
	basepath	=> "$self->{bbsroot}/$PATH_USR/".
			   lc(substr($self->{id}, 0, 1)),
	board		=> lc($self->{id}),
	idxfile	 	=> '.DIR',
	bm		=> $self->{id},
	readlevel	=> -1,
	postlevel	=> -1,
    });
}

sub STORE {
    my ($self, $key, $value) = @_;
    $self = $self->ego;
    $self->refresh_meta($key);

    my $path = "$self->{bbsroot}/usr/".
		lc(substr($self->{id}, 0, 1)."/$self->{id}");

    if ($self->contains($key)) {
	$self->{_hash}{$key} = $value;

	open my $USR, '>', "$path/.ACCT";
	print $USR pack($packstring, @{$self->{_hash}}{@packlist});
	close $USR;
    }
    else {
	die "malicious intent stopped cold" if index($key, '..') > -1;

	require OurNet::BBS::ScalarFile;
	tie $self->{_hash}{$key}, 'OurNet::BBS::ScalarFile',
	    "$path/$key" unless $self->{_hash}{$key};

	$self->{_hash}{$key} = $value;
    }
}

1;

