# vim: set cindent expandtab ts=4 sw=4:
#
# Copyright (c) 1998-2005 Chi-Keung Ho. All rights reserved.
#
# This programe 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 2
# of the License, or (at your option) any later version.
#
# Extmail - a high-performance webmail to maildir
# $Id$
package Ext::Session;

use strict;
no strict qw(refs);

use Ext;
use Exporter;
use Ext::Utils;
use vars qw(@KEY_MAP $RANDED);
use vars qw($_RTYPE $_RFUNC);
$_RTYPE = '';
$_RFUNC = '';

our @ISA = qw(Exporter);
our @EXPORT = qw(
    gen_sid @KEY_MAP read_sess
    write_sess parse_sess
    kill_sid valid_sess
);

@KEY_MAP = (
    0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E',
    'F','G','H','I','J','K','L','M','N','O',
    'P','Q','R','S','T','U','V','W','X','Y',
    'Z','a','b','c','d','e','f','g','h','i',
    'j','k','l','m','n','o','p','q','r','s',
    't','u','v','w','x','y','z'
);

# package init

sub sess_dir {
    my $dir = $Ext::Cfg{SYS_SESS_DIR};
    if (! defined $dir) {
        $dir = "/tmp/";
    }
    $dir;
}

# gen_rand_func - to generate proper random function, and do
# a little trick to cache the function name and type.
sub gen_rand_func {
    return if ($_RFUNC && $_RTYPE);

    eval { require 'sys/syscall.ph' };
    if ($@ or !defined &SYS_gettimeofday) {$_RTYPE = 'time' }
    else { $_RTYPE = 'syscall' }

    eval { require Digest::MD5 };
    if($@) {
        $_RFUNC = 'rand_time';
    } else {
        Digest::MD5->import(qw(md5_hex));
        if (-r '/dev/urandom') {
            $_RFUNC = 'rand_dev';
        } elsif ($_RTYPE eq 'syscall') {
            $_RFUNC = 'rand_md5';
        } else {
            $_RFUNC = 'rand_time';
        }
    }
}

# gen_sid - to generate unique Session id
# XXX FIXME - $RANDED is not safe under persistent envirement
#             , so this is an experimental tricks

sub gen_sid {
    # put require 'sys/syscall.ph' in function to advoid
    # Maildir.pm complain gettimeofday() undefined, but why?:(
    gen_rand_func();
    &$_RFUNC(@_)
}

sub rand_time {
    my ($sid, $len) = (undef, $_[0] ? $_[0]-1 : 23);

    if (!$RANDED) {
        srand(time() ^ $$);
        $RANDED = 1;
    }

    foreach(0...$len) {
        $sid .= $KEY_MAP[int rand(61)]; # total of $#KEY_MAP -1
    }
    $sid;
}

sub rand_md5 {
    my $start = pack('LL', ());
    syscall(&SYS_gettimeofday, $start, 0) != -1
        or die "gettimeofday: $!";
    my $str = join('/',unpack('LL', $start));
    md5_hex($str);
}

sub rand_dev {
    my $seed = '';
    my $len = 32;
    open (FD, '/dev/urandom') or die "open urandom error: $!\n";
    for(0...$len-1) {
        next unless sysread FD, my $buf, 1;
        $seed .= sprintf("%x", ord $buf);
    }
    md5_hex($seed);
}

sub kill_sid {
    my ($sid) = $_[0];
    my $dir = sess_dir();
    return 0 if not defined $sid or $sid eq "";
    if(-e "$dir/sid_$sid") {
        unlink untaint("$dir/sid_$sid")
            or die "Can't unlink $dir/sid_$sid, $!\n";
        return 1;
    }
    0;
}

sub write_sess {
    my ($sid, $info) = @_;
    my $dir = sess_dir();
    my $sfile = untaint ("$dir/sid_$sid");

    if(!-e $sfile) {
        open(my $FD, "> $sfile") or die "Can't open $sfile, $!\n";
        print $FD $info;
        close $FD;
        return 1;
    }else {
        return 0;
    }
    0;
}

sub read_sess {
    my ($sid) = $_[0];
    my $str = "";
    my $dir = sess_dir();
    my $sfile = "$dir/sid_$sid";

    if(-e $sfile) {
        open(my $FD, "< $sfile") or die "Can't open $sfile, $!\n";
        while(<$FD>) {
            $str .= $_;
        }
        close $FD;
    }
    "$str";
}

sub parse_sess {
    my ($sid) = $_[0];
    my $str = read_sess($sid);
    my %shash;

    if(length($str)<2) {# no content or in-compat
        return {};
    }else {
        foreach(split(/\n/, $str)) {
            /^([a-zA-Z0-9-_]+)\s*=\s*([a-zA-Z0-9-_\.\/@]+)/;
            my ($k, $v) = ($1, $2);
            if(defined $k) {
                $shash{$k}=$v;
            }
        }
    }
    return \%shash;
}

sub valid_sess {
    my ($sid) = $_[0];
    my $dir = sess_dir();
    if(!-e "$dir/sid_$sid") {
        return 0;
    }

    if(!-r "$dir/sid_$sid") {
        return 0;
    }
    1;
}

1;


syntax highlighted by Code2HTML, v. 0.9.1