#!/usr/bin/perl -w

########################################################################################################################
#
# GetLive - perl script to get mail from hotmail (live) mailboxes.
#
# $Id: GetLive.pl,v 2.29 2013/04/02 20:44:18 jdla Exp $
# $Name: Release_3_0 $
#
# Copyright (C) 2007-2013 Jos De Laender <jos@de-laender.be>
#
# 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
#
########################################################################################################################

use strict;
use encoding('UTF-8');
use File::Spec;
use URI::Escape;

########################################################################################################################
#
# Global constants and variables.
#
########################################################################################################################

my $ProgramName = "GetLive";
my $Revision    = '$Revision: 2.29 $';                # Meant for RCS.

# Constants of configuration.
my $Verbosity      = 1;                    # 0:Silent; 1:Normal; 2:Verbose; 10:debug; 100:heavy debug 
my $ConfigFile     = "";
my $ServerMode     = 0;
my $TrashFolderId  = "00000000-0000-0000-0000-000000000002"; 
my $DraftsFolderId = "00000000-0000-0000-0000-000000000004"; 
my $JunkFolderId   = "00000000-0000-0000-0000-000000000005"; 
my $NullFolderId   = "00000000-0000-0000-0000-000000000000";  # MoveToFolder ?

# This is not used in server mode. Only in classic mode :
my %FoldersToProcess = ();  # The folders to process (empty will be considered as all). Otherwise FolderName=>1 assoc.

# Browser.
my $Proxy     = "";
my $ProxyAuth = "";
my $UserAgent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.5) Gecko/20061201 Firefox/2.0.0.5 (Ubuntu-feisty)";

# Start page.
my $LoginPage = "https://mail.live.com";

########################################################################################################################
#
# GetLive package
# This is a kind of 'objectization' of the original GetLive.
# It should be useable in a 'classic' GetLive scenario as well as in a POP3 server scenario.
#
########################################################################################################################

package GetLive;
 
use strict;
use File::Spec;
use URI::Escape;
use HTML::Entities;

use WWW::Mechanize::GZip;
use HTTP::Cookies;

########################################################################################################################
#
# GetLive object creation.
#
########################################################################################################################

sub new {

  my $Class = shift;
  my $Self  = {};

  # User options.
  $Self->{'Login'}             = "";
  $Self->{'Domain'}            = "hotmail.com";
  $Self->{'Password'}          = "";
  $Self->{'MailProcessor'}     = '/usr/local/bin/procmail'; # Any program taking mbox formatted at stdin will do.
  $Self->{'DownloadedIdsFile'} = "";                  # Local file with Ids of already downloaded messages.
  $Self->{'MarkRead'}          = "No";                # No,Yes : But never when downloaded before !
  $Self->{'Delete'}            = "No";                # No,Yes : But never when downloaded before !
  $Self->{'SkipTrash'}         = "No";                # No,Yes : Do not handle the Trash folder
  $Self->{'SkipJunk'}          = "No";                # No,Yes : Do not handle the Junk folder
  $Self->{'Strategy'}          = "All";               # "Unread" or "All" or "NotDownloaded"
                                                      # Determines which messages will be handled.
  $Self->{'MoveToFolder'}      = "";                  # The name of the folder to move to after the download. "" is not.
                                                      # If it begins with @ it is reference to a filename that
                                                      # contains the folder to move to. This is a hook for 
                                                      # autoclassifying the mail on the server,including spam filtering.
  $Self->{'BreakOnAlreadyDownLoaded'} = 0;            # Stop folder scanning when a sequence found of already 
                                                      # downloaded message. A sequence as large as this number.
                                                      # (where 0 is inactive, i.e. keep scanning).

  # Mechanized browser.
  $Self->{'CookieJar'} = HTTP::Cookies->new();
  $Self->{'Browser'}   = WWW::Mechanize::GZip->new(agent      => $UserAgent, 
                                                   cookie_jar => $Self->{'CookieJar'}, 
                                                   autocheck  => 0);

  # List of Message characteristics.
  $Self->{'NrMessages'}        = 0;
  $Self->{'MessagesRead'}      = ();
  $Self->{'MessagesFrom'}      = ();
  $Self->{'MessagesSubject'}   = ();
  $Self->{'MessagesId'}        = ();
  $Self->{'MessagesMad'}       = ();
  $Self->{'MessagesDeleted'}   = (); # In support of pop server. Marking deleted.
  $Self->{'MessagesPopped'}    = (); # In support of pop server. Marking popped.

  # List of Folder characteristics.
  $Self->{'FolderNames'} = ();
  $Self->{'FolderIds'}   = ();
  $Self->{'NrFolders'}   = 0;

  # Logging related.
  $Self->{'DieOnError'}  = 1;        # Might be put on 0 for server application.
  $Self->{'LogToStdout'} = 1;        # Might be put on 0 for server application.

  # Log file in the temporary directory. Must be per object.
  my $TmpDir = File::Spec->tmpdir() . "/$ProgramName.$$.$^T";
  umask(077);
  mkdir($TmpDir) || die "Could not create $TmpDir : $!.";
  $Self->{'TmpDir'} = $TmpDir;

  my $LogFileHandle;
  my $LogFileName = "$TmpDir/Log";
  if ($Verbosity) {
    open ($LogFileHandle,">$LogFileName") || die "Could not open $LogFileName : $!";
    $Self->{'LogFileHandle'} = $LogFileHandle;
  }

  bless $Self,$Class;
  return $Self;
}

########################################################################################################################
# 
# Class method
# Parse the Configuration File
#
########################################################################################################################

sub ParseConfig {

  my $Self = shift;

  open (CONFIG,$ConfigFile) || die "Configuration file '$ConfigFile' could not be opened : $!.";

  # Parse the file
  while (<CONFIG>) {
    my $Line = $_;
    next if ($Line =~ /^#/); # Comment.
    next if ($Line =~ /^\s*$/); # Empty line.
    if (not $Line =~ m/^([a-zA-Z0-9-_]+)/) {
      $Self->Log("Wrong configuration line : '$_'.\n",stderr=>1);
    }
    my $Option      = $1;
    my $OptionValue = "";
    $Line           = $'; # The remaining of the line.
    if (not $Line =~ m/\s*=\s*\S+/) {
      $Self->Log("Wrong configuration line : '$_' (no value).\n",stderr => 1);
    }
    # Remove equals sign and leading, trailing whitespace.
    $Line =~ s/=//;
    $Line =~ s/^\s+|\s+$//g;
    $OptionValue = $Line;

    if ($Option =~ m/^UserName$/i) {
      $Self->{'Login'} = $OptionValue;
    } elsif ($Option =~ m/^Password$/i) {
      $Self->{'Password'} = $OptionValue;
    } elsif ($Option =~ m/^Mode$/i) {
      warn "Option Mode is not available anymore";
    } elsif ($Option =~ m/^Domain$/i) {
      $Self->{'Domain'} = $OptionValue;
    } elsif ($Option =~ m/^Proxy$/i) {
      $Proxy = $OptionValue;
      warn "Please contact author. Proxy code not yet foreseen.";
    } elsif ($Option =~ m/^ProxyAuth$/i) {
      $ProxyAuth = $OptionValue;
      warn "Please contact author. Proxy code not yet foreseen.";
    } elsif ($Option =~ m/^Downloaded$/i) {
      $Self->{'DownloadedIdsFile'} = $OptionValue;
      if ($OptionValue) {
        $Self->{'Strategy'} = "NotDownloaded";
      }
    } elsif ($Option =~ m/^RetryLimit$/i) {
      warn "Option RetryLimit is not available anymore";
    } elsif ($Option =~ m/^Processor$/i) {
      $Self->{'MailProcessor'} = $OptionValue;
    } elsif ($Option =~ m/^CurlBin$/i) {
      warn "Option CurlBin is not available anymore";
    } elsif ($Option =~ m/^Folder$/i) {
      $FoldersToProcess{lc $OptionValue} = 1;
    } elsif ($Option =~ m/^FetchOnlyUnread$/i) {
      my $FetchOnlyUnread = $OptionValue;
      if ($FetchOnlyUnread =~ m/Yes/i) {
        $Self->{'Strategy'} = "Unread";
      }
    } elsif ($Option =~ m/^MarkRead$/i) {
      $Self->{'MarkRead'} = $OptionValue;
    } elsif ($Option =~ m/^Delete$/i) {
      $Self->{'Delete'} = $OptionValue;
    } elsif ($Option =~ m/^SkipTrash$/i) {
      $Self->{'SkipTrash'} = $OptionValue;
    } elsif ($Option =~ m/^SkipJunk$/i) {
      $Self->{'SkipJunk'} = $OptionValue;
    } elsif ($Option =~ m/^MoveToFolder$/i) {
      $Self->{'MoveToFolder'} = $OptionValue;
    } elsif ($Option =~ m/^BreakOnAlreadyDownloaded$/i) {
      $Self->{'BreakOnAlreadyDownloaded'} = $OptionValue;
    } else {
      $Self->Log("Wrong configuration line : '$_' (unknown option).\n",stderr=>1);
    }
  }
  close(CONFIG);
}

########################################################################################################################
# 
# Class method : Destructor.
# Needed for cleanup of tmp files.
#
########################################################################################################################

sub DESTROY {

  my $Self = shift;

  close $Self->{'LogFileHandle'} if $Verbosity;
  return if ($Verbosity >9);    # Considered debug mode and thus keep the files !
  $Self->CleanTempFiles();
}


########################################################################################################################
# 
# Class method.
# Clean up any temporary files which are collected in a temporary directory.
#
########################################################################################################################

sub CleanTempFiles {

  my $Self = shift;

  my $TmpDir = $Self->{'TmpDir'};
  return if (! -e $TmpDir);     # We're even not at the point that the tmpdir exists ...
  # We are very forgiving on errors in removal. It's not the end of the world in the first place.
  # Besides our logging would be maybe in this same dir as well ...
  opendir (TMPDIR,$TmpDir) || return;
  while (my $FileName = readdir(TMPDIR)) {
    next if $FileName =~ m/^\.$/;    # Not the .
    next if $FileName =~ m/^\.\.$/;  # Nor .. directory
    $FileName =~ m/(.*)/;
    $FileName = $1;
    unlink("$TmpDir/$FileName");
  }
  closedir (TMPDIR);
  # Finally get rid of the temporary directory itself.
  rmdir($TmpDir);
}
 
########################################################################################################################
# 
# Class method.
# Log some text.
# First parameter : text to be displayed.
# Then a number of named parameters that are optional. 
# See %args.
#
########################################################################################################################

sub Log {

  my $Self = shift;
  my $Text = shift;
  my %Args = (MinVerbosity => 0,
              stderr       => 0,
              @_);

  my $LogFileHandle = $Self->{'LogFileHandle'};
  my $DieOnError    = $Self->{'DieOnError'};
  my $LogToStdout   = $Self->{'LogToStdout'};

  if ($Args{'stderr'}) {
    my ($Package,$FileName,$Line) = caller;
    $Text = "($FileName,$Line) :\n$Text";
    $Text .= "Calling sequence :\n";
    for (my $Depth=1; $Depth<10; $Depth++) {
      my ($Package,$FileName,$Line) = caller($Depth);
      last unless $Line;
      $Text .= "($FileName,$Line)\n";
    }
  }

  # stderr messages are under no circumstances suppressed.
  if ($Args{'stderr'}) {
    if ($Verbosity) {
      print $LogFileHandle $Text;
    }
    print $Text if $LogToStdout;
    die if $DieOnError and $LogToStdout;
    die $Text if $DieOnError and not $LogToStdout;
    return;
  }

  # Filter out the ones for which the verbosity is too high.
  return if ($Args{'MinVerbosity'} > $Verbosity);

  # And finally print ;-)
  # Flushed immediate , not to miss error messages.
  if ($Verbosity) {
    my $WasSelected = select($LogFileHandle);
    $|=1;
    select($WasSelected);
    print $LogFileHandle $Text;
  }
  print $Text if $LogToStdout;

  return;
}

########################################################################################################################
# 
# Class method.
# FindCookie
#
########################################################################################################################

sub FindCookie {
  my $Self = shift;
  my $Name = shift;
  my $CookieJarAsString = $Self->{'CookieJar'}->as_string();
  my @CookieJar = split '\n',$CookieJarAsString;
  foreach  my $Cookie (@CookieJar) {
    $Cookie =~ s/^Set-Cookie3:\s*(.*?)=(.*?);//i;
    if ($1 eq $Name) {
      return $2;
    }
  }
  return undef;
}

########################################################################################################################
# 
# Class method.
# Get a html page.
# Returns a second string with the latest url.
#
########################################################################################################################

sub GetPage {

  my $Self = shift;
  my %Args = (Url      => "",
              PostData => {},
              @_);

  my $Url      = $Args{'Url'};
  my %PostData = %{$Args{'PostData'}};
  my $Browser  = $Self->{'Browser'};

  $Self->Log("Get page '$Url'.\n", MinVerbosity => 10);

  my $Response;
  if (keys %PostData) {
    foreach my $Field (keys %PostData) {
      $Self->Log("Data '$Field' : '$PostData{$Field}'.\n", MinVerbosity => 100);
    }
    $Response = $Browser->post($Url,[%PostData]);
  } else {
    $Response = $Browser->get($Url);
  }

  if ($Response->is_error()) {
    my $Explanation = $Response->status_line();
    $Self->Log("Could not access '$Url'. Status : '$Explanation'.\n",stderr => 1);
    return;
  }
 
  $Self->Log("Response headers : \n" . $Response->headers_as_string(), MinVerbosity => 100);

  $Self->Log("Response content : \n" . $Response->content(), MinVerbosity => 100);

  return ($Response->content(),uri_unescape($Response->request->uri));
}

########################################################################################################################
# 
# Class method.
# Do the HotMail login process - log in until we have the URL of the inbox.
# Return 1 on success, 0 on failure.
#
########################################################################################################################

sub Login {

  my $Self = shift;

  $Self->Log("Getting hotmail loginpage '$LoginPage'.\n", MinVerbosity =>2);

  my ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => $LoginPage);

  # We expect here a number of functions now (aug 2007) to be hidden in a javascript
  # that is loaded separately. Let's load and append.
  
  my $BaseHref = "";
  if ($LoginPageAsString =~ m/<base\s+href=\"([^\"]+)\"/) {
    $BaseHref = $1;
    $Self->Log("Found base href to be '$BaseHref'.\n",MinVerbosity => 10);
  }

  my @JavaScriptHrefs;
  my $JavaScriptHref = "";
  while ($LoginPageAsString =~ m/<script\s+type=\"text\/javascript\"\s+src=\"([^\"]+)\"/g ) {
    $JavaScriptHref = $1;
    push @JavaScriptHrefs,$JavaScriptHref;
    $Self->Log("Found javascript href to be '$JavaScriptHref'.\n",MinVerbosity => 10);
  }
  
  if (!$JavaScriptHref) {
    $Self->Log("Expected javascript href at this stage.\n",stderr => 1);
    return 0;
  }
 
  foreach my $Ref (@JavaScriptHrefs) {
    $Self->Log("Fetching the JS href.\n",MinVerbosity => 10);
    if ($Ref !~ m/^http[s]?:\/\//i) {
      $Ref = $BaseHref . $Ref;
    }
    my ($JSPageAsString,$JSGetPageUrl) = $Self->GetPage(Url => $Ref);

    # Append the JS stuff into our page.
    $LoginPageAsString .= $JSPageAsString;
  }

  # Thanks to Michael Kelly for this patch at July 29, 2012. And
  # that he updated at December 15, 2012.

  my %PostData = ();
  my $Domain = $Self->{'Domain'};
  my ($LoginUrl) = $LoginPageAsString =~ m{'(https://login.live.com/ppsecure/post.srf.*?)'};

  if ($LoginPageAsString !~ m/[F|AP]:'(P.*?)'/s) {
    $PostData{"PPSX"} = "Pass";
  } else {
    $Self->Log("PPSX detected as '$1'.\n", MinVerbosity => 10 );
    $PostData{"PPSX"} = $1;
  }
  
  # End of Michael Kelly patch.

  # PPFT is a normal (ie non JS) hidden input type.
  if( $LoginPageAsString !~ m/<\s*input\s+.*name=\"PPFT\"(\s+id="\S+")?\s+value=\"(\S*)\"/ ) {
    Self->Log("Page doesn't contain input field PPFT as expected.\n",stderr => 1);
    return 0;
  }
  $Self->Log("PPFT detected : '$2'.\n",MinVerbosity => 10 );
  $PostData{"PPFT"} = $2;

  # A number of other assumption that are peeled deep out of JS.
  # I'm afraid that the need for an embedded JS interpreter is coming closer ...
  $PostData{"type"} = "11";
  $PostData{"NewUser"} = "1";
  $PostData{"i1"} = "0";
  $PostData{"i2"} = "0";
  
  # Omar Ramadan Bug-fix for passwords containing '='
  my $Password = $Self->{'Password'};
  my @PassExplode = split("=", $Password);
  $Password = $PassExplode[0];

  #login and password.
  my $Login = $Self->{'Login'};
  $PostData{"login"} =  $Login.'@'.$Domain;
  $PostData{"passwd"} = $Password; 

  # Second step of login.
  $Self->Log("Logging in '$PostData{'login'}'.\n",MinVerbosity => 1);

  ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => $LoginUrl,PostData => \%PostData);

  # An extra cycle if we have a nag screen. (edlin303 patch)
  if ($LoginPageAsString =~ m/action="https:\/\/account\.live\.com\/Proofs\/Manage/i) { 
    $Self->Log("Detected Security nag - Retrying http://www.hotmail.com.\n", MinVerbosity => 1); 
    ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => 'http://www.hotmail.com'); 
  }

  # Cycle around until logged in.
  my $MaxCycles = 10;
  my %Visited   = ();
  while (1) {
    my $Redirect = "";
    if ($LoginPageAsString =~ m/window\.location\.replace\(\"(.*)\"\);/i or
        $LoginPageAsString =~ m/<meta http-equiv=\"REFRESH\" content=\"0;\sURL=([^\"]*)\"/i) { 
      $Redirect = $1;
      $Self->Log("Detected redirect '$Redirect'.\n", MinVerbosity => 10);
    }
    last unless $Redirect;
    if (exists $Visited{$Redirect} || keys %Visited > $MaxCycles) {
      $Self->Log("Running in cycles. Login structure has changed ?\n",stderr => 1);
      return 0;
    }
    $Visited{$Redirect} = ();
    $Self->Log("Following redirect '$Redirect'.\n",MinVerbosity => 2);
    ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => $Redirect);
  }
  
  $Self->Log("PageUrl : '$GetPageUrl'.\n", MinVerbosity => 10);

  # Yet a cycle for the browsersupport that is announced in the URL.
  if ($GetPageUrl =~ m/BrowserSupport.*?targetUrl=(.*?)\&/i) {
    $Self->Log("BrowserSupport url : '$1'.\n", MinVerbosity => 10);
    $GetPageUrl = $1;
  }

  $Self->Log("Final main url : '$GetPageUrl'.\n", MinVerbosity => 10);

  # Finally fech the page.
  ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => $GetPageUrl);
  
  $GetPageUrl =~ m/(.*\/)/;

  $Self->{'BaseUrl'}  = $1;
  $Self->{'LoginUrl'} = $GetPageUrl;

  # At this moment we assume we are logged in, but there should be some 'markers' to  
  # check this reasonably.

  my $LoggedIn = 0;
  if ($LoginPageAsString =~ m/<title>(.*?)<\/title>/s) {
    my $Title = $1;
    $Title =~ s/^\s*//sm;
    $Title =~ s/\s*$//sm;
    $Title =~ s/Outlook - //;
    if ($Title =~ m/^$Login\@$Domain$/i) {
      $LoggedIn = 1;
    }
  }

  if (!$LoggedIn) {
    $Self->Log("Could not log in. Maybe structure has changes or was not foreseen.\n",stderr => 1);
    return 0;
  }

  # We also expect (and need) Session information for later Ajax calls.
  if ($LoginPageAsString !~ m/fppCfg:\s*?\{(.*?)\}/s) {
    $Self->Log("Could not retrieve 'fppCfg'.\n",stderr => 1);
    return 0;
  }
  my $FppCfg = $1;

  if ($FppCfg !~ m/SessionId:\s*?"(.*?)",/s) {
    $Self->Log("Could not retrieve 'SessionId'.\n",stderr => 1);
    return 0;
  }
  $Self->{'SessionId'} = $1;

  if ($FppCfg !~ m/AuthUser:\s*?"(.*?)",/s) {
    $Self->Log("Could not retrieve 'AuthUser'.\n",stderr => 1);
    return 0;
  }
  $Self->{'AuthUser'} = $1;

  $Self->Log("LoginUrl    : '$Self->{'LoginUrl'}'.\n" , MinVerbosity => 10);
  $Self->Log("BaseUrl     : '$Self->{'BaseUrl'}'.\n"  , MinVerbosity => 10);
  $Self->Log("SessionId   : '$Self->{'SessionId'}'.\n", MinVerbosity => 10);
  $Self->Log("AuthUser    : '$Self->{'AuthUser'}'.\n" , MinVerbosity => 10);

  $Self->Log("Got MainPage for '$Login\@$Domain'.\n", MinVerbosity => 1);

  # Scan the line for all folders, their href and title.
  # NrFolders on the fly;
  $Self->{'FolderNames'} = ();
  $Self->{'FolderIds'}   = ();
  my $NrFolders = 0;

  # Peel out the "folderViewModel" table
  if ($LoginPageAsString !~ m/folderViewModel:\[(.*?)\]/si) {
    $Self->Log("Could not detect 'folderViewModel'.\n", stderr => 1);
    return 0;
  }
  $LoginPageAsString = $1;

  while ($LoginPageAsString =~ m/\{(.*?)\}/sgc) {
    my $FolderStuff = $1; 
    # do not die on this non match as the end of the table summary is just like this.
    if ($FolderStuff !~ m/fid:'(.*?)',name:'(.*?)'/) {
      $Self->Log("Could not parse 'folderViewModel'.\n", stderr => 1);
      return 0;
    }

    $Self->{'FolderIds'}[$NrFolders] = $1;
    my $Name = decode_entities($2);
    $Self->{'FolderNames'}[$NrFolders] = $Name;
    $Self->Log(
     "Folder $NrFolders - $Self->{'FolderIds'}[$NrFolders] - $Self->{'FolderNames'}[$NrFolders].\n", 
      MinVerbosity => 10);
    $NrFolders++;
  }
  if (!$NrFolders) {
    $Self->Log("No folders detected. Likely the page structure has changed.\n",stderr => 1);
    return 0;
  }

  $Self->{'NrFolders'} = $NrFolders;

  return 1;
}

########################################################################################################################
# 
# Class method.
# Get the messages from the folder with Idx as argument.
# 
########################################################################################################################

sub GetMessagesFromFolder {

  my $Self       = shift;
  my $FolderIdx  = shift;

  my $FolderName = $Self->{'FolderNames'}[$FolderIdx];
  my $FolderId   = $Self->{'FolderIds'}[$FolderIdx];

  $Self->Log("Loading folder '$FolderName'.\n", MinVerbosity => 1);
 
  my $LoginUrl = $Self->{'LoginUrl'};

  my $Page          = 0;
  my $StillPageToGo = 1;
  my $CurrentPage   = 1;

  my $PageAsString;
  my $GetPageUrl;
  
  my $PageUrl = "$LoginUrl&fid=$FolderId";
  
  $Self->{'MessagesRead'}      = ();
  $Self->{'MessagesFrom'}      = ();
  $Self->{'MessagesSubject'}   = ();
  $Self->{'MessagesId'}        = ();
  $Self->{'MessagesMad'}       = ();
  $Self->{'MessagesDeleted'}   = ();
  $Self->{'MessagesPopped'}    = ();
  
  my $NrMessages           = 0;
 
  my $SequenceOfDownloaded = 0;
  my $MessagesToScan       = -1;
  my $MessagesScanned      = 0;

  while ($StillPageToGo) {

    $Page++;
    $StillPageToGo = 0;

    ($PageAsString,$GetPageUrl) = $Self->GetPage(Url => $PageUrl);
    $Self->Log("Handling page $Page.\n",MinVerbosity => 2);

    # Search and check on total number messages
    if ($PageAsString !~ m/<div id="mlRange".*?>(\d*)/si) {
      $Self->Log("Did not find 'mlRange'\n",stderr => 1);
      return;
    }
    if ($MessagesToScan != -1 && $1 != $MessagesToScan) {
      $Self->Log("Inconsistent MessagesToScan\n",stderr => 1);
      return;
    }
    $MessagesToScan = $1;

    if ($PageAsString !~ m/<div.*?class="InboxTableBody".*?>/si) {
      # Either there are no messages or we have an error.
      if ($PageAsString !~ m/<div\s*?id="NoMsgs"/si) {
        $Self->Log("Did not find 'InboxTable' nor 'NoMsgs'\n",stderr => 1);
        return;
      }
      last; # We just happen to be empty.
    }

    $PageAsString = $& . $'; # Everything from <table on ...

    my $MessageId; #outside the loop as it is used for the next page url

    while ($PageAsString =~ m/(<li\s+?class="(ia_hc.*?)".*?id="(.*?)".*?mad="(.*?)".*?>.*?<\/li>)/sig) {

      my $OneMessageTable = $1;
      my $ReadIndicator   = $2;
         $MessageId       = $3;
      $Self->{'MessagesId'}[$NrMessages]  = $MessageId;
      $Self->{'MessagesMad'}[$NrMessages] = $4;

      my $Read = 1;
      if ($ReadIndicator =~ m/mlUnrd/) {
        $Read = 0;
      }
      $Self->{'MessagesRead'}[$NrMessages] = $Read;

      if ($OneMessageTable !~ m/<span email="(.*?)".*?>.*?<\/span>/si) {
        $Self->Log("Did not find 'span email'\n",stderr => 1);
        return
      }
      my $From = decode_entities($1);
      $Self->{'MessagesFrom'}[$NrMessages] = $From;

      if ($OneMessageTable !~ m/<span class=Sb><a href=.*?>(.*?)<\/a>/si) {
        $Self->Log("Did not find 'Sb'\n",stderr => 1);
        return;
      }
      # WTF ?
      my $Subject = $1;
      $Subject =~ s/&#x200f;//i;
      $Subject = decode_entities($Subject);
      $Self->{'MessagesSubject'}[$NrMessages] = $Subject;

      # Mark undeleted/Unpopped. In support of pop server.
      $Self->{'MessagesDeleted'}[$NrMessages] = 0;
      $Self->{'MessagesPopped'} [$NrMessages] = 0;

      my $Downloaded = 0;
      if ($Self->{'MessagesDownloaded'}->{uc($MessageId)}) {
        $Downloaded = 1;
        $SequenceOfDownloaded++;
        if (($Self->{'BreakOnAlreadyDownloaded'})&&($SequenceOfDownloaded > $Self->{'BreakOnAlreadyDownloaded'})) {
          $Self->Log("Breaking on $SequenceOfDownloaded\n",MinVerbosity=>10);
          last;
        }
      } else {
        $SequenceOfDownloaded = 0;
      }

      if ($Self->{'Strategy'} =~ m/NotDownloaded/i) {
        $NrMessages++ unless $Downloaded;
      } elsif ($Self->{'Strategy'} =~ m/UnRead/i) {
        $NrMessages++ unless $Read;
      } else {
        $NrMessages++;
      }

      $Self->Log("$NrMessages - From '$From' - Subject '$Subject' - Read : $Read - Downloaded : $Downloaded\n",
                 MinVerbosity=>3);

      $MessagesScanned++;
    }

    if (($Self->{'BreakOnAlreadyDownloaded'})&&($SequenceOfDownloaded > $Self->{'BreakOnAlreadyDownloaded'})) {
      $Self->Log("Breaking on $SequenceOfDownloaded\n",MinVerbosity=>10);
      last;
    }

    if ($MessagesScanned < $MessagesToScan) {
      $StillPageToGo = 1;
      $CurrentPage++;
      $PageUrl = "$LoginUrl&fid=$FolderId&pdir=NextPage&paid=$MessageId&pidx=$CurrentPage";
    } 
  }
  $Self->{'NrMessages'} = $NrMessages;
}

########################################################################################################################
# 
# Class method.
# Load DownloadedIds
# 
########################################################################################################################

sub LoadDownloadedIds {
  my $Self               = shift;

  my $DownloadedIdsFile  = $Self->{'DownloadedIdsFile'};

  my %MessagesDownloaded = ();

  return unless $DownloadedIdsFile;

  # First we check and or create the file with the downloaded Ids.
  if (not -e $DownloadedIdsFile) {
    open (DOWNLOADED,">$DownloadedIdsFile") || die "Could not open $DownloadedIdsFile : $!.";
    print DOWNLOADED "-- This is an automatically generated file by $0 containing the id of downloaded messages\n";
    close (DOWNLOADED);
  }
     
  open (DOWNLOADED,"$DownloadedIdsFile") || die "Could not open $DownloadedIdsFile : $!.";
  while(my $Id = <DOWNLOADED>) {
    chomp ($Id);
    $MessagesDownloaded{uc($Id)} = 1;
  }
  close (DOWNLOADED);

  $Self->{'MessagesDownloaded'} = \%MessagesDownloaded;
}
 
########################################################################################################################
# 
# Class method.
# Save DownloadedIds
# 
########################################################################################################################

sub SaveDownloadedIds {
  my $Self               = shift;

  my $DownloadedIdsFile  = $Self->{'DownloadedIdsFile'};
  my $MessagesDownloaded = $Self->{'MessagesDownloaded'}; 

  return unless $DownloadedIdsFile;

  # Remove preexisting file and recreate with new info.
  unlink($DownloadedIdsFile);
  open (DOWNLOADED,">$DownloadedIdsFile") || die "Could not open $DownloadedIdsFile : $!.";
  print DOWNLOADED "-- This is an automatically generated file by $0 containing the id of downloaded messages\n";
  foreach my $Key (sort keys %$MessagesDownloaded) {
    print DOWNLOADED "$Key\n";
  }
  close (DOWNLOADED);
}
 
########################################################################################################################
# 
# Class method.
# Process the messages retrieved from a folder.
# Acts on global variables @Messages ...
# It just takes FolderIdx for knowing the name. (and now also for the MoveToFolder/Delete command)
# 
########################################################################################################################

sub ProcessMessagesFromFolder  {
  my $Self              = shift;
  my $FolderIdx         = shift;

  my $MailProcessor     = $Self->{'MailProcessor'};
  my $Strategy          = $Self->{'Strategy'};
  my $MarkRead          = $Self->{'MarkRead'};
  my $MoveToFolder      = $Self->{'MoveToFolder'};
  my $Delete            = $Self->{'Delete'};
  my $FolderName        = $Self->{'FolderNames'}[$FolderIdx];

  # Now let's run through all detected messages ..
  my $MessageIdx;

  for ($MessageIdx = 0; $MessageIdx < $Self->{'NrMessages'}; $MessageIdx++) {

    # Identifying a bit the message for the log.
    $Self->Log("Handling mail\n".
               "  from    : '$Self->{'MessagesFrom'}[$MessageIdx]'\n".
               "  subject : '$Self->{'MessagesSubject'}[$MessageIdx]'\n",MinVerbosity => 1);

    my $Message = $Self->GetEmail($MessageIdx,$FolderName);

    # Pipe it through a processor such as procmail.
    if ($MailProcessor) {
      $Self->Log("Sending mail to '$MailProcessor'.\n",MinVerbosity => 1);
      open PR,"|$MailProcessor";
      print PR $Message;
      close PR || die "Sending mail to '$MailProcessor' did not succeed. See error log.";
    }

    # And maybe we have to mark it read too ?
    if ($MarkRead =~ m/^Yes$/i and not $Self->{'MessagesRead'}[$MessageIdx]) {
      $Self->MarkRead($MessageIdx);
    }
 
    # Maybe we even have to move it !
    if ($MoveToFolder ne "") {

      # If MoveToFolder is of the format @FileName, get the folder name from that FileName.
      if ($MoveToFolder =~ m/^@(.*)$/) {
        my $MoveToFolderName = $1;
        open(IN,$MoveToFolderName) || die "Could not open '$MoveToFolderName' : $!";
        $MoveToFolder = <IN>;
        chomp $MoveToFolder;
        close(IN);
      }

      # Do the move.
      $Self->MoveToFolder($MessageIdx,$MoveToFolder,$FolderIdx);
    }
   
    # Or maybe we have to remove it.
    if ($Delete =~ m/^Yes$/i) {
      $Self->DeleteMessage($MessageIdx);
    }

    # And now also remember it was 'downloaded'
    my $MessageId = $Self->{'MessagesId'}[$MessageIdx];
    $Self->{'MessagesDownloaded'}->{uc($MessageId)} = 1;

    # Some safety saving for "crashing" boxes.
    if ( ($MessageIdx % 10) == 9) {
      $Self->SaveDownloadedIds();
    }

    $Self->Log("Done.\n",MinVerbosity => 1);
  }
}

########################################################################################################################
# 
# Class method.
# Move the email message to a folder.
# MessageIdx and FolderName as argument.
#
########################################################################################################################

sub MoveToFolder {

  my $Self             = shift;
  my $MessageIdx       = shift;
  my $TargetFolderName = shift;
  my $SourceFolderIdx  = shift;

  my $NrFolders    = $Self->{'NrFolders'};

  my $MessageId    = $Self->{'MessagesId'}[$MessageIdx];
  my $MessageMad   = $Self->{'MessagesMad'}[$MessageIdx];

  my $TargetFolderIdx   = 0;
  my $TargetFolderFound = 0; 
  while ((not $TargetFolderFound) && $TargetFolderIdx<$NrFolders) {
    if (lc $TargetFolderName eq lc $Self->{'FolderNames'}[$TargetFolderIdx]) {
      $TargetFolderFound = 1;
    } else {
      $TargetFolderIdx++;
    }
  }

  # Let's die the hard way if we do not find that folder.
  if (not $TargetFolderFound) {
    $Self->Log("Folder with name '$TargetFolderName' used in MoveToFolder could not be located.\n",stderr => 1);
    return;
  }
      
  $Self->Log("Moving email message to folder '$TargetFolderName'.\n",MinVerbosity => 1);
  
  my $ToBox   = $Self->{'FolderIds'}[$TargetFolderIdx];
  my $FromBox = $Self->{'FolderIds'}[$SourceFolderIdx];

  my $BaseUrl   = $Self->{'BaseUrl'};
  my $SessionId = $Self->{'SessionId'};
  my $AuthUser  = $Self->{'AuthUser'};

  # A cookie that has to be added to the header.
  $Self->{'Browser'}->add_header("mt",$Self->FindCookie("mt"));

  my $Url = "${BaseUrl}mail.fpp?cnmn=Microsoft.Msn.Hotmail.Ui.Fpp.MailBox.MoveMessagesToFolder&". 
            "ptid=0&".
            "a=$SessionId&".
            "au=$AuthUser";

  my %PostData = ();
  $PostData{"cn"} = "Microsoft.Msn.Hotmail.Ui.Fpp.MailBox";
  $PostData{"mn"} = "MoveMessagesToFolder";
  $PostData{"v"}  = "1";
  $PostData{"d"}  = "\"$FromBox\"," .
                    "\"$ToBox\","   .
                    "[\"$MessageId\"]," .
                    "[{\"$MessageMad\"}]," .
                    "null," .
                    "{null,null,,FirstPage,5,1,\"$NullFolderId\",\"\",null,Date,false,false,\"\",null,0," .
                      "Off,47,null,null,false},".
                    "false,false,null,null,null,false,false,false,false";

  # Do The move ...
  my ($EmailPageAsString,$GetPageUrl) = $Self->GetPage(Url => $Url,PostData => \%PostData); 
}

########################################################################################################################
# 
# Class method.
# Delete the message.
# MessageIdx as argument.
#
########################################################################################################################

sub DeleteMessage {
  my $Self       = shift;
  my $MessageIdx = shift;

  my $MessageId  = $Self->{'MessagesId'}[$MessageIdx];

  $Self->Log("Deleting email message.\n",MinVerbosity => 1);
 
  my $LoginUrl = $Self->{'LoginUrl'};

  my $PageUrl = "$LoginUrl?mid=$MessageId&aId=moveTo&fid=$TrashFolderId";
  warn "DeleteMessage not yet implemented.";
  return;
  my ($PageAsString,$GetPageUrl) = $Self->GetPage(Url => $PageUrl);
}

########################################################################################################################
# 
# Class method.
# Mark the email message as read
# MessageIdxIdx as argument.
#
########################################################################################################################

sub MarkRead {
  my $Self       = shift;
  my $MessageIdx = shift;

  my $MessageId  = $Self->{'MessagesId'}[$MessageIdx];

  $Self->Log("Marking email message as read.\n",MinVerbosity => 1);
 
  my $LoginUrl = $Self->{'LoginUrl'};

  # The day I guesstimated this simple combination, I'd better spent buying lots of lotto forms !
  my $PageUrl = "$LoginUrl&mid=$MessageId";
  my ($PageAsString,$GetPageUrl) = $Self->GetPage(Url => $PageUrl);
}

########################################################################################################################
# 
# Class method.
# Return the email message (mbox format) as one big string.
# MessageIdx and FolderName as argument.
#
########################################################################################################################

sub GetEmail {
  my $Self       = shift;
  my $MessageIdx = shift;
  my $FolderName = shift;

  my $Login      = $Self->{'Login'};
  my $Domain     = $Self->{'Domain'};
  my $BaseUrl    = $Self->{'BaseUrl'};

  my $MessageId  = $Self->{'MessagesId'}[$MessageIdx];

  $Self->Log("Getting email message.\n",MinVerbosity => 1);

  my $Url = "${BaseUrl}GetMessageSource.aspx?msgid=$MessageId";
  my ($EmailPageAsString,$GetPageUrl) = $Self->GetPage(Url => $Url);

  $EmailPageAsString =~ s/^[\s\n]*//; 
  $EmailPageAsString = decode_entities($EmailPageAsString); # Strips all HTML artifacts from the message body.
  $EmailPageAsString =~ s/\r\n/\n/gs; # Force unix line endings.

  if ($EmailPageAsString !~ /<pre>[\s\n]*(.*?)<[^<]+$/si) {
    $Self->Log("Unable to download email message.\n",stderr => 1);
    return;
  }
  $EmailPageAsString = $1;

  # Fallback envelope sender and date, case it would not be in the message.
  my $FromAddress = "$Login\@$Domain";
  my $FromDate    = scalar gmtime;

  # Strip "From whoever" when found on the first line- the format is wrong for mbox files anyway.
  if ($EmailPageAsString =~ s/^From ([^ ]*) [^\n]*\n//s) { 
    $FromAddress = $1; 
  } elsif ($EmailPageAsString =~ m/^From:[^<]*<([^>]*)>/m) { 
    $FromAddress = $1;  
  }

  # Apply >From quoting
  $EmailPageAsString =~ s/^From ([^\n]*)\n/>From $1/gm;

  # If an mboxheader was desired, make up one
  if ($EmailPageAsString =~ m/^\t (\w+), (\d+) (\w+) (\d+) (\d+):(\d+):(\d+) ([+-]?.+)/m) {
    my $DayOfWeek = $1;
    my $Month     = $3;
    my $Day       = $2;
    my $Hour      = $5;
    my $Minute    = $6;
    my $Second    = $7;
    my $Year      = $4;
    my $TimeZone  = $8;

    # Put date in mboxheader in UTC time
    $Hour -= $TimeZone;
    while ($Hour < 0)  { $Hour += 24; }
    while ($Hour > 23) { $Hour -= 24; }

    $FromDate = sprintf ("%s %s %02d %02d:%02d:%02d %d",$DayOfWeek,$Month,$Day,$Hour,$Minute,$Second,$Year);
  }

  # Add an mbox-compatible header
  # And add some identifying headers.
  $EmailPageAsString =~ s/^/From $FromAddress $FromDate\nX-$ProgramName-Version: $Revision\nX-$ProgramName-Folder: $FolderName\nX-$ProgramName-User: $Login\n/;

  return $EmailPageAsString;
}

########################################################################################################################
# 
# Standard return for a correct package.
#
########################################################################################################################

1;

########################################################################################################################
#
# PopLive package
# This is a POP3 server object that takes services of GetLive to interface between a POP client and hotmail.
#
########################################################################################################################

package PopLive;

use strict;
use vars qw(@ISA);
use Net::Server::Fork; # any personality will do
@ISA = qw(Net::Server::Fork);

my $ConnectionTimeOut = 600;

########################################################################################################################
# 
# Inherited Class method.
# A new request (connection) has come in. Process it.
#
########################################################################################################################

sub process_request {
  my $Self = shift;

  $Self->{'LoggedIn'}          = 0;
  $Self->{'Username'}          = "";
  $Self->{'Password'}          = "";
  $Self->{'FolderToProcess'}   = "";
  $Self->{'FolderIdToProcess'} = 1;
  $Self->{'MarkRead'}          = 0;

  $Self->Respond("+OK POP3 server ready.\r\n");

  my $Remote = $Self->{'server'}->{'peeraddr'};
  print LOG "INFO - Client : $Remote - Established connection.\n" if ($Verbosity);

  # Handy for debugging purposes. All 'values' of this self object.
  #my $ServerHash = $Self->{'server'};
  #foreach my $Key (keys %$ServerHash) {
  #  print "Key : '$Key' - Value : $ServerHash->{$Key}\r\n";
  #}

  my $PreviousAlarm = alarm($ConnectionTimeOut);
  my $Input;

  while (1) {

    # Wait on input, but with a TimeOut.
    eval {
      local $SIG{ALRM} = sub { die "TimeOut"; };
      $Input = <STDIN>;
      die "NoInput" unless $Input;
      alarm($ConnectionTimeOut);
    };

    if ( $@=~/TimeOut/i ) {
      $Self->Respond("-ERR Timed out.\r\n");
      return;
    } elsif ( $@=~/NoInput/i ) {
      $Self->Respond("-ERR No input.\r\n");
      return;
    }

    chomp($Input);
    $Input =~ s/\r$//;

    $Self->{'Input'} = $Input;
 
    print LOG "INFO - Client : $Remote - Handling input '$Input'.\n" if ($Verbosity);

    # Commands in the POP3 consist of a case-insensitive keyword, possibly
    # followed by one or more arguments.
    my ($Command,$Argument,$Argument2) = split(/ /,$Input);
    $Command   = "" unless defined $Command;
    $Argument  = "" unless defined $Argument;
    $Argument2 = "" unless defined $Argument2;

    if (!$Command) {
      $Self->PopCmdUnknown();
      next;
    }

    $Command =~ tr/a-z/A-Z/;  # Convert commands to uppercase

    # Handle the different potential POP commands.
    # Mostly by handing off to a sub.

    if ($Command eq "USER") {

      if (!defined($Argument)) {
        $Self->PopCmdUnknown();
        next;
      }

      if ($Self->{'LoggedIn'} == 1) {
        $Self->Respond("-ERR Already logged in.\r\n");
        next;
      }

      # An easy trick to circumvent problems with spaces in quoted strings like "Postvak IN" that get splitted.
      my $RestoredArgument = $Input;
      $RestoredArgument =~ s/USER\s+//i;
      $Self->{'Username'} = $RestoredArgument;
 
      $Self->Respond("+OK Password ?\r\n");
    }

    # We start action of retrieving on the "PASS" command.

    elsif ($Command eq "PASS") {

      if ($Self->{'LoggedIn'} == 1) {
        $Self->Respond("-ERR Already logged in.\r\n");
        next;
      }
      $Self->{'Password'} = $Argument;

      # All of the action starts with creating a GetLive object.
      my $GetLive         = GetLive->new();
      $Self->{'GetLive'}  = $GetLive;
      print LOG "INFO - Client : $Remote - GetLive serving with tmp in $GetLive->{'TmpDir'}\n" if ($Verbosity);

      # Now we parse the username (like jos@hotmail.com?folder=Sent&markread=0

      my $Username =  $Self->{'Username'};
      if ($Username !~ m/^([^@]+?)@/) {
        $Self->Respond("-ERR Username '$Self->{'Username'}' malformed ('Login').\r\n");
        return;
      }
      $Username = $';
      $GetLive->{'Login'} = $1;

      if ($Username !~ m/^([^\?]+?)(\?|$)/) {
        $Self->Respond("-ERR Username '$Self->{'Username'}' malformed ('Domain').\r\n");
        return;
      }
      $Username = $';
      $GetLive->{'Domain'} = $1;

      my @ParameterPairs = split(/&/,$Username);
      foreach my $ParameterPair (@ParameterPairs) {
        chomp($ParameterPair);
        if ($ParameterPair !~ m/(.+)=(.+)/) {
          $Self->Respond("-ERR Username '$Self->{'Username'}' malformed (ParameterPair '$ParameterPair').\r\n");
          return;
        }
        my $Key   = $1;
        my $Value = $2;
        $Value =~ s/"(.+?)"/$1/;
        if ($Key =~ m/folder/i) {
          $Self->{'FolderToProcess'} = $Value;
        } elsif ($Key =~ m/folderid/i) {
          $Self->{'FolderIdToProcess'} = $Value;
        } elsif ($Key =~ m/markread/i) {
          $Self->{'MarkRead'} = $Value;
        } elsif ($Key =~ m/keepmsgstatus/i) {
          ; # just accept as a dummy do nothing.
        } else {
          $Self->Respond("-ERR Username '$Self->{'Username'}' malformed ('$ParameterPair' : unknown key).\r\n");
          return;
        }
      }

      print LOG "INFO - Client : $Remote - ".
        "Username parsed to '$GetLive->{'Login'}' @ '$GetLive->{'Domain'}' - Folder : '$Self->{'FolderToProcess'}' - FolderId : '$Self->{'FolderIdToProcess'}'\n" if ($Verbosity);

      # Some other loose ends we have to deliver to GetLive object before it can do its job.
      $GetLive->{'Password'}    =  $Self->{'Password'};
      $GetLive->{'Strategy'}    =  "All";
      $GetLive->{'LogToStdout'} = 0; # Because in a server it would go to the client ...
      $GetLive->{'DieOnError'}  = 0; # Because in a server it stops the server ...

      # Try to login and obtain the folders. Success will be seen as a login.
      my $LoggedIn = $GetLive->Login();
      $Self->{'LoggedIn'} = $LoggedIn;

      # Return appropriate status if not logged in. And get the messages if correctly logged in.
      if (!$LoggedIn) {
        $Self->Respond("-ERR Login incorrect.\r\n");
        exit(0); # This closes (intentionally) the connection on a wrong password.
      } elsif ($LoggedIn) {
        my $NrFolders         = $GetLive->{'NrFolders'};
        my $FolderToProcess   = $Self->{'FolderToProcess'};
        my $FolderIdToProcess = $FolderToProcess ? 0 : $Self->{'FolderIdToProcess'};
        for (my $FolderIdx=0;$FolderIdx<$NrFolders;$FolderIdx++) {
          next if ($FolderToProcess && (lc($FolderToProcess) ne lc($GetLive->{'FolderNames'}[$FolderIdx])));
          next if ($FolderIdToProcess && ($GetLive->{'FolderIds'}[$FolderIdx] !~ m/^(0|-)*$FolderIdToProcess$/));
          $Self->{'FolderToProcessIdx'} = $FolderIdx;
          # JDLA hack. Drafts folder does not work, also not in real. Assuming 000-...-4 is the draft folder.
          next if ($GetLive->{'FolderIds'}[$FolderIdx] =~ m/^(0|-)*4$/);
          print LOG "INFO - Client : $Remote - Processing folder $GetLive->{'FolderNames'}[$FolderIdx].\n" if ($Verbosity);
          $GetLive->GetMessagesFromFolder($FolderIdx);
          my $NrMessages = $GetLive->{'NrMessages'};
          print LOG "INFO - Client : $Remote - $NrMessages Messages.\n" if ($Verbosity);
        }
        my $NrMessages = $GetLive->{'NrMessages'};
        # OK Logged in and number of messages known. Octet count is fake.
        $Self->Respond("+OK $NrMessages messages (1302 octets)\r\n");
      }
    }

    # AUTH

    elsif ($Command eq "AUTH") {
      $Self->Respond("+OK\r\n.\r\n");
    }

    # Unexisting FOLD extension

    elsif ($Command eq "FOLD") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdFold();
    }

    # STAT

    elsif ($Command eq "STAT") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdStat();
    }

    # LIST

    elsif ($Command eq "LIST") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdList($Argument);
    }

    # RETR

    elsif ($Command eq "RETR") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdRetr($Argument);
    }

    # TOP

    elsif ($Command eq "TOP") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdTop($Argument,$Argument2);
    }

    # DELE

    elsif ($Command eq "DELE") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdDele($Argument);
    }

    # NOOP

    elsif ($Command eq "NOOP") {
      $Self->Respond("+OK No operation\r\n");
    }

    # RSET

    elsif ($Command eq "RSET") {

      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdRset();
    }

    # QUIT

    elsif ($Command eq "QUIT") {
      $Self->PopCmdQuit();
    }

    # UIDL

    elsif ($Command eq "UIDL") {
      if ($Self->{'LoggedIn'} == 0) {
        $Self->PopCmdNotLoggedIn();
        next;
      }
      $Self->PopCmdUidl($Argument);
    }

    # CAPA

    elsif ($Command eq "CAPA") {
      $Self->Respond( "+OK Capability list follows\r\n".
             "TOP\r\n".
             "USER\r\n".
             "UIDL\r\n".
             "EXPIRE NEVER\r\n".
             ".\r\n");
    }

    # Unkown ???
 
    else {
      $Self->PopCmdUnknown();
    }

  }; # while(1) loop receiving commands.
  
  # Reinstating previous alarm.
  alarm($PreviousAlarm);
}

########################################################################################################################
# 
# Class method.
# POP3 command unknown.
# 
########################################################################################################################

sub PopCmdUnknown {
  my $Self   = shift;

  my $Input  = $Self->{'Input'} || "";

  $Self->Respond("-ERR Unknown command : '$Input'.\r\n");
}

########################################################################################################################
# 
# Class method.
# POP3 command DELE.
# 
########################################################################################################################

sub PopCmdDele {
  my $Self       = shift;
  my $MessageIdx = shift;

  $MessageIdx = 0 if (!defined $MessageIdx || $MessageIdx eq "");

  my $GetLive    = $Self->{'GetLive'};
  my $NrMessages = $GetLive->{'NrMessages'};

  if ($MessageIdx < 1 || $MessageIdx > $NrMessages) {
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n");
    return;
  }

  $GetLive->{'MessagesDeleted'}[$MessageIdx-1] = 1;

  $Self->Respond("+OK message $MessageIdx deleted.\r\n");
}                        

########################################################################################################################
# 
# Class method.
# POP3 command RSET.
# 
########################################################################################################################

sub PopCmdRset {
  my $Self       = shift;

  my $GetLive    = $Self->{'GetLive'};
  my $NrMessages = $GetLive->{'NrMessages'};

  # The only requirement on RSET is unmarking deleted.
  for (my $Message = 0; $Message < $NrMessages; $Message++) {
    $GetLive->{'MessagesDeleted'}[$Message] = 0;
  }

  $Self->Respond("+OK $NrMessages 1302\r\n");
}                        

 
########################################################################################################################
# 
# Class method.
# POP3 command LIST.
# 
########################################################################################################################

sub PopCmdList {
  my $Self       = shift;
  my $MessageIdx = shift;

  $MessageIdx = 0 if (!defined $MessageIdx || $MessageIdx eq "");

  my $GetLive    = $Self->{'GetLive'};
  my $NrMessages = $GetLive->{'NrMessages'};

  if ($MessageIdx == 0) {
    $Self->Respond("+OK $NrMessages messages (1302 octets)\r\n");
    for (my $i=0;$i<$NrMessages;$i++) {
      my $j = $i+1;
      $Self->Respond("$j 1302\r\n") unless $GetLive->{'MessagesDeleted'}[$i];
    }
    $Self->Respond(".\r\n");
  } else {
    $Self->Respond("+OK $MessageIdx 1302\r\n") unless $GetLive->{'MessagesDeleted'}[$MessageIdx-1];
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n") if $GetLive->{'MessagesDeleted'}[$MessageIdx-1];
  }
}
   
########################################################################################################################
# 
# Class method.
# POP3 command QUIT.
# 
########################################################################################################################

sub PopCmdQuit {
  my $Self       = shift;

  if (!$Self->{'LoggedIn'}) {
    $Self->Respond("+OK POP3 Quit.\r\n");
    exit(0);
  }

  my $Remote          = $Self->{'server'}->{'peeraddr'};
  my $GetLive         = $Self->{'GetLive'};
  my $SourceFolderIdx = $Self->{'FolderToProcessIdx'};
  my $NrMessages      = $GetLive->{'NrMessages'};
 
  # Effectively delete messages that are marked deleted.
  # MarkRead if asked so.
  for (my $Message = 0; $Message < $NrMessages; $Message++) {
    if ($GetLive->{'MessagesDeleted'}[$Message]) {
      $GetLive->DeleteMessage($Message);
      print LOG "INFO - Client : $Remote - DeleteMessage($Message)\n" if ($Verbosity);
    }
    if (not $GetLive->{'MessagesRead'}[$Message] && $Self->{'MarkRead'} && $GetLive->{'MessagesPopped'}[$Message]) {
      $GetLive->MarkRead($Message);
      print LOG "INFO - Client : $Remote - MarkRead($Message)\n" if ($Verbosity);
    }
  }

  $Self->Respond("+OK POP3 Quit.\r\n");
  exit(0);
}

########################################################################################################################
# 
# Class method.
# POP3 command RETR.
# 
########################################################################################################################

sub PopCmdRetr {
  my $Self       = shift;
  my $MessageIdx = shift;

  $MessageIdx = 0 if (!defined $MessageIdx || $MessageIdx eq "");

  my $GetLive    = $Self->{'GetLive'};
  my $NrMessages = $GetLive->{'NrMessages'};

  if ($MessageIdx < 1 || $MessageIdx > $NrMessages) {
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n");
    return;
  }

  if ($GetLive->{'MessagesDeleted'}[$MessageIdx-1]) {
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n");
    return;
  }

  $Self->Respond("+OK 1302 octets\r\n");
  my $Message = $GetLive->GetEmail($MessageIdx-1,$Self->{'FolderToProcess'});
  $Message =~ s/^\.$/\.\./g;     # Avoid . on its own. Replace by ..
  $Message =~ s/\n/\r\n/g;  # CR/LF endings.
  $Self->Respond($Message,1); # 1 suppresses log
  $Self->Respond(".\r\n");

  # Mark popped.
  $GetLive->{'MessagesPopped'}[$MessageIdx-1] = 1;
}

########################################################################################################################
# 
# Class method.
# POP3 command STAT.
# 
########################################################################################################################

sub PopCmdStat {
  my $Self       = shift;

  my $GetLive    = $Self->{'GetLive'};
  my $NrMessages = $GetLive->{'NrMessages'};
  $Self->Respond("+OK $NrMessages 1302\r\n");
}

########################################################################################################################
# 
# Class method.
# POP3 command TOP.
# 
########################################################################################################################

sub PopCmdTop {
  my $Self       = shift;
  my $MessageIdx = shift;
  my $Lines      = shift;

  $MessageIdx = 0  if (!defined $MessageIdx || $MessageIdx eq "");
  $Lines      = -1 if (!defined $Lines      || $Lines      eq "");

  my $GetLive    = $Self->{'GetLive'};
  my $NrMessages = $GetLive->{'NrMessages'};

  if ($Lines < 0) {
    $Self->PopCmdUnknown();
    return;
  }

  if ($MessageIdx < 1 || $MessageIdx > $NrMessages) {
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n");
    return;
  }

  if ($GetLive->{'MessagesDeleted'}[$MessageIdx-1]) {
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n");
    return;
  }

  $Self->Respond("+OK\r\n");
  my $Message = $GetLive->GetEmail($MessageIdx-1,$Self->{'FolderToProcess'});
  my $FoundNewLine = 0;
  my @Lines = split(/\n/,$Message,-1);
  pop(@Lines); # Last is always \n in a mail. Drop.
  foreach my $Line (@Lines) {
    $Line =~ s/^\.$/\.\./g;     # Avoid . on its own. Replace by ..
    $Self->Respond("$Line\r\n");
    $FoundNewLine |= ($Line eq "");
    last if ($FoundNewLine && ($Lines-- <= 0))
  }
  $Self->Respond(".\r\n");
}

########################################################################################################################
# 
# Class method.
# POP3 command UIDL.
# 
########################################################################################################################

sub PopCmdUidl {
  my $Self       = shift;
  my $MessageIdx = shift;

  $MessageIdx = 0  if (!defined $MessageIdx || $MessageIdx eq "");

  my $Remote     = $Self->{'server'}->{'peeraddr'};
  my $GetLive    = $Self->{'GetLive'};
  my $NrMessages = $GetLive->{'NrMessages'};

  if ($MessageIdx == 0) {
    $Self->Respond("+OK UIDL listing follows.\r\n");
    for (my $i=0;$i<$NrMessages;$i++) {
      my $j = $i+1;
      $Self->Respond("$j $GetLive->{'MessagesId'}[$i]\r\n") unless $GetLive->{'MessagesDeleted'}[$i];
    }
    $Self->Respond(".\r\n");
  } else {
    $Self->Respond("+OK $MessageIdx $GetLive->{'MessagesId'}[$MessageIdx-1]\r\n") unless $GetLive->{'MessagesDeleted'}[$MessageIdx-1];
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n") if $GetLive->{'MessagesDeleted'}[$MessageIdx-1];
  }
}

########################################################################################################################
# 
# Class method.
# Pseudo POP3 command FOLD.
# 
########################################################################################################################

sub PopCmdFold {
  my $Self = shift;

  my $GetLive   = $Self->{'GetLive'};
  my $NrFolders = $GetLive->{'NrFolders'};

  $Self->Respond("+OK Folders follow.\r\n");
  for (my $Folder=0;$Folder<$NrFolders;$Folder++) {
    $Self->Respond("$GetLive->{'FolderIds'}[$Folder] - $GetLive->{'FolderNames'}[$Folder]\r\n");
  }
  $Self->Respond(".\r\n");
}

########################################################################################################################
# 
# Class method.
# POP3 Error : not logged in.
# 
########################################################################################################################

sub PopCmdNotLoggedIn {
  my $Self   = shift;

  my $Input  = $Self->{'Input'};
  $Self->Respond("-ERR You are not logged in.\r\n");
}

########################################################################################################################
# 
# Class method.
# POP3 Give Response. We didn't use simple print to enable logging facility if needed.
# 
########################################################################################################################

sub Respond {
  my $Self        = shift;
  my $What        = shift;
  my $SuppressLog = shift;

  $SuppressLog = 0 if (!defined $SuppressLog || $SuppressLog eq "");

  binmode STDOUT; # Needed for avoiding protocol errors by \n -> \r\n issues f.i. in Windows.
  print $What;
  if (!$SuppressLog) {
    my $Remote = $Self->{'server'}->{'peeraddr'};
    print LOG "INFO - Client : $Remote - $What" if ($Verbosity);
  }
}

########################################################################################################################
# 
# Standard return for a correct package.
#
########################################################################################################################

1;

########################################################################################################################
# 
# Here starts the 'main' stuff and routines
#
########################################################################################################################

########################################################################################################################
# 
# Display some text.
# First parameter : text to be displayed.
# Then a number of named parameters that are optional. 
# See %args.
#
########################################################################################################################

sub Display($%) {
  my $Text = shift;
  my %Args = (MinVerbosity => 0,
              stderr       => 0,
              @_);

  # stderr messages are under no circumstances suppressed.
  if ($Args{'stderr'}) {
    print STDERR $Text;
    return;
  }

  # Filter out the ones for which the verbosity is too high.
  return if ($Args{'MinVerbosity'} > $Verbosity);

  # And finally print ;-)
  # Stdout is flushed immediate , not to miss error messages.
  my $WasSelected = select(STDOUT);
  $|=1;
  select($WasSelected);

  print STDOUT $Text;

  return;
}

########################################################################################################################
# 
# Display the introduction text.
# Text as argument, stderr as optional named argument to redirect to stderr.
#
########################################################################################################################

sub DisplayIntroText(%) {
  my %Args = (stderr => 0,
              MinVerbosity => 1,
              @_);
  my $Text = 
    "\n\n".
    "$ProgramName $Revision Copyright (C)2007-2010 Jos De Laender.\n".
    "$ProgramName comes with ABSOLUTELY NO WARRANTY.\n".
    "This is free software, and you are welcome to redistribute it\n".
    "under certain conditions; see the file License for details.\n".
    '$Name: Release_3_0 $' . "\n".
    '$Id: GetLive.pl,v 2.29 2013/04/02 20:44:18 jdla Exp $' . "\n".
    "Running at ".localtime(time)."\n\n";
  Display($Text,%Args);
}

########################################################################################################################
# 
# This is only called in error conditions. Output will go to stderr.
#
########################################################################################################################

sub DisplayUsageAndExit() {
  Display("Usage: $ProgramName --config-file ConfigFile [--verbosity -1..100]\n".
          "Usage: $ProgramName --port PortNumber [--verbosity 0..100]\n",
          stderr => 1);
  exit(1);
}

########################################################################################################################
# 
# Parse the command line
#
########################################################################################################################

sub ParseArgs() {
  my $ArgvAsString =  join(" ",@ARGV);

  # --config-file or --port is a mandatory argument.
  if ($ArgvAsString !~ m/--(config-file|port)\s+([\w\/\\~\.\-]+)/si) {
    DisplayUsageAndExit();
  }
  my $OrigArgvAsString = $ArgvAsString;
  $ArgvAsString = $` . $';   # The matched stuff removed.

  if ($OrigArgvAsString =~ m/--config-file\s+([\w\/\\~\.\-]+)/si) {
    $ConfigFile =  $1;
  } else {
    $ServerMode = 1;
  }

  # --verbosity is an optional argument.
  if ($ArgvAsString =~ m/--verbosity\s+(\d+)/si) {
    $Verbosity = $1;
    $ArgvAsString = $` . $'; # The matched stuff removed.
  }
  # Should have no other arguments.
  $ArgvAsString =~ s/\s//sg;
  if ($ArgvAsString ne "") {
    Display("Wrong command line arguments '$ArgvAsString'.\n",stderr => 1);
    DisplayUsageAndExit();
  }
}

########################################################################################################################
# 
# The 'main' program.
#
########################################################################################################################

DisplayIntroText();
ParseArgs();

if ($ServerMode) {
  # Open a log file and make it line buffered.
  my $LogFileName = File::Spec->tmpdir() . "/$ProgramName.$$.$^T.log";
  if ($Verbosity) {
    open (LOG,">$LogFileName") || die "Could not open '$LogFileName' : $!";
    my $OldFileHandle = select LOG;
    $| =1;
    select $OldFileHandle;
    print "INFO : Logging to $LogFileName\n\n";
  }

  # Start a POP3 server object and have it run. Never finishes.
  my $Server = PopLive->new();
  $Server->run();
  exit(0);
}

# Here goes the normal GetLive, but now via object.
my $GetLive = GetLive->new();

$GetLive->ParseConfig();
$GetLive->Login();
$GetLive->LoadDownloadedIds();

my $NrFolders   = $GetLive->{'NrFolders'};
my $SkipTrash   = $GetLive->{'SkipTrash'};
my $SkipJunk    = $GetLive->{'SkipJunk'};
my $UserName    = $GetLive->{'Login'};
my $Domain      = $GetLive->{'Domain'};

for (my $FolderIdx=0;$FolderIdx<$NrFolders;$FolderIdx++) {
  next if (scalar keys %FoldersToProcess and not exists $FoldersToProcess{lc $GetLive->{'FolderNames'}[$FolderIdx]});
  next if ($GetLive->{'FolderIds'}[$FolderIdx] eq $DraftsFolderId); # Doesn't fly in real either.
  next if ( ($SkipTrash =~ m/^Yes$/i) and ($GetLive->{'FolderIds'}[$FolderIdx] eq $TrashFolderId) );
  next if ( ($SkipJunk  =~ m/^Yes$/i) and ($GetLive->{'FolderIds'}[$FolderIdx] eq $JunkFolderId) );
  Display("\nProcessing folder '$GetLive->{'FolderNames'}[$FolderIdx]' for '$UserName\@$Domain'.\n", MinVerbosity => 1);
  $GetLive->GetMessagesFromFolder($FolderIdx);
  my $NrMessages = $GetLive->{'NrMessages'};
  Display("$NrMessages Messages.\n", MinVerbosity => 1);
  $GetLive->ProcessMessagesFromFolder($FolderIdx);  # FolderIdx just for name calculation.
  $GetLive->SaveDownloadedIds();
}

$GetLive->SaveDownloadedIds();

Display("All done.\n", MinVerbosity => 1);

exit(0);

########################################################################################################################
