#!/usr/bin/perl

use strict;
use MIME::Base64;

my($p_name)   = $0 =~ m|/?([^/]+)$|;
my $p_version = "20031027.2";
my $p_usage   = "Usage: $p_name [--help|--version] | <type> ...";
my $p_cp      = <<EOM;
Copyright (c) 2003
      John Jetmore <jj33\@pobox.com>.  All rights reserved.
This code freely redistributable provided my name and this copyright notice
are not removed.  Send email to the contact address if you use this program.
EOM
ext_usage();

my $type = get_input(\@ARGV, "encryption type: ");

if ($type =~ /^plain$/i) {
  my $user = get_input(\@ARGV, "username: ");
  my $pass = get_input(\@ARGV, "password: ", 1);
  print "Auth String: ", encode_base64("\0$user\0$pass", ''), "\n";

} elsif ($type =~ /^decode$/i) {
  my $user = get_input(\@ARGV, "string: ");
  print decode_base64($user), "\n";

} elsif ($type =~ /^encode$/i) {
  my $user = get_input(\@ARGV, "string: ");
  print encode_base64($user, ""), "\n";

} elsif ($type =~ /^login$/i) {
  my $user = get_input(\@ARGV, "username: ");
  my $pass = get_input(\@ARGV, "password: ", 1);
  print "Username: ", encode_base64($user, ""), "\n",
        "Password: ", encode_base64($pass, ""), "\n";

} elsif ($type =~ /^cram(-md5)?$/i) {
  try_load("Digest::MD5") || die "Digest::MD5 required for CRAM-MD5\n";
  my $user = get_input(\@ARGV, "username: ");
  my $pass = get_input(\@ARGV, "password: ", 1);
  my $chal = get_input(\@ARGV, "challenge: ");
  if ($chal !~ /^</) {
    chomp($chal = decode_base64($chal));
  }
  my $digest = get_digest($pass, $chal);
  print encode_base64("$user $digest", ""), "\n";

} elsif ($type =~ /^(ntlm|spa|msn)$/i) {
  try_load("Authen::NTLM") || die "Authen::NTLM required for $type\n";
  my $user = get_input(\@ARGV, "username: ");
  my $pass = get_input(\@ARGV, "password: ", 1);
  my $domn = get_input(\@ARGV, "domain: ");
  Authen::NTLM::ntlm_user($user);
  Authen::NTLM::ntlm_password($pass);
  Authen::NTLM::ntlm_domain($domn);
  print "Auth Request: ", Authen::NTLM::ntlm(), "\n";
  my $chal = get_input(\@ARGV, "challenge: ");
  print "Auth Response: ", Authen::NTLM::ntlm($chal), "\n";

} else {
  print STDERR "I don't speak $type\n";
  exit 1;
}

exit 0;

sub get_input {
  my $a = shift; # command line array
  my $s = shift; # prompt string
  my $q = shift; # quiet
  my $r;         # response

  if (scalar(@$a) > 0) {
    $r = shift(@$a);
  } else {
    print $s;
    system('stty', '-echo') if ($q);
    $r = <>;
    system('stty', 'echo') if ($q);
    print "\n" if ($q);
    chomp($r);
  }

  $r = '' if ($r eq '<>');
  return($r);
}

sub get_digest {
  my $secr = shift;
  my $chal = shift;
  my $retr = '';
  my $ipad = chr(0x36);
  my $opad = chr(0x5c);
  my($isec, $osec) = undef;

  if (length($secr) > 64) {
    $secr = Digest::MD5::md5($secr);
  } else {
    $secr .= chr(0) x (64 - length($secr));
  }

  foreach my $char (split(//, $secr)) {
    $isec .= $char ^ $ipad;
    $osec .= $char ^ $opad;
  }

  map { $retr .= sprintf("%02x", ord($_)) }
            split(//,Digest::MD5::md5($osec . Digest::MD5::md5($isec . $chal)));
  return($retr);
}

sub try_load {
  my $mod = shift;

  eval("use $mod");
  return $@ ? 0 : 1;
}

sub ext_usage {
  if ($ARGV[0] =~ /^--help$/i) {
    require Config;
    $ENV{PATH} .= ":" unless $ENV{PATH} eq "";
    $ENV{PATH} = "$ENV{PATH}$Config::Config{'installscript'}";
    exec("perldoc", "-F", "-U", $0) || exit 1;
    # make parser happy
    %Config::Config = ();
  } elsif ($ARGV[0] =~ /^--version$/i) {
    print "$p_name version $p_version\n\n$p_cp\n";
  } else {
    return;
  }

  exit(0);
}

__END__

=head1 NAME

gen-auth - Brief description

=head1 USAGE

gen-auth [--help|--version] | <type> ...

=head1 DESCRIPTION

gen-auth can generate various authentication strings used for SMTP 
authentication.  The currently supported methods are PLAIN (RFC 2595),
LOGIN, and CRAM-MD5 (RFC-2195).

The program actions are broken down into types of encoding to generate.
each <type> then takes its own specific args.  The arguments are expected
in a specific order on the command line.  Every argument that isn't
available on the command line will be prompted for.  One benefit to this is
arguments corresponding to passwords will not be echoed to the terminal when
prompted for.

=head1 TYPES

The program action is controlled by the first argument.  The meaning of the
following arguments is specified by this type

=over 4

=item PLAIN

This type generates a PLAIN authentication string.  It accepts two 
supplemental arguments of username and password.  It generates a Base64
encoded string "\0<username>\0<password>".

=item LOGIN

This method accepts username and password as supplemental args.  It simply
returns each string Base64 encoded.  This provides only minimal advantages
over using ENCODE twice.  One advantage is hiding the password if you
provide it on STDIN

=item CRAM-MD5

CRAM-MD5 accepts three supplemental arguments.  The first is the username and
the second is the password.  The third is the challenge string provided
by the server.  This string can be either Base64 encoded or not.  RFC states
that all (unencoded) challenge strings must start w/ '<'.  This is used to
whether the string is Base64 encoded or not.

CRAM-MD5 uses the challenge and the supplied password to generate a digest.
it then returns the Base64 encoded version of the string "<username> <digest>"

This authentication method requires the Digest::MD5 perl module to be installed.

=item NTLM/SPA/MSN

Although it may be advertised as one of the above types, this method of authentication if refered to singularly as NTLM.  This is a multi-step authentication type.  The first 3 arguments must be supplied up front.  They are username, password, and domain, in that order.  These three strings are used to generate an "Auth Request" string.  This string should be passed verbatim to the server.  The server will then respond with a challenge.  This challenge is the fourth argument.  After receiving the server challenge, gen-auth will produce an "Auth Response".  Posting this response to the server completes the NTLM authentication transaction.

This authentication method requires the Authen::NTLM perl module to be installed.  See EXAMPLES for an example of this transaction.  Note also that 'domain' is often blank from client or ignored by server.

=item ENCODE

Simply Base64 encodes a plaintext string.  Provided as a convenience function.

=item DECODE

Decodes a Base64 encoded string.  Provided as a convenience function.

=back

=head1 OPTIONS

=item --help

this screen.

=item --version

version info.

=back

=head1 EXAMPLES

=over 4

=item generate a PLAIN AUTH string for user 'tim', password 'tanstaaftanstaaf'

  > gen-auth plain tim tanstaaftanstaaf
  Auth String: AHRpbQB0YW5zdGFhZnRhbnN0YWFm

=item generate a CRAM-MD5 string for user 'tim', password 'tanstaaftanstaaf', 
challenge '<1896.697170952@postoffice.reston.mci.net>', using prompt to 
hide password

  > gen-auth cram-md5                  
  username: tim
  password: 
  challenge: PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+
  dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw

=item use the DECODE method to ensure we provided the correct output in our last
example

  > gen-auth decode dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw
  tim b913a602c7eda7a495b4e6e7334d3890

=item use the NTLM (MSN) method to authenticate to a mail server using user 'tim', password 'tanstaaftanstaaf', and domain MAIL.  Both the gen-auth transaction and SMTP transaction are shown to demonstrate the interaction between the two.

  AUTH MSN
  334 NTLM supported
  TlRMTVNTUAABAAAAB7IAAAMAAwAgAAAABAAEACMAAAB0aW1NQUlM
  334 TlRMTVNTUAACAAAAAAAAAAAoAAABggAA9RH5KZlXvygAAACAAAAAZL//4sQAAAAC
  TlRMTVNTUAADAAAAGAAYAEAAAAAYABgAWAAAAAAAAAAwAAAABgAGAHAAAAAGAAYAdgAAAAAAAAA8AAAAAYIAAK3lcO8PldNxIrkbvgKGJRR5owQePUtYaTtLVgfQiVQBywW2yZKyp+VFGqYfgDtdEHQAaQBtAHQAaQBtAA==
  235 Authentication succeeded

  > gen-auth spa
  username: tim
  password: 
  domain: MAIL
  Auth Request: TlRMTVNTUAABAAAAB7IAAAMAAwAgAAAABAAEACMAAAB0aW1NQUlM
  challenge: TlRMTVNTUAACAAAAAAAAAAAoAAABggAA9RH5KZlXvygAAACAAAAAZL//4sQAAAAC
  Auth Response: TlRMTVNTUAADAAAAGAAYAEAAAAAYABgAWAAAAAAAAAAwAAAABgAGAHAAAAAGAAYAdgAAAAAAAAA8AAAAAYIAAK3lcO8PldNxIrkbvgKGJRR5owQePUtYaTtLVgfQiVQBywW2yZKyp+VFGqYfgDtdEHQAaQBtAHQAaQBtAA==

=back

=head1 REQUIRES

MIME::Base64, Digest::MD5 (for CRAM-MD5), Authen::NTLM (for NTLM/SPA/MSN)

=head1 EXIT CODES

=item 0 - no errors occurred

=item 1 - unrecognized type specified

=head1 CONTACT

=item proj-gen-auth@jetmore.net
