# Mail::SMI Perl Module
# 
# If you are need some help, read documentation first.
# $ perldoc Mail::SMI
# 
# $Id$
#
package Mail::SMI;

use Socket qw(:DEFAULT :crlf);
use POSIX qw(strftime);
use strict;
use vars qw($sock $VERSION $DEBUG);

eval("use MIME::QuotedPrint");

$DEBUG   = 0;
$VERSION = '0.01';

sub _gethostname
{
  my $this= @_;
  my ($port, $iaddr, $sockaddr, $hostname, $hostaddr);

  $sockaddr = getpeername($sock);
  ($port, $iaddr) = unpack_sockaddr_in($sockaddr);
  $hostname = gethostbyaddr($iaddr, AF_INET);
  return $hostname;
}

sub _getrcpt
{
  my $this = shift;
  return @{$this->{SMTP_RCPT}};
}

sub connect
{
  my($this,$host,$port,$hostname)=@_;
  my ($iaddr, $paddr, $proto);

  $this->{HOST} = $host;
  $this->{PORT} = $port || 25;

  $iaddr = inet_aton($this->{HOST});
  $paddr = sockaddr_in($port, $iaddr);
  $proto = getprotobyname('tcp');

  unless(socket SOCKFH, AF_INET, SOCK_STREAM, $proto)
  {
    $this->{ERR} = "connect method failed: $!\n";
    return 0;
  }
  unless(connect SOCKFH, $paddr)
  {
    $this->{ERR} = "connect method failed: $!\n";
    return 0;
  }

  $sock = \*SOCKFH;

  select($sock); $|=1; select(STDOUT);

  my $resp = <$sock>;
  if($resp !~ /^220/) 
  { 
    $this->{ERR} = "connect method failed: ($resp)"; 
    return 0; 
  }

  $hostname = (gethostbyname('localhost'))[0] unless $hostname;

  print $sock "HELO $hostname\015\012";
  $resp = <$sock>;
  print $resp if $DEBUG;
  if($resp !~ /^250/) 
  { 
    $this->{ERR} = "connect method failed: ($resp)\n"; 
    return 0; 
  }

  return 1;
}

sub disconnect
{
  my $resp;

  print $sock "QUIT\015\012";
  $resp = <$sock>;
  close($sock);

  return 1;
}

sub new 
{
  my $this = 
  {
    ERR => undef,
    HOST => undef,
    PORT => 0,
    SMTP_FROM => undef, 
    SMTP_RCPT => [],
    SMTP_DATA => undef,
  };

  bless $this;
  return $this;
}

sub reset
{
  my $this = shift;

  undef $this->{ERR};
  undef $this->{SMTP_FROM};
  undef $this->{SMTP_RCPT};
  undef $this->{SMTP_DATA};
}

sub rfc822_date
{
  return strftime "%a, %d %b %Y %H:%M:%S %z", localtime(time());
}

sub sendmail
{
  my ($this, $host, $port) = shift;
  my ($resp, $c);

  if($host && $port) {
    return 0 unless $this->connect($host, $port);
  }

  if(!$this->{SMTP_FROM})
  {
    $this->{ERR} = "sendmail method failed: missing SMTP_FROM\n";
    return 0;
  }

  print $sock "MAIL FROM: <$this->{SMTP_FROM}>\015\012";
  $resp = <$sock>;
  if($resp !~ /^250/) { $this->{ERR} = "sendmail method failed (MAIL FROM)"; return 0; }

  foreach my $rcpt ($this->_getrcpt())
  {
    print $sock "RCPT TO: <$rcpt>\015\012";
    $resp = <$sock>;
    unless($resp =~ /^250/) { $this->{ERR} = "sendmail method failed: ($resp)"; }
    $c++;
  }

  if(!$c)
  {
    $this->{ERR} = "sendmail method failed: missing SMTP_RCPT\n";
    return 0;
  }

  print $sock "DATA\015\012";
  $resp = <$sock>;
  print "smtp: $resp" if $DEBUG;
  unless($resp =~ /^354/) 
  { 
    chomp $resp;
    $this->{ERR} = "sendmail method failed: ($resp)"; 
    return 0; 
  }

  print $sock $this->{SMTP_DATA};
  $resp = <$sock>;
  print "smtp: $resp" if $DEBUG;
  unless($resp =~ /^250/) 
  {
    chomp $resp;
    $this->{ERR} = "sendmail method failed: ($resp)"; 
    return 0; 
  }

  if($host && $port)
  {
    $this->disconnect();
  }

  if($this->{ERR}) { return -1; }
  else { return 1; }
}

sub setdata
{
  my ($this, $MSG) = @_;
  my ($c, $body, $hdrs, $type);

  foreach my $key(keys %{$MSG})
  {
    if($key =~ /mime\-version/i) { $hdrs .="m|"; }
    elsif($key =~ /content\-type/i) { $hdrs .= "ct|"; $type = $MSG->{$key}; }
    elsif($key =~ /content\-transfer\-encoding/i) { $hdrs .= "ce|"; }
    if($key =~ /body/i) { $body = $MSG->{$key}; }
    else { $this->{SMTP_DATA} .= "$key: $MSG->{$key}\015\012"; }
  }
  if($hdrs !~ /m\|/) 
  {
    $this->{SMTP_DATA} .= "Mime-version: 1.0\015\012";
  }
  if($hdrs !~ /ct\|/) { 
    $this->{SMTP_DATA} .= "Content-type: text/plain; charset=\"iso-8859-1\"\015\012";
  }
  if($hdrs !~ /ce\|/) {
    $this->{SMTP_DATA} .= "Content-transfer-encoding: 8bit\015\012";
  }

  if($type =~ /quoted\-printable/i) { encode_qp $body; }
  else { $body =~ s/\012/\015\012/g; }
  $this->{SMTP_DATA} .= "\015\012";
  $this->{SMTP_DATA} .= $body;
  $this->{SMTP_DATA} .= "\015\012.\015\012";

  unless($c) 
  { 
    $this->{ERR} = "setdata method failed: DATA key missing\n";
    return 0; 
  }
  return 1;
}

sub setfrom
{
  my ($this, $from) = @_;
  $this->{SMTP_FROM} = $from;
  return $this->{SMTP_FROM};
}

sub setrcpt
{
  my ($this, $rcpt) = @_;
  return push(@{$this->{SMTP_RCPT}}, $rcpt);
}

sub strerror
{
  my $this = shift;
  return $this->{ERR};
}

1;
__END__

=head1 NAME

Mail::SMI - SMTP and Mail Interface Module

=cut

=head1 SYNOPSIS

  use Mail::SMI;

  $mobj = new Mail::SMI;
  $mobj->connect(HOST, 25) || die $mobj->strerror();
  $mobj->setfrom(MAILFROM) || die $mobj->strerror();
  $mobj->setrcpt(RCPTTO) || die $mobj->strerror();
  $mobj->setrcpt(RCPTTO) || die $mobj->strerror();
  $MSG = {
    'Date' => $mobj->rfc822_date(),
    'To' => 'TO',
    'From' => 'FROM',
    'Subject' => 'SENDMAIL Module',
    'X-AnotherHeader' => 'OK',
    .
    .
    .
    'Body' => BODY
  };
  $mobj->setdata($MSG);
  $mobj->sendmail() || die $mobj->strerror();
  $mobj->disconnect();

=head1 DESCRIPTION

This module was created to substitute the old modules that establish a
new local connection every time that you want send a new mail.
With this you can make one connection and send all mail throught this connection
without disconnecting in this transaction

=head1 METHODS

=item $mobj = new Mail::SMI;

=item $mobj->connect(HOST, PORT, HOSTNAME);

=item $mobj->disconnect();

=item $mobj->reset();

=item $mobj->rfc822_date();

=item $mobj->sendmail([HOST, PORT]);

=item $mobj->setdata(HASHREF);

=item $mobj->setfrom(FROM);

=item $mobj->setrcpt(RCPT);

=item $mobj->strerror();

=head1 AUTHOR

Daniel Froz Costa

Please feel free to modify this module,
send diff to dfroz@users.sourceforge.net.
Any suggestions and comments are welcome ;^)

=head1 LICENSE

Copyright (C) 2001 Daniel Froz Costa

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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA

=head1 SEE ALSO

perl(1).

=cut
