next up previous contents
Next: Bibliography Up: ProtoWrap program code Previous: ProtoWrap/SMTP.pm   Contents

ProtoWrap/POP3.pm

1


package ProtoWrap::POP3;
#
# ProtoWrap::POP3 - POP3 extension to ProtoWrap
#
use ProtoWrap;
@ISA = qw(ProtoWrap);
use strict;
use vars qw($crlf);

$crlf = chr(13).chr(10);

sub new {
    my ($class,$self,@pop3Specific,@pop3NotProtoWrap,%tmpHash);
    $class = shift;
    $self = { @_ };
    bless $self, $class;
    # Parameters accepted for POP3 that are not part of the base 
    # ProtoWrap should be stored in a temporal hash until the object is 
    # created if we do not want ProtoWrap to complain, and then added
    # to the existing object.
    # The affected attributes are:
    # maxLoginAttempts: The maximum login attempts permitted before
    # disconnection
    @pop3NotProtoWrap = ('maxLoginAttempts');

    foreach (@pop3NotProtoWrap) {
        if (defined $self->{$_}) {
            $tmpHash{$_} = $self->{$_};
            delete $self->{$_};
        }
    }

    # Check for wrong parameters
    @pop3Specific = ('testLine','testReply');


    foreach (@pop3Specific) {
        if (defined $self->{$_}){
            $self->log(0,"Parameter $_ incompatible with POP3");
            return undef 
        }
    }
    # Set the POP3 defined parameters needed to create a ProtoWrap
    $self->{destType} = 'ip' if (!defined $self->{destType});
    if ($self->{destType} eq 'ip') {
        $self->{destPort} = 110 unless (defined $self->{destPort});
        $self->{destAddr} = '127.0.0.1' unless (defined $self->{destAddr});
    } elsif ($self->{destType} eq 'pipe') {
        $self->{pipeCmd} = '/usr/sbin/ipop3d' unless (defined $self->{pipeCmd});
    }
    $self->{standalone} = 1 unless defined $self->{standalone};
    $self->{testLine} = \&defaultTestLine;
    $self->{logName} = 'ProtoWrap-POP3';
    $self->{testReply} = 1;
    # Create the ProtoWrap. If it is undef, an error occured and we
    # should not start.
    $self = ProtoWrap->new(%$self);
    return undef if (not defined $self);
    # Add POP3-specific attributes not allowed for base PSNs, 
    # and free %tmpHash's memory
    foreach (keys(%tmpHash)) {
        $self->{$_} = $tmpHash{$_};
        delete $tmpHash{$_};
    }
    # Set the needed parameters for a POP3 ProtoWrap that could not be
    # specified before creating a ProtoWrap
    $self->{maxLoginAttempts} = 0 if (not defined $self->{maxLoginAttempts});
    $self->{stage}=-1;
    # Make $self a POP3 object
    bless $self, $class;
}

sub defaultTestLine {
    my ($self,$line,$socket,$who) = @_;
    if ($who == 0) {
        return &fromClient($self,$line,$socket);
    } elsif ($who == 1) {
        return &fromServer($self,$line,$socket);
    } else {

    }

}

sub fromServer {
    # stage indicates the current stage of the connection, thus
    # indicating what will be accepted or rejected in the ongoing
    # communication. The relation between stage and the stages
    # defined in the RFC is:
    #
    # AUTHORIZATION:
    # 0 - USER has not yet been specified.
    # 1 - USER specified, waiting for server to answer.
    # 2 - USER has been specified, waiting for PASS.
    # 3 - USER and PASS specified, waiting for server's authorization
    # TRANSACTION:
    # 5 - Transaction state, all transaction commands are valid.
    #
    my($self,$line,$socket,$who,$retValue);
    ($self,$line,$socket,$who) = @_;

    $retValue = 1;

    # We strip CR and LF to do the tests, add them back at the
    # end.
    $$line =~ s/[\r\n]//g;

    if ($self->{stage} == -1) {
        # Stage -1: Waiting for the server to say hello.
        # Instead of letting the real server expose its identity,
        # we give ours. We only check if the first character sent
        # by the server is '+', indicating success.
        $$line = '+OK POP3 localhost.localdomain ProtoWrap '.$self->version() if 
            (substr($$line,0,1) eq '+');
        # We set the stage to 0 to allow for interaction to start
        $self->{stage} = 0;
        # As the connection is just starting, we set to zero all the
        # counters
        $self->{loginAttempts} = 0;
        $self->{maxMsgNumber} = 0;
        $self->{deletedMessages} = undef;
    } elsif ($self->{stage} == 1) {
        # We do not care about the server's answer - USER should
        # always get a positive answer.
        $$line = '+OK User name accepted, password please';
        $self->{stage} = 2;
    } elsif ($self->{stage} == 3) {
        if (substr($$line,0,1) eq '+') {
            $self->{stage} = 5;
        } else {
            $$line = '-ERR Bad login';
            $self->{stage} = 0;
            $self->{loginAttempts}++;
            if ($self->{maxLoginAttempts} && $self->{loginAttempts} > 
                $self->{maxLoginAttempts}) {
                # Too many login attempts - Exit, terminating connection
                # automatically.
                $self->log(2,'Too many login attempts - terminating connection.');
                exit 0;
            }
        }
    } else {
        # Checking how many messages are there waiting to be
        # retrieved. This is done right after the client sends a
        # STAT command.
        if (defined($self->{statJustAsked})) {
            # We should not go back here for the next line
            $self->{statJustAsked} = undef;
            # Get the relevant part of the answer into maxMsgNumber
            $self->{maxMsgNumber} = $$line;
            $self->{maxMsgNumber} =~ s/^.+(\d+).+$/$1/;
        }
    }
    
    $$line .= chr(13).chr(10);
    return $retValue;
}

sub fromClient {
    # stage indicates the current stage of the connection, thus
    # indicating what will be accepted or rejected in the ongoing
    # communication. The relation between stage and the stages
    # defined in the RFC is:
    #
    # AUTHORIZATION:
    # 0 - USER has not yet been specified.
    # 1 - USER specified, waiting for server to answer.
    # 2 - USER has been specified, waiting for PASS.
    # 3 - USER and PASS specified, waiting for server's authorization
    # TRANSACTION:
    # 5 - Transaction state, all transaction commands are valid.
    #
    my($self,$line,$socket,$who,$retValue);
    ($self,$line,$socket,$who) = @_;

    $retValue = 1;
    # We strip CR and LF to do the tests, add them back at
    # the end.
    $$line =~ s/[\r\n]//g;

    #
    # Our first test should be for maxLineLength
    #
    if (length($$line) > $self->{maxLineLength} && $self->{maxLineLength} > 0) {
        $$line = substr($$line,0,$self->{maxLineLength});
        $self->log(1,"Line too long\nChopping to:\n$$line");
    }

    # Commands that will always be handled the same way, no matter
    # where are we at
    if (uc(substr($$line,0,4)) eq 'QUIT') {
        # If a QUIT is recieved, return only QUIT, chopping the
        # rest of the line.
        $$line = 'QUIT';
        $self->{stage} = 10;
    } elsif (uc(substr($$line,0,4)) eq 'NOOP') {
        $socket->print("+OK This is your very special No-op!$crlf");
        $$line = '';
        $retValue = -1;
    } elsif (uc(substr($$line,0,4)) eq 'RPOP') {
        $socket->print("-ERR RPOP authorization not allowed$crlf");
        $$line = '';
        $retValue = 0;
    } elsif ($self->{stage} < 0) {
    # Stage -1 indicates that the server has not yet talked on
    # the connection, and all traffic before this should be
    # silently dropped.
        $$line = '';
        return -1;
    } elsif ($self->{stage} == 0) {
        # Stage 0 should only allow, besides non-stage-specific
        # commands, USER.
        if (uc(substr($$line,0,4)) eq 'USER') {
            # check for format
            if ($$line =~ s/^USER\s+([\w\d\.\-\_]*)$/USER $1/i) {
                $self->{stage} = 1;
            } else {
                $$line = '';
                $socket->print("-ERR Invalid username $crlf");
                $retValue = -1;
            }
        } else {
            &invalid($line,\$retValue,$socket);
        }
    } elsif ($self->{stage} == 2) {
        if (uc(substr($$line,0,4)) eq 'PASS') {
            # check for format
            if ($$line =~ s/^PASS\s+([\S]*)$/PASS $1/i) {
                $self->{stage} = 3;
            } else {
                $$line = '';
                $socket->print("-ERR Invalid password$crlf");
                $retValue = -1;
                $self->{loginAttempts}++;
                if ($self->{maxLoginAttempts} && $self->{loginAttempts} > 
                    $self->{maxLoginAttempts}) {
                    # Too many login attempts - Exit, terminating connection
                    # automatically.
                    $self->log(2,'Too many login attempts - terminating connection.');
                    exit 0;
                } else {
                    &invalid($line,\$retValue,$socket);
                }
            }
        }
    } elsif ($self->{stage} == 5) {
        if (uc(substr($$line,0,4)) eq 'RSET') {
            # Send only RSET, ignoring the rest of the line
            $$line = 'RSET';
            # Reset the deleted message list
            $self->{deletedMessages} = undef;
        } elsif (uc(substr($$line,0,4)) eq 'LAST') {
            # Send only  LAST, ignoring the rest of the line
            $$line = 'LAST';
        } elsif (uc(substr($$line,0,4)) eq 'STAT') {
            # Send only  STAT, ignoring the rest of the line
            $$line = 'STAT';
            $self->{statJustAsked} = 1;
        } elsif (uc(substr($$line,0,4)) eq 'LIST') {
            # Send only  LIST, ignoring the rest of the line
            $$line = 'LIST';
        } elsif (uc(substr($$line,0,4)) eq 'RETR' || uc(substr($$line,0,3)) eq 'TOP') {
            # Store in $msgNum just the message number requested
            my $msgNum = $$line;
            my $lines = $$line;
            if (uc(substr($$line,0,4)) eq 'RETR') {
                $msgNum =~ s/^RETR\s*(\d+).*$/$1/i;
            } else {
                $msgNum =~ s/^TOP\s*(\d+).*$/$1/i;
                $lines =~ s/^TOP\s*\d+\s*(\d+).*$/$1/i;
            }
            # Check if it has already been deleted
            if (defined($self->{deletedMessages}->{$msgNum})) {
                $$line = '';
                $retValue = -1;
                $socket->print("-ERR Marked as deleted.$crlf");
            } elsif ($msgNum > $self->{maxMsgNumber} || $self->{maxMsgNumber} == 0) {
                # Message number greater than the total number of messages
                $$line = '';
                $retValue = -1;
                $socket->print("-ERR Invalid message number.$crlf");
            } else {
                # Ok, go ahead...
                if (uc(substr($$line,0,4)) eq 'RETR') {
                    $$line = "RETR $msgNum";
                } else {
                    $$line = "TOP $msgNum $lines";
                }
            }
        } elsif (uc(substr($$line,0,4)) eq 'DELE') {
            # Store in $msgNum just the message number requested
            my $msgNum = $$line;
            $msgNum =~ s/^DELE\s+(\d+).+$/$1/i;
            # Check if it has already been deleted
            if (defined($self->{deletedMessages}->{$msgNum})) {
                $$line = '';
                $retValue = -1;
                $socket->print('-ERR Message already deleted.$crlf');
            } else {
                $$line = "DELE $msgNum";
                $self->{deletedMessages}->{$msgNum} = 1;
            }
        } else {
            # Disallow everything else
            &invalid($line,\$retValue,$socket);
        }
    } else {
        # Guess the line was recieved when it is the server's turn to
        # talk. Respond with an error and ignore it.
        &invalid($line,\$retValue,$socket);
    }
    
    $$line .= chr(13).chr(10);
    return $retValue;
}

sub invalid {
    my ($line,$retValue,$socket) = @_;
    $socket->print("-ERR Invalid command$crlf");
    $$line = '';
    $$retValue = 0;
}

1;



Gunnar Wolf
2001-03-12