#!/usr/bin/perl -w
use lib '/home/mod_perl/hm';
use Mail::IMAPTalk;
use Net::LMTP;
use IO::Socket::UNIX;
use Getopt::Std;
use Data::Dumper;
use strict;

# Constants {{{
use constant SASL_SOCKET => '/var/state/saslauthd/mux';
use constant SERVER => 'localhost';
use constant ADMIN_USER => 'admin';
use constant TRACING => 0;
use constant INIT_SCRIPT => '/etc/rc.d/init.d/cyrus';

$Data::Dumper::Indent = 1;
# }}}

# Get command line options
my %opts;
getopts('ts', \%opts);

if (!@ARGV || (!$opts{t} && !$opts{s})) {
  print <<EOF;
usage: $0 -s /path/to/socket (saslauthd mode)
   or: $0 -t [all|test_name|test_*] (run tests mode)

##############################################
WARNING WARNING WARNING WARNING WARNING

Runing these tests will delete ALL existing
folders!
##############################################

When run with the -s flag, the process listens on the given
unix socket as a sasl authd and authenticates every user
and password

When run with the -t flag, runs specified tests. Tests
can be:
  all - all tests
  test_name - one specific text
  test.* - regexpt of test names to run

EOF
  exit(1);
}

# Run based on mode
sasl_mode(@ARGV) if $opts{s};
test_mode(@ARGV) if $opts{t};

exit(0);

# SASL mode {{{

sub sasl_mode {
  my ($socket_path) = @_;

  unlink SASL_SOCKET;
  my $listen_socket = IO::Socket::UNIX->new(
    Type => SOCK_STREAM,
    Local => SASL_SOCKET,
    Listen => 1
  ) || die "Could not create sasl socket: $!";
  chmod 0777, SASL_SOCKET;

  END {
    if ($listen_socket) {
      close $listen_socket;
    }
  }

  while (my $socket = $listen_socket->accept()) {
    $socket->autoflush(1);
    my $username = get_counted_string($socket);
    my $password = get_counted_string($socket);
    my $service = get_counted_string($socket);
    my $realm = get_counted_string($socket);
#	    warn "login: $username, $password, $service, $realm";
    print $socket pack("nA3", 2, "OK\000");
    close $socket;
  }

}

sub get_counted_string {
  my $socket = shift;

  my $data;
  read($socket, $data, 2);
  my $size = unpack("n", $data);
  read($socket, $data, $size);
  return unpack("A${size}", $data);
}

# }}}

sub test_mode {
  my @tests = @_;

  # Make a backup copy of the imapd.conf file
  system("/bin/cp -f /etc/imapd.conf /etc/imapd.conf.bak");

  # Get stash so we can get function names
  my $stash;
  {
    no strict 'refs';
    $stash = *{'Tests::'}{HASH};
  }

  # Iterate over all items in main stash (sorted)
  my @stash_items = sort keys %$stash;
  undef $stash;

  foreach my $name (@stash_items) {
    # Only want functions
    next if !Tests->can($name);

    my $call_it = 0;
    for (@tests) {
      $call_it = 1 if $name =~ /^t\d{3}_/ && $_ eq 'all';
      $call_it = 1 if $name =~ /^t\d{3}_${_}$/;
    }

    if ($call_it) {
      print $name, "\n";
#	      eval {
        my $fullname = "Tests::$name";
        my $sub;
        {
          no strict 'refs';
          $sub = \&$fullname;
        }
        $sub->();
#	      };
      if ($@) {
        print "Test failed: $@";
      }
    }
  }

  # Restore backup copy of the imapd.conf file
  system("/bin/mv -f /etc/imapd.conf.bak /etc/imapd.conf");

}

package Tests;

sub restart_server {
  system(::INIT_SCRIPT . " stop");
  sleep 1;
  system(::INIT_SCRIPT . " start");
}

sub set_imapdconf_flags {
  my %flags = @_;

  # Read existing file
  open(my $f, "</etc/imapd.conf")
    || die "Could not open imapd.conf: $!";
  my @lines = <$f>;
  close($f);

  # Remove existing lines
  @lines = grep { /^(.*?):/ && !exists $flags{$1} } @lines;

  # Add new lines
  while (my ($flag, $value) = each %flags) {
    push @lines, "$flag: $value\n" if defined $value;
  }

  # Write file
  open($f, ">/etc/imapd.conf")
    || die "Could not open imapd.conf: $!";
  print $f @lines;
  close($f);
}

sub new_imap_connection {
  my $username = shift;

  my $imap = Mail::IMAPTalk->new(
    Server => ::SERVER,
    Username => $username,
    Password => 'blah'
  ) || die "Could not connect to server : $@";
  $imap->set_tracing(1) if ::TRACING;

  return $imap;
}

sub random_folder_name {
  my ($min_len, $max_len) = @_;

  my $len = int(rand($max_len - $min_len)) + $min_len;
  return join '', map { chr(ord('a') + int(rand(26))) } (1 .. $len);
}

sub match_acl {
  my ($acl, $user, $expectedacl) = @_;

  # Get user acl we want
  while (@$acl && $acl->[0] ne $user) {
    shift @$acl; shift @$acl;
  }

  # Should have found it
  die "Didn't find ACL we wanted : $user, $expectedacl"
    if !@$acl;

  $acl = $acl->[1];
  
  join('', sort split(//, $acl)) eq
    join('', sort split(//, $expectedacl))
    || die "ACL lists don't match : $acl, $expectedacl";
}

sub t001_connect {
  set_imapdconf_flags(
    sasl_pwcheck_method => 'saslauthd'
  );
  restart_server();
  my $imap = new_imap_connection('noone');

  ($imap->state == Mail::IMAPTalk::Authenticated)
    || die "Not authenticated";

  $imap->noop() || die "No noop response";
  $imap->capability() || die "No capability response";
  exists $imap->capability()->{imap4} || die "No imap4 capability";

  $imap->is_open() || die "Expected open connection but was closed";

  $imap->logout() || die "Could not logout";
  ($imap->state == Mail::IMAPTalk::Unconnected)
    || die "Not unconnected";

#	  !$imap->is_open() || die "Expected closed connection";
}

sub t002_basic_user_folders {

  # Set admin user so we can create top-level folders
  set_imapdconf_flags(
    sasl_pwcheck_method => 'saslauthd',
    admins => ::ADMIN_USER,
    altnamespace => 0,
    unixhierarchysep => 0
  );
  restart_server();

  my $imap = new_imap_connection(::ADMIN_USER);

  # Cleanup any possible existing mess
  $imap->setacl('user.test1', 'admin', 'lrswipcda');
  $imap->delete('user.test1');

  # Create user folder
  $imap->create('user.test1')
    || die "Could not create user.test1";
  my $acl = $imap->getacl('user.test1')
    || die "Could not get user.test1 ACL 1";
  match_acl($acl, 'test1', 'lrswipcda');

  $imap->setacl('user.test1', 'admin', 'lrswipcda')
    || die "Could not set user.test1 ACL";
  $acl = $imap->getacl('user.test1')
    || die "Could not get user.test1 ACL 2";
  match_acl($acl, 'admin', 'lrswipcda');

  # Check user sees it as inbox
  my $imapu = new_imap_connection('test1');

  my $folders = $imapu->list('*', '*')
    || die "Could not get user folder list";
  ref($folders) || die "Not folder list";
  @$folders == 1 || die "Too many folders";
  $folders->[0]->[2] eq 'Inbox' || die "Not inbox";

  undef $imapu;

  $imap->delete('user.test1')
    || die "Could not delete user.test1";
}

sub t003_repeat_user_folders {

  # Set admin user so we can create top-level folders
  set_imapdconf_flags(
    sasl_pwcheck_method => 'saslauthd',
    admins => ::ADMIN_USER,
    altnamespace => 0,
    unixhierarchysep => 0
  );
  restart_server();

  my $imap = new_imap_connection(::ADMIN_USER);

  # Cleanup any existing mess
  my $list = $imap->list('*', '*');
  if (ref $list) {
    # Delete sub-folder first
    for (sort { length $b <=> length $a } map { $_->[2] } @$list) {
      $imap->setacl($_, 'admin', 'lrswipcda');
      $imap->delete($_);
    }
  }

  my @folders;
  for (1 .. 10000) {
    print $_, "\r" if $_ % 1000 == 0;
    my $rand = rand();

    # Create a random folder
    if ($rand < 0.7) {
      my $len = int(rand(5)) + 5;
      my $folder = "user." . random_folder_name($len, $len+4);
      $imap->create($folder)
        || die "Could not create folder $folder: $@";
      $imap->setacl($folder, 'admin', 'lrswipcda')
        || die "Could not set folder acl $folder: $@";
      push @folders, $folder;

    # Delete a random folder
    } elsif ($rand < 0.9 && @folders) {
      my $folder = splice @folders, int(rand(@folders)), 1;
      $imap->delete($folder)
        || die "Could not delete folder $folder: $@";

    # Check mailbox list
    } elsif (@folders) {
      $list = $imap->list('*', '*');
      my %list = map { $_->[2] => 1 } @$list;
      for (@folders) {
        delete $list{$_} || die "Folder $_ not found";
      }
      !%list || die "Not all folders found";
    }
  }
  for (@folders) {
    $imap->delete($_)
      || die "Could not delete folder $_: $@";
  }
  $list = $imap->list('*', '*');
  !ref($list) || die "Unexpectedly found folders";
}



# Older stuff not used yet...

# {{{ Messages
my $Message1 =<<'EOF';
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain
MIME-Version: 1.0
Date: Tue, 7 Nov 2000 08:31:21 UT
From: Joe Blogs <blogs@nowhere.com>
To: Who Ever <whoever@where-ever.com>
Reply-To: Joe Blogs <blogs@nowhere.com>
Subject: This is a message for you

Not much to say. It's just a boring message with not much in it.

EOF
my $Message2 =<<'EOF';
From: Joe Blogs <blogs@nowhere.com>
To: joe@nowhere.com
Cc: blah@blah.com
Subject: Nothing important
Date: Fri, 14 Sep 2001 17:18:03 +1000
MIME-Version: 1.0
Content-Type: multipart/related;
        boundary="----=_NextPart_000_00DD_01C13D41.3639C9D0";
        type="multipart/alternative"
X-Priority: 3
X-MSMail-Priority: Normal
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2600.0000

This is a multi-part message in MIME format.

------=_NextPart_000_00DD_01C13D41.3639C9D0
Content-Type: multipart/alternative;
        boundary="----=_NextPart_001_00DE_01C13D41.3639C9D0"


------=_NextPart_001_00DE_01C13D41.3639C9D0
Content-Type: text/plain;
        charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

This is an html message.
  a.. Bullet 1
  b.. Bullet 2
Bold, Arial font, even a different colour



------=_NextPart_001_00DE_01C13D41.3639C9D0
Content-Type: text/html;
        charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<META http-equiv=3DContent-Type content=3D"text/html; =
charset=3Diso-8859-1">
<META content=3D"MSHTML 6.00.2600.0" name=3DGENERATOR>
<STYLE></STYLE>
</HEAD>
<BODY bgColor=3D#ffffff>
<DIV><FONT face=3D"Courier New" size=3D2>This is an html =
message.</FONT></DIV>
<UL>
  <LI><FONT face=3D"Courier New" size=3D2>Bullet 1</FONT></LI>
  <LI><FONT face=3D"Courier New" size=3D2>Bullet 2</FONT></LI></UL>
<DIV><FONT face=3D"Courier New" size=3D2><STRONG>Bold</STRONG>, <FONT =
face=3DArial=20
size=3D4>Arial font<FONT face=3D"Courier New" size=3D2>, <FONT =
color=3D#ff0000>even a=20
different colour</FONT></FONT></FONT></FONT></DIV>
<DIV><FONT face=3D"Courier New" size=3D2></FONT>&nbsp;</DIV>
<DIV>&nbsp;</DIV>
<DIV><FONT face=3D"Courier New" size=3D2><IMG alt=3D"" hspace=3D0=20
src=3D"cid:00dc01c13ced$648aac90$0100a8c0@robm" align=3Dbaseline=20
border=3D0></FONT></DIV></BODY></HTML>

------=_NextPart_001_00DE_01C13D41.3639C9D0--

------=_NextPart_000_00DD_01C13D41.3639C9D0
Content-Type: image/jpeg;
        name="Portable-mini.jpg"
Content-Transfer-Encoding: base64
Content-ID: <00dc01c13ced$648aac90$0100a8c0@robm>

/9j/4AAQSkZJRgABAQEAGQAZAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a
HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy
MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCADBARUDASIA
AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3
ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm
p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA
AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx
BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK
U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3
uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwC1e4f4
kSgoV8uyPGeD8x/+KrVZQXAI3YB47dqyN5fx9qZb5njtEXI75ERraMYdR5iD157Gt6PwHPV+IpXC
FYnaNcKOMjI/kQePT9Kj+zQTRvbh5Db7MSJ5hww54xnHHfAHarE+fIURocvIoA555B5/I1IIlMGM
h1PI9z1zx+J4qt2K+h5h4u0JdBurS6sWmS2aRirFsvE3B+VuuBgkHryfqem0rxe7226+t/tPGBPb
Y+bgZ3KW4z35x2xxWrdadFql3L9pjF1DaOEEBkK5LKpY4Hfa+B6ZJ7isWbw3o0ckqWN3LYSKxCxF
wUDYyzEP8w5yOp+70rGXNF3ibJxkrSMWIS7kWZVUhi+2P5ggPOP1I5HQ1mX1u5kWR5FIiZlcEAA4
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

------=_NextPart_000_00DD_01C13D41.3639C9D0--
EOF
$Message1 =~ s/\n/\015\012/g;
$Message2 =~ s/\n/\015\012/g;
# }}}

#	sub match_quota {
#	  my ($Q, $Item, $Limit, $ApproxSize) = @_;
#	
#	  while (@$Q && lc($Q->[0]) ne $Item) {
#	    shift @$Q; shift @$Q; shift @$Q;
#	  }
#	
#	  die "Could not find quota" if !@$Q;
#	
#	  die "Invalid quota"
#	    if $Q->[2] != $Limit;
#	
#	  if ($ApproxSize) {
#	    my $Storage = $Q->[1]*1024;
#	    die "Unexpected storage size: $Storage, $ApproxSize"
#	      if (abs($ApproxSize-$Storage) > 1024*16);
#	  }
#	
#	}
#	
#	
#	sub test_acl {
#	  trace("test_acl");
#	  my $IMAP = shift;
#	
#	  if (exists $IMAP->capability()->{acl}) {
#	    # Create some folders
#	    my $ACL;
#	    my @F = create_folders($IMAP, $F1, $F2, $SF1);
#	
#	    # Set and test some ACLs for one user on a folder
#	    $IMAP->setacl($F1, 'imaptalktest1', 'lrswipcda')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswipcda');
#	    $IMAP->setacl($F1, 'imaptalktest1', '-a')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswipcd');
#	    $IMAP->setacl($F1, 'imaptalktest1', '-c')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswipd');
#	    $IMAP->setacl($F1, 'imaptalktest1', '-wipd')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrs');
#	    $IMAP->setacl($F1, 'imaptalktest1', '+dacwip')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswipcda');
#	
#	    # Add modify ACLs for first and second user
#	    $IMAP->setacl($F1, 'imaptalktest2', 'lrs')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $IMAP->setacl($F1, 'imaptalktest1', 'lrswipcda')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswipcda');
#	    match_acl($ACL, 'imaptalktest2', 'lrs');
#	    $IMAP->setacl($F1, 'imaptalktest2', '+w')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswipcda');
#	    match_acl($ACL, 'imaptalktest2', 'lrsw');
#	    $IMAP->setacl($F1, 'imaptalktest2', '+dac')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswipcda');
#	    match_acl($ACL, 'imaptalktest2', 'lrswdac');
#	    $IMAP->setacl($F1, 'imaptalktest1', '-dac')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswip');
#	    match_acl($ACL, 'imaptalktest2', 'lrswdac');
#	
#	    # Try on a sub folder
#	    $IMAP->setacl($SF1, 'imaptalktest1', 'lrs')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $ACL = $IMAP->getacl($SF1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrs');
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswip');
#	    match_acl($ACL, 'imaptalktest2', 'lrswdac');
#	
#	    $IMAP->setacl($SF1, 'imaptalktest1', '+wdac')
#	      || die "IMAP setacl failed on '$F1' failed";
#	    $ACL = $IMAP->getacl($SF1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswdac');
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswip');
#	    match_acl($ACL, 'imaptalktest2', 'lrswdac');
#	
#	    # And one more bit on another folder
#	    $IMAP->setacl($F2, 'imaptalktest1', 'lrsw')
#	      || die "IMAP setacl failed on '$F2' failed";
#	    $ACL = $IMAP->getacl($F2) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrsw');
#	    $ACL = $IMAP->getacl($F1) || die "Could not get ACL";
#	    match_acl($ACL, 'imaptalktest1', 'lrswip');
#	    match_acl($ACL, 'imaptalktest2', 'lrswdac');
#	
#	    # Delete the folders
#	    remove_folders($IMAP, @F);
#	
#	  }
#	
#	}
#	
#	sub test_quota {
#	  trace("test_quota");
#	  my $IMAP = shift;
#	
#	  if (exists $IMAP->capability()->{quota}) {
#	    my $Q;
#	    my @F = create_folders($IMAP, $F1, $SF1);
#	
#	    $IMAP->setquota($F1, '(storage 200)') || die "Can't set quota";
#	    $Q = $IMAP->getquota($F1) || die "Could not get quota";
#	    match_quota($Q, 'storage', 200);
#	    $IMAP->setquota($F1, '(storage 500)') || die "Count not set quota";
#	    $Q = $IMAP->getquota($F1) || die "Could not get quota";
#	    match_quota($Q, 'storage', 500);
#	
#	    $IMAP->getquota($SF1) && die "Unexpected quota on $SF1";
#	
#	    $Q = $IMAP->getquotaroot($SF1) || die "Could not get quotaroot";
#	    my @QR = @{$Q->{quotaroot}};
#	    shift(@QR) eq $SF1 || die "Quota root folder mismatch";
#	    shift(@QR) eq $F1 || die "Quota root folder mismatch";
#	
#	    $Q = $Q->{$F1};
#	    match_quota($Q, 'storage', 500);
#	
#	    $IMAP->append($F1, [ 'Literal', $Message1 ]);
#	    $IMAP->append($F1, [ 'Literal', $Message2 ]);
#	
#	    $Q = $IMAP->getquota($F1) || die "Could not get quota";
#	    match_quota($Q, 'storage', 500, length($Message1)+length($Message2));
#	
#	    my $Used = length($Message1)+length($Message2);
#	    while ($Used < 480*1024) {
#	      $IMAP->append($F1, [ 'Literal', $Message2 ]);
#	      $Used += length($Message2);
#	    }
#	
#	    $Q = $IMAP->getquota($F1) || die "Could not get quota";
#	    match_quota($Q, 'storage', 500, $Used);
#	
#	    $IMAP->append($F1, [ 'Literal', $Message2 ]);
#	    $IMAP->append($F1, [ 'Literal', $Message2 ])
#	      && die "Append succeeded over quota";
#	
#	    remove_folders($IMAP, @F);
#	  }
#	
#	}
#	
#	sub test_select {
#	  trace("test_select");
#	  my $IMAP = shift;
#	
#	  my @F = create_folders($IMAP, $F1, $SF1);
#	
#	  $IMAP->select($F1) || die "Could not select folder: $F1";
#	  my $Exists = $IMAP->get_response_code('exists');
#	  defined($Exists) && $Exists == 0 || die "Unexpected items in folder";
#	  my $Recent = $IMAP->get_response_code('recent');
#	  defined($Recent) && $Recent == 0 || die "Unexpected items in folder";
#	  my $PermFlags = $IMAP->get_response_code('permanentflags');
#	  ref($PermFlags) eq 'ARRAY' || die "Unexpected permanent flags";
#	
#	  $IMAP->select($SF1) || die "Could not select folder: $SF1";
#	
#	  $IMAP->append($F1, [ 'Literal', $Message1 ]);
#	  $IMAP->append($F1, [ 'Literal', $Message2 ]);
#	
#	  $IMAP->message_count($F1) == 2 || die "Wrong message count";
#	
#	  $IMAP->select($F1) || die "Could not select folder: $F1";
#	  $Exists = $IMAP->get_response_code('exists');
#	  defined($Exists) && $Exists == 2 || die "Unexpected items in folder";
#	  $Recent = $IMAP->get_response_code('recent');
#	  defined($Recent) && $Recent == 2 || die "Unexpected items in folder";
#	
#	#	  $IMAP->set_root_folder(uc($UserDir), $Sep, 1);
#	
#	#	  $IMAP->select($F1) || die "Could not select folder: $F1";
#	#	  $IMAP->select($SF1) || die "Could not select folder: $SF1";
#	#	  $IMAP->set_root_folder($UserDir, $Sep, 1);
#	#	  $IMAP->select('imaptalktest1') || die "Could not select folder: $F1";
#	#	
#	#	  my $LRes = $IMAP->list($UserDir, "%");
#	#	  my @LRes = map { $_->[2] } @$LRes;
#	#	
#	#	  grep { lc($_) eq 'imaptalktest1' } @LRes
#	#	    || die "Could not find folder name $F1 in list";
#	#	  
#	#	  grep { lc($_) eq 'imaptalktest1.sub1' } @LRes
#	#	    || die "Could not find folder name $SF1 in list";
#	
#	  remove_folders($IMAP, @F);
#	}
#	
#	sub test_folderops {
#	  trace("test_folderops");
#	  my $IMAP = shift;
#	
#	  my @F = create_folders($IMAP, $F1, $SF1, $SF2);
#	
#	  $IMAP->select('a_dummy_folder') && die "Seleted non-existant folder";
#	  $@ eq $IMAP->get_last_error() || die "Errors don't match";
#	
#	  $IMAP->delete($SF1) || die "Could not delete folder: $SF1";
#	  $IMAP->rename($SF2, $SF1) || die "Could not rename folder";
#	
#	  $IMAP->select('a_dummy_folder') && die "Seleted non-existant folder";
#	
#	  $IMAP->select($F1) || die "Could not select folder: $F1";
#	  my $Exists = $IMAP->get_response_code('exists');
#	  defined($Exists) && $Exists == 0 || die "Unexpected items in folder";
#	  my $Recent = $IMAP->get_response_code('recent');
#	  defined($Recent) && $Recent == 0 || die "Unexpected items in folder";
#	  my $PermFlags = $IMAP->get_response_code('permanentflags');
#	  ref($PermFlags) eq 'ARRAY' || die "Unexpected permanent flags";
#	
#	  my $LR = $IMAP->list("", "") || die "Could not list no folders";
#	  my $Sep = $LR->[0]->[1];
#	  $LR = $IMAP->list("", "%") || die "Could not list base folder";
#	  my $Base = $LR->[0]->[2];
#	  $LR = $IMAP->list("", $Base . $Sep . "%") || die "Could not list folders";
#	
#	  if (exists $IMAP->capability()->{acl}) {
#	    foreach my $FolDet (@$LR) {
#	      my $Fol = $FolDet->[2];
#	      # Save permissions for folder and ensure we can select it
#	      my $ACL = $IMAP->getacl($Fol) || die "Couldn't get ACL";
#	      $IMAP->setacl($Fol, $Username, 'lrswipcda') || die "Couldn't set ACL";
#	      $IMAP->select($Fol) || die "Could not select folder: $Fol";
#	      $IMAP->select($F1) || die "Could not delete folder: $F1";
#	      my %ACL = @$ACL;
#	      if ($ACL{$Username}) {
#	        $IMAP->setacl($Fol, $Username, $ACL{$Username})
#	          || die "Couldn't set ACL";
#	      } else {
#	        $IMAP->deleteacl($Fol, $Username) || die "Couln't delete ACL";
#	      }
#	    }
#	  }
#	
#	  $IMAP->select($F1);
#	
#	  $IMAP->append($F1, [ 'Literal', $Message1 ]);
#	  $IMAP->append($F1, [ 'Literal', $Message2 ]);
#	
#	  $IMAP->message_count($F1) == 2 || die "Wrong message count";
#	
#	  $IMAP->store(2, '+flags', '\\deleted');
#	  $IMAP->store(1, '+flags', '\\deleted');
#	  $IMAP->expunge();
#	  $IMAP->close();
#	
#	  $IMAP->message_count($F1) == 0 || die "Wrong message count";
#	
#	  remove_folders($IMAP, $SF1, $F1);
#	}
#	
#	sub test_messageops {
#	  trace("test_messageops");
#	  my $IMAP = shift;
#	
#	  my @F = create_folders($IMAP, $F1, $SF1, $SF2);
#	
#	  $IMAP->select($SF1) || die "Could not select folder: $SF1";
#	  my $Exists = $IMAP->get_response_code('exists');
#	  defined($Exists) && $Exists == 0 || die "Unexpected items in folder";
#	  my $Recent = $IMAP->get_response_code('recent');
#	  defined($Recent) && $Recent == 0 || die "Unexpected items in folder";
#	  my $PermFlags = $IMAP->get_response_code('permanentflags');
#	  ref($PermFlags) eq 'ARRAY' || die "Unexpected permanent flags";
#	
#	  $IMAP->append($SF1, [ 'Literal', $Message1 ]);
#	  $IMAP->append($SF1, [ 'Literal', $Message2 ]);
#	
#	  if ($IMAP->capability()->{uidplus}) {
#	    $IMAP->get_response_code('appenduid')->[1] == 2
#	      || die "Unexpected appenduid response code";
#	  }
#	
#	  $IMAP->message_count($SF1) == 2 || die "Wrong message count";
#	
#	  $IMAP->store(2, '+flags', '\\deleted');
#	  $IMAP->store(1, '+flags', '\\deleted');
#	  $IMAP->close();
#	
#	  $IMAP->message_count($SF1) == 0 || die "Wrong message count";
#	
#	  $IMAP->uid(1);
#	
#	  $IMAP->select($SF1) || die "Could not select folder: $F1";
#	  $IMAP->append($SF1, [ 'Literal', $Message1 ]);
#	  $IMAP->append($SF1, [ 'Literal', $Message2 ]);
#	
#	  $IMAP->copy(3, $SF2);
#	  $IMAP->message_count($SF2) == 1 || die "Wrong message count";
#	
#	  my $SR = $IMAP->search('not', 'deleted') || die "Could not search";
#	  @$SR == 2 || die "Unexpected message list";
#	  $SR->[0] == 3 || die "Unexpected message id";
#	  $SR->[1] == 4 || die "Unexpected message id";
#	
#	  # Now test flags
#	  my $PF = $IMAP->get_response_code('permanentflags')
#	    || die "Could not find permanent flags response";
#	
#	  # Pick \\flagged or any permanent flag
#	  my $PF1 = (grep { lc($_) eq '\\flagged' } @$PF)[0] || $PF->[0];
#	
#	  # Check if flag is set
#	  my $F = $IMAP->fetch(3, 'flags');
#	  my $FL = $F->{3}->{flags};
#	  my $HasFlag = grep { $PF1 eq $_ } @$FL;
#	
#	  # Toggle it
#	  $IMAP->store(3, ($HasFlag ? '-' : '+') . 'flags', $PF1);
#	
#	  # And check
#	  $F = $IMAP->fetch(3, 'flags');
#	  $FL = $F->{3}->{flags};
#	  my $HasFlag2 = grep { $PF1 eq $_ } @$FL;
#	
#	  !($HasFlag && $HasFlag2) && ($HasFlag || $HasFlag2)
#	    || die "Could not toggle flag";
#	
#	  remove_folders($IMAP, $SF1, $F1);
#	}
#	
#	sub test_bodystruct {
#	  trace("test_bodystruct");
#	  my $IMAP = shift;
#	
#	  my @F = create_folders($IMAP, $F1, $SF1, $SF2);
#	
#	  $IMAP->select($SF1) || die "Could not select folder: $SF1";
#	  my $Exists = $IMAP->get_response_code('exists');
#	  defined($Exists) && $Exists == 0 || die "Unexpected items in folder";
#	  my $Recent = $IMAP->get_response_code('recent');
#	  defined($Recent) && $Recent == 0 || die "Unexpected items in folder";
#	  my $PermFlags = $IMAP->get_response_code('permanentflags');
#	  ref($PermFlags) eq 'ARRAY' || die "Unexpected permanent flags";
#	
#	  $IMAP->append($SF1, [ 'Literal', $Message1 ]);
#	  $IMAP->append($SF1, [ 'Literal', $Message2 ]);
#	
#	  $IMAP->message_count($SF1) == 2 || die "Wrong message count";
#	
#	  $IMAP->uid(1);
#	  $IMAP->parse_mode(1);
#	
#	  my $E = $IMAP->fetch(1, 'envelope');
#	  $E = $E->{1}->{envelope};
#	  $E->{Date} eq 'Tue, 7 Nov 2000 08:31:21 UT' || die "Envelope date wrong";
#	  $E->{Subject} eq 'This is a message for you' || die "Envelope subj wrong";
#	  $E->{From} eq '"Joe Blogs" <blogs@nowhere.com>'
#	    || die "Envelope From wrong";
#	  $E->{To} eq '"Who Ever" <whoever@where-ever.com>'
#	    || die "Envelope To wrong";
#	  $E->{'Reply-To'} eq '"Joe Blogs" <blogs@nowhere.com>'
#	    || die "Envelope To wrong";
#	  $E->{'Cc'} eq '' || die "Envelope Cc wrong";
#	
#	  my $B = $IMAP->fetch(2, 'bodystructure');
#	  $B = $B->{2}->{bodystructure};
#	
#	  $B->{'MIME-Type'} eq 'multipart' || die "Bad mime type";
#	  $B->{'MIME-Subtype'} eq 'related' || die "Bad mime sub-type";
#	
#	  my $S1 = $B->{'MIME-Subparts'}->[0];
#	  $S1->{'MIME-Type'} eq 'multipart' || die "Bad mime type";
#	  $S1->{'MIME-Subtype'} eq 'alternative' || die "Bad mime sub-type";
#	  
#	  my $S2 = $S1->{'MIME-Subparts'}->[0];
#	  $S2->{'MIME-Type'} eq 'text' || die "Bad mime type";
#	  $S2->{'MIME-Subtype'} eq 'plain' || die "Bad mime sub-type";
#	  $S2->{'Size'} == 144 || die "Bad size";
#	  $S2->{'IMAP-Partnum'} eq '1.1' || die "Bad part num";
#	
#	  $S2 = $S1->{'MIME-Subparts'}->[1];
#	  $S2->{'MIME-Type'} eq 'text' || die "Bad mime type";
#	  $S2->{'MIME-Subtype'} eq 'html' || die "Bad mime sub-type";
#	  $S2->{'Size'} == 1079 || die "Bad size";
#	  $S2->{'IMAP-Partnum'} eq '1.2' || die "Bad part num";
#	
#	  my $BP = Mail::IMAPTalk::get_body_part($B, $S2->{'IMAP-Partnum'});
#	  $BP->{'IMAP-Partnum'} eq $S2->{'IMAP-Partnum'}
#	    || die "Unexpected part numbers";
#	
#	  my $MP = Mail::IMAPTalk::find_message($B);
#	  $MP->{plain}->{'IMAP-Partnum'} eq '1.1'
#	    || die "Error finding message plain part";
#	  $MP->{html}->{'IMAP-Partnum'} eq '1.2'
#	    || die "Error finding message html part";
#	
#	  my $CL = Mail::IMAPTalk::build_cid_map($B);
#	  $CL->{'00dc01c13ced$648aac90$0100a8c0@robm'}->{'IMAP-Partnum'} eq '2'
#	    || die "Error finding CID links";
#	
#	  remove_folders($IMAP, $SF1, $F1);
#	}

exit(0);

