getlive-2.4+cvs20120801.orig/0000755000175000017500000000000012006173400014705 5ustar tinchotinchogetlive-2.4+cvs20120801.orig/SmtpAuthForward.pl0000755000175000017500000000135211451146360020350 0ustar tinchotincho#!/usr/bin/perl -w use strict; use Net::SMTP_auth; my $Debug = 1; my $Message; my $Server = "smtp.servername.com"; my $Address = "foo\@foobar.be"; my $From = "foo\@foobar.be"; my $AuthType = "login"; my $UserName = "MyUserName"; # For the smtp server. my $Password = "MyPassword"; # For the smtp server. # Slurp the message and detect/remove the from. while (<>) { #if (m/^From (.*)$/) { # $From = $1; # next; #} $Message .= $_; } my $Smtp = Net::SMTP_auth->new(Host=>$Server,Debug=>$Debug) || die "Could not connect to SMTP server $Server : $!"; $Smtp->auth($AuthType,$UserName,$Password); $Smtp->mail($From); $Smtp->to($Address); $Smtp->data(); $Smtp->datasend($Message); $Smtp->dataend(); $Smtp->quit(); getlive-2.4+cvs20120801.orig/Manual0000755000175000017500000001670411466317475016105 0ustar tinchotinchoDescription =========== GetLive is a perl script that interfaces with your Hotmail Live account to retrieve your emails. It has 2 operating modes that I call "Classic" and "FreePops". In "Classic" mode it fetches mail from your Hotmail Live account. The mail is then presented to any filter (typically procmail) for further processing or dropping in a local mailbox. The downloading can be limited to unread mail or to message-ids that were not yet downloaded before. For the latter GetLive keeps track of all downloaded message-ids. Messages can be marked read or moved to a folder after being downloaded. In "FreePops" mode, GetLive starts up as a POP3 server listening on a port. Your email client would connect to it as it does to any POP3 server. Requests are however translated such that you effectively get your mails from your hotmail account. Special constructs are possible to interact with more than Inbox. Also in this case messages can be marked read on the hotmail account after being read. Usage in "Classic" mode ======================= ./GetLive --config-file ConfigFile [--verbosity N] - ConfigFile : Name or full path to a configuration file, with contents described hereafter. - verbosity : Optional argument, defining the verbosity of the diagnostic messages and taking following values : 0 : Silent 1 : Normal output (default value) 2 : Extensive output 10 : Debugging mode 100 : Heavy debugging mode The configuration file takes arguments of the form option = value. Empty lines or lines commented with # are possible. Following are the possible options : UserName = YourHotmailUserName (without @something) Password = YourHotmailPassword (so restrict the access rights to this configuration file. Domain = YourHotmailDomain (default 'hotmail.com', basically what's after the @ in your address) Proxy = ProxyServer if you're behind one. ProxyAuth = ProxyPassword if you're behind one with password. Downloaded = SomeFileName (it keeps track of all id's of messages that were fetched already) FetchOnlyUnread = Yes or No (default) If Yes only messages that are marked unread are fetched. Downloaded and FetchOnlyUnread are mutual exclusive. RetryLimit = N (default 2) : how many times to retry fetching a web page via curl (the webpage fetcher behind the scenes) CurlBin = CommandForCurl ('curl -k' by default). Processor = FilterCommand ('/usr/bin/procmail' by default). Any filtercommand taking the message in mbox format on its stdin and doing something with it. *) '/bin/cat - >> FetchedMail' might be another interesting one to drop directly in a mbox file. *) The distribution contains also SmtpForward.pl and SmtpAuthForward.pl. Those can be used as 'Processor' to forward the mails to an SMTP server. Folder = FolderName. Folders that need to be fetched. This option must be repeated for each folder you want to fetch. If this option is not present then all folders will be fetched. MarkRead = Yes or No (default) : whether the message will be marked read after being downloaded. By the way : it never gets marked in any way when not downloaded due to it being already in the Downloaded = SomeFileName (see higer) file. Delete = Yes or No (default) : whether the message will be deleted after being downloaded. By the way : it never gets deleted in any way when not downloaded due to it being already in the Downloaded = SomeFileName (see higer) file. MoveToFolder = FolderName or @FileName FolderName is the folder to which the message must be moved after being downloaded. By the way : it never gets moved in any way when not downloaded due to it being already in the Downloaded = SomeFileName (see higer) file. If this argument takes the form @FileName then the FolderName is taken from the contents of the file FileName. The idea is that f.i. a spamfilter can decide to what folder it must be moved. That spamfilter would be part of Processor = FilterCommand (see higher) and write a FolderName (for instance Junk if considered junk) to the file FileName. SkipTrash = Yes or No (default) : whether the message Trash folder will be handled (default) or not. Usage in "FreePops" mode ======================= ./GetLive --port PortNumber [--verbosity N] - verbosity : Optional argument, defining the verbosity of the diagnostic messages and taking following values : 0 : Silent 1 : Normal output (default value) 2 : Extensive output 10 : Debugging mode 100 : Heavy debugging mode - PortNumber : The port on which GetLive will listen to incoming connections (and that you thus should use in your client). Your email client should be configured as follows (one account per folder) : Server : localhost and port as defined in PortNumber above. User : username@hotmail.com[?Option=Value][&Option=Value][&Option=Value] ... with Option=Value pairs from : - folder="FolderName" : The folder as displayed in hotmail for the folder you want to interact with. Default : "Inbox". - folderid=N : the folderid you want to interact with : 1 : Inbox 2 : Trash 3 : Sent 5 : Junk - markread=1 : It will mark mails read on your hotmail account if you read them via the the Pop3 Server. - keepmsgstatus=1 : Dummy for FreePops compatibility. Does nothing. (remark GetLive doesn't touch statuses unless asked for) !Attention ! : "Leave on server" option does work. So if you choose in your email client not to leave messages on the server, they will be deleted in your hotmail account. Requirements ============ * UNIX/LINUX * Perl - Mine is v5.8.8, feedback on other working versions welcome. * curl - Mine is v7.15.5 feedback on other working versions welcome. - v7.15.4 reported working. * curl-ssl * Windows (WIN32) * Combination reported working : * ActiveState Perl v5.8.8 * curl.exe v7.10.3 from http://curl.haxx.se/ * openssl binaries libeay32.dll and libssl.dll version 0.9.7.d from http://curl.haxx.se/ * Cygwin under Windows * Probably as UNIX/LINUX - feedback welcome. Bugs, Questions, ... ==================== https://sourceforge.net/projects/getlive getlive-2.4+cvs20120801.orig/License0000755000175000017500000004311011451146360016224 0ustar tinchotincho GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. getlive-2.4+cvs20120801.orig/ChangeLog0000644000175000017500000001527511604404201016470 0ustar tinchotinchoRelease 2.4 - CVS tag : Release_2_4 =================================== *) Corrected bug 3345510 Release 2.3 - CVS tag : Release_2_3 =================================== *) A small password larger than 16 characters bug corrected. *) Windows debugging. Release 2.2 - CVS tag : Release_2_2 =================================== *) For windows : removed warnings on the eval for locale. *) Command length check removed. Due to FreePops this can be longer than 40. *) keepmsgstatus accepted as dummy in 'FreePops' mode. Release 2.1 - CVS tag : Release_2_1 =================================== *) Overhaul for supporting FreePops users. Should be transparant for classic users. *) The addition of k_walter as discussed in https://sourceforge.net/projects/getlive/forums/forum/686670/topic/3865433 *) Bug 2312859 : Password issue. Fix from Omar Ramadan included. Release 2.0 (Beta) - CVS tag : Release_2_0 ========================================== *) Overhaul for August 2010 Changes. Basically a simplification as we now use the 'mobile' site for most of the actions, but the downloading. Release 0.59 (Beta) - CVS tag : Release_0_59 ============================================ *) Some code cleanup and simplification. *) Change in login procedure by yet another Microsoft change. Mode 201003 Release 0.58 (Beta) - CVS tag : Release_0_58 ============================================ *) It seems Microsoft is changing again. Mode 201002 added therefore. *) BreakOnAlreadyDownloaded option *) Bug 2862786 (MarkRead/MoveToFolder) solved by Aaron Stemen patch. *) Bug 2862230 : Hotmail changes at 19/9/2009 *) Bug 2595501 : Hotmail changes at 14/2/2009 *) Bug 2560285 : Hotmail changes in 2/2009 *) Bug 2208443 : Hotmail changes in 10/2008 *) Bug 2017097 : Messages retrieved multiple times (SkipTrash option) Release 0.57 (Beta) - CVS tag : Release_0_57 ============================================ *) Bug 1962937 : Could not correctly parse the messages table (after MS started changing things again around 1/7/2008) *) Bug 1871076 : GetLive died with Unexpected HTTP status : '100' *) Bug 1875392 : Login on msn.com does fail ! *) Bug 1881842 : Does not handle folder names containing non-ASCII characters *) Bug 1909801 : Locale does not work in Windows. Release 0.56 (Beta) - CVS tag : Release_0_56 ============================================ *) Bug 1778938 : Error when using SmtpForward.pl *) Bug 1796107 : HTTP/500 etc should be catched. *) Feature 1792688 : Option to get a count of unread messages only *) Feature 1778902 : deletewhenread=yes option Release 0.55 (Beta) - CVS tag : Release_0_55 ============================================ *) Bug 830063 : Doesn't work anymore on some accounts. Hotmail page changed by MS. Release 0.54 (Beta) - CVS tag : Release_0_54 ============================================ *) Bug 1784876 : Command line parsing error. *) Bug 1789899 : Unable to Download. Release 0.53 (Beta) - CVS tag : Release_0_53 ============================================ *) Bug 1780285 : MARK READ Release 0.52 (Beta) - CVS tag : Release_0_52 ============================================ *) Bug 1779371 : Manageforlderslight error Release 0.51 (Beta) - CVS tag : Release_0_51 ============================================ *) Bug 1779788 : Some Accounts do not work. Release 0.50 (Beta) - CVS tag : Release_0_50 ============================================ *) Revamping to catch up with MS changing the login to live login. From now on only supports 'Live' boxes. Please convert old ones. It's lossless. Release 0.12 (Beta) - CVS tag : Release_0_12 ============================================ *) Bug 1774546 (second part, because in fact two unrelated bugs were entered into the same) : Live or dead: Could not find expected url (After change of interface by MS) Release 0.11 (Beta) - CVS tag : Release_0_11 ============================================ *) Bug 1774546 : Live or dead: Could not find expected url. (After change of interface by MS) Release 0.10 (Beta) - CVS tag : Release_0_10 ============================================ *) Changed Curl quoting to support Windows (thx to 'gharkink'). *) Adapted SmtpForward.pl (also thx to 'gharkink'). *) Added alternate SmtpAuthForward.pl (thx to 'runemaagensen'). *) Update manual with above (and the info on working versions) Release 0.9 (Beta) - CVS tag : Release_0_9 ========================================== *) Bug 1763128 : msn.com problems : See submitted patch 1758859 *) Inclusion of sample SmtpForward.pl in the distribution. Release 0.8 (Beta) - CVS tag : Release_0_8 ========================================== *) Bug 1742447 : Could not find expected url. (After change of interface by MS) *) Bug 1742493 : GetLive doesn't die on wrong 'MailProcessor'. Release 0.7 (Beta) - CVS tag : Release_0_7 ========================================== *) Bug 1739263 : --verbosity 0 should be silent. *) Request 1724728 : only fetch unread messages w/o id file Release 0.6 (Beta) - CVS tag : Release_0_6 ========================================== *) Bug 1722346 : MoveToFolder : sometimes read , sometimes not read. This one is now definitely solved , as well for Live as for 'Dead'. Messages will appear in the moved to box with the status as where it was coming from. Release 0.5 (Beta) - CVS tag : Release_0_5 ========================================== *) MoveToFolder now possible on downloading. *) MarkRead now possible on downloading. Release 0.4 (Beta) - CVS tag : Release_0_4 ========================================== *) Request 1721287 : Folder selection *) Bug 1719819 : Improve error message if Downloaded not specified. *) After the problem of Alex [dahaas] in which gotmail (the predecessor of GetLive) was not able to correctly load his account, an overhaul was made for correcting the counting of the messages per folder and for detection of the correct NextPage url (page=n&wo=...) in his case. Confirmed working for him and no regression for me. *) Support 1717590 : error message => Classic named Dead now. *) Support 1717590 : error message => Improved error message. *) Bug 1714417 : execution fails if the config file name contains a dot *) Bug 1713304 : Strange characters in 'Processing folder Verwijderd'. Release 0.3 (Beta) - CVS tag : Release_0_3 ========================================== *) Bug 1712959 : GetLive chokes on hotmail folders with 'Concepts' in it. *) Bug 1712958 : File with Ids incompatible between gotmail and GetLive. Release 0.2 (Beta) - CVS tag : Release_0_2 ========================================== *) Corrected problem with fetching unread mail in Classic mode. *) Corrected several problems with detection of number of mails in a folder. *) Some output shifted to different verbosity. getlive-2.4+cvs20120801.orig/SmtpForward.pl0000755000175000017500000000075211451146360017531 0ustar tinchotincho#!/usr/bin/perl -w use strict; use Net::SMTP; my $Message; my $Server = "smtp.servername.com"; my $Address = "foo\@foobar.be"; my $From = "foo\@foobar.be"; # Slurp the message and detect/remove the from. while (<>) { if (m/^From:(.*)$/) { $From = $1; next; } $Message .= $_; } my $Mail = Net::SMTP->new($Server) || die "Could not connect to SMTP server $Server : $!"; $Mail->mail($From); $Mail->recipient($Address); $Mail->data($Message); $Mail->quit(); getlive-2.4+cvs20120801.orig/GetLive.pl0000755000175000017500000022217712006032211016611 0ustar tinchotincho#!/usr/bin/perl -w ######################################################################################################################## # # GetLive - perl script to get mail from hotmail (live) mailboxes. # # $Id: GetLive.pl,v 2.19 2012/07/31 19:39:53 jdla Exp $ # $Name: $ # # Copyright (C) 2007-2012 Jos De Laender # # 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 File::Spec; use URI::Escape; ######################################################################################################################## # # XXX # XXX This is inserted to cope with French characters in the folder names. # XXX Not too sure about. It works on my LANG=nl_BE.UTF-8 box, but I'm afraid it may screw up other boxes ... # XXX In my case also use encoding("UTF-8") worked (it's my locale after all). # XXX # ######################################################################################################################## eval "local $^W = 0; use encoding(\":locale\");"; ######################################################################################################################## # # Global constants and variables. # ######################################################################################################################## my $ProgramName = "GetLive"; my $Revision = '$Revision: 2.19 $'; # Meant for RCS. # Constants of configuration. my $Proxy = ""; my $ProxyAuth = ""; my $CurlCommand = 'curl -k'; my $Verbosity = 1; # 0:Silent; 1:Normal; 2:Verbose; 10:debug; 100:heavy debug my $RetryLimit = 2; my $ConfigFile = ""; my $ServerMode = 0; my $TrashFolderId = "00000000-0000-0000-0000-000000000002"; # 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. ######################################################################################################################## # # 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; ######################################################################################################################## # # GetLive object creation. # ######################################################################################################################## sub new { my $Class = shift; my $Self = {}; $Self->{'Login'} = ""; $Self->{'Domain'} = "hotmail.com"; $Self->{'Password'} = ""; $Self->{'MailProcessor'} = '/usr/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->{'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). # Files in a temporary directory. Must be per object. my $TmpDir = File::Spec->tmpdir() . "/$ProgramName.$$.$^T"; # Don't allow others to read our temp files umask(077); # The temporary directory creation. mkdir($TmpDir) || die "Could not create $TmpDir : $!."; $Self->{'TmpDir'} = $TmpDir; $Self->{'TmpCurlHeadersFile'} = "$TmpDir/Headers"; $Self->{'TmpCookiesFile'} = "$TmpDir/Cookies"; $Self->{'TmpFormDataFile'} = "$TmpDir/Form"; $Self->{'TmpCurlStderrFile'} = "$TmpDir/CurlStderr"; $Self->{'TmpCurlStdoutFile'} = "$TmpDir/CurlStdout"; $Self->{'TmpCurlTraceFile'} = "$TmpDir/CurlTrace"; $Self->{'TmpLogFile'} = "$TmpDir/Log"; # Messages retrieved from a folder. $Self->{'NrMessages'} = 0; # Various variables. $Self->{'BaseUrl'} = ""; # The one in the logged in screen used for fetching folders. $Self->{'NParameter'} = ""; $Self->{'FolderIds'} = (); # The Ids found for the different folders. $Self->{'FolderNames'} = (); # The names found for the different folders. $Self->{'FolderNrMessages'} = (); # The number of messages found for the different folders. $Self->{'NrFolders'} = 0; # The number of folders found. $Self->{'CurlRun'} = 0; # Increased with each Curl run. Basically for debug reasons. $Self->{'DieOnError'} = 1; # Might be put on 0 for server application. $Self->{'LogToStdout'} = 1; # Might be put on 0 for server application. my $LogFileHandle; my $LogFileName = $Self->{'TmpLogFile'}; 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 () { 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; } elsif ($Option =~ m/^ProxyAuth$/i) { $ProxyAuth = $OptionValue; } elsif ($Option =~ m/^Downloaded$/i) { $Self->{'DownloadedIdsFile'} = $OptionValue; if ($OptionValue) { $Self->{'Strategy'} = "NotDownloaded"; } } elsif ($Option =~ m/^RetryLimit$/i) { $RetryLimit = $OptionValue; } elsif ($Option =~ m/^Processor$/i) { $Self->{'MailProcessor'} = $OptionValue; } elsif ($Option =~ m/^CurlBin$/i) { $CurlCommand = $OptionValue; } 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/^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'}; # stderr messages are under no circumstances suppressed. if ($Args{'stderr'}) { if ($Verbosity) { print $LogFileHandle $Text; } print $Text if $LogToStdout; die $Text if $DieOnError; 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. if ($Verbosity) { my $WasSelected = select($LogFileHandle); $|=1; select($WasSelected); print $LogFileHandle $Text; } print $Text if $LogToStdout; return; } ######################################################################################################################## # # Class method. # Get a html page, basically via curl. # Returns the page as one big string. # Returns a second string with the latest url. # The parameters should be reasonably clear. FollowForward will follow a redirection. # ######################################################################################################################## sub GetPage { my $Self = shift; my %Args = (Url => "", CurlDataArg => "", FollowForward => 0, @_); my $Url = $Args{'Url'}; my $CurlDataArg = $Args{'CurlDataArg'}; my $FollowForward = $Args{'FollowForward'}; my $TmpCurlHeadersFile = $Self->{'TmpCurlHeadersFile'}; my $TmpCookiesFile = $Self->{'TmpCookiesFile'}; my $TmpFormDataFile = $Self->{'TmpFormDataFile'}; my $TmpCurlStderrFile = $Self->{'TmpCurlStderrFile'}; my $TmpCurlStdoutFile = $Self->{'TmpCurlStdoutFile'}; my $TmpCurlTraceFile = $Self->{'TmpCurlTraceFile'}; my $CurlRun = $Self->{'CurlRun'}; $CurlRun++; $Self->{'CurlRun'} = $CurlRun; my $OptionsToCurl = ""; if ($Proxy) { $OptionsToCurl .= "--proxy $Proxy "; } if ($ProxyAuth) { $OptionsToCurl .= "--proxy-user $ProxyAuth "; } # The files with the Cookies. $OptionsToCurl .= "-b $TmpCookiesFile -c $TmpCookiesFile "; if ($CurlDataArg ne "") { $OptionsToCurl .= "--data \"$CurlDataArg\" "; } # Curl is put silent (but with error output) # when not interactive or low verbosity. if ( (not -t STDOUT) || ($Verbosity <= 1) ) { $OptionsToCurl .= "-s -S " } if ($Verbosity > 9) { $OptionsToCurl .= "-v --trace $TmpCurlTraceFile.$CurlRun" } # JDLA curl outputs info via stderr. Catched in file and appended # to stdout output in debug mode. my $CommandLine = "$CurlCommand --stderr $TmpCurlStderrFile.$CurlRun \"$Url\" " . "$OptionsToCurl -i -m 600 -D $TmpCurlHeadersFile.$CurlRun " . "-A \"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.5) Gecko/20061201 Firefox/2.0.0.5 (Ubuntu-feisty)\""; $Self->Log("Curl run $CurlRun.\nCommandLine : '$CommandLine'.\n", MinVerbosity => 10); my $NrTries = 0; my @CurlOutput = (); while (!@CurlOutput && $NrTries++ < $RetryLimit) { $Self->Log("Trying [$NrTries/$RetryLimit].\n",MinVerbosity => 2); $CommandLine =~ m/(.*)/; $CommandLine = $1; @CurlOutput = `$CommandLine`; # Copy output. Only in very high debug levels. # We have it in file anyway. if ($Verbosity > 99) { # The if around makes it a bit more efficient over the loop. foreach my $Line (@CurlOutput) { $Self->Log($Line,MinVerbosity => 100); } } my $Success = open (CURL_STDERR,"$TmpCurlStderrFile.$CurlRun"); if (!$Success) { $Self->Log(__LINE__ . ": Could not open $TmpCurlStderrFile.$CurlRun : $!.\n",stderr => 1); return("",""); } # Copy curl stderr. $Self->Log("\nstderr of curl :\n",MinVerbosity => 10); while() { my $Line = $_; my $PasswordToBlank = uri_escape($Self->{'Password'},"^A-Za-z"); $Line =~ s/$PasswordToBlank/YouThinkThisIsThePassword/g; $Self->Log("$Line",MinVerbosity => 10); } close(CURL_STDERR); $Self->Log("\nEnd of stderr of curl.\n",MinVerbosity => 10); # Some checking on the HTTP response to see if there's no 5** Server errror or 4** Client error. # In general : 2** is Success, 3** is Redirection, 4** is Client Error and 5** is Server Error. if ($CurlOutput[0] !~ m/HTTP[^ ]+ (\d{3})/) { $Self->Log(__LINE__ . ": Irregular HTTP header '$CurlOutput[0]' received.\n",stderr => 1); return ("",""); } my $HttpCode = $1; if ($HttpCode =~ m/(1|2|3)\d{2}/) { $Self->Log("Http Status OK : $HttpCode.\n",MinVerbosity=>2); } elsif ($HttpCode =~m/4\d{2}/) { $Self->Log("Http Client Error : $HttpCode.\n",MinVerbosity=>2); @CurlOutput = (); # Force retry. } elsif ($HttpCode =~m/5\d{2}/) { $Self->Log("Http Server Error : $HttpCode.\n",MinVerbosity=>2); @CurlOutput = (); # Force retry. } else { $Self->Log(__LINE__ . ": Unexpected HTTP status : '$HttpCode'.\n",stderr => 1); return ("",""); } } # In debug mode (Verbosity>9) we copy the output to a file. if ($Verbosity > 9) { my $Success = open (CURL_STDOUT,">$TmpCurlStdoutFile.$CurlRun"); if (!$Success) { $Self->Log(__LINE__ . ": Could not open $TmpCurlStdoutFile.$CurlRun : $!.\n",stderr => 1); return ("",""); } print CURL_STDOUT @CurlOutput; close(CURL_STDOUT); } if (!@CurlOutput && $NrTries > $RetryLimit) { $Self->Log(__LINE__ . ": Curl run $CurlRun.\nCommandLine : '$CommandLine'.\n",stderr => 1); $Self->Log(__LINE__ . ": An error was encountered getting the page.\n",stderr => 1); return ("",""); } # Redirect search in headers. my $Redirection = ""; my $Success = open (CURL_HEADERS,"$TmpCurlHeadersFile.$CurlRun"); if (!$Success) { $Self->Log(__LINE__ . ": Could not open $TmpCurlHeadersFile.$CurlRun : $!.\n",stderr => 1); return("",""); } while () { if (m/^Location: (\S+)\s/) { $Redirection = $1; # XXX JDLA #if ($Redirection =~ m/BrowserSupport/i){ # $Redirection = ""; #} $Self->Log("Following $Redirection.\n",MinVerbosity => 2); last; } } close(CURL_HEADERS); # If we have been asked to follow Location: headers if ($FollowForward) { if ($Redirection ne "") { if ($Redirection !~ m/^http.*/i){ if ($Url =~ m/(https?:\/\/[^\/]+)\//i) { $Redirection = $1 . $Redirection; } } die "URL : $Url Redirection : $Redirection" unless ($Redirection =~ m/^http.*/i); $Self->Log("Following redirect to $Redirection.\n",MinVerbosity => 2); return $Self->GetPage(Url => $Redirection,FollowForward => $FollowForward); } } return (join("",@CurlOutput),$Url); } ######################################################################################################################## # # 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; my $BaseUrl = $Self->{'BaseUrl'}; $Self->Log("Getting hotmail index loginpage.\n", MinVerbosity =>2); my ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => "http://mail.live.com/",FollowForward => 1); # 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. # XXX JDLA It can turnout that after all we don't use anything of it, but reconstruct. # Then one can speed up by leaving this JSPageAsString out. my $BaseHref = ""; if ($LoginPageAsString =~ m/Log("Found base href to be '$BaseHref'.\n",MinVerbosity => 10); } my @JavaScriptHrefs; my $JavaScriptHref = ""; while ($LoginPageAsString =~ m/Log("Found javascript href to be '$JavaScriptHref'.\n",MinVerbosity => 10); } if (!$JavaScriptHref) { $Self->Log(__LINE__ . ": 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",FollowForward => 1); # Append the JS stuff into our page. $LoginPageAsString .= $JSPageAsString; } # We would look to : # # function FormStart(){var s=" #
"; # s+=WL_HiddenField("idsbho","IDSBHO","1"); # s+=WL_HiddenField("PwdPad","i0340",null); # s+=WL_HiddenField("LoginOptions","LoginOptions","3"); # s+=WL_HiddenField("CS","CS",null); # s+=WL_HiddenField("FedState","FedState",null); # s+=WL_HiddenField("PPSX","i0326",g_sRBlob); # s+=WL_HiddenField("type","type",null);return s;} # # The WL_HiddenField = 'name','identifier','value'. Identifier unimportant. # Thanks to Michael Kelly for this patch at July 29, 2012. my %Fields = (); my $Domain = $Self->{'Domain'}; my ($LoginUrl) = $LoginPageAsString =~ m{'(https://login.live.com/ppsecure/post.srf[^']*)'}; if ($LoginPageAsString !~ m/h:'(P[^']*)'/s) { $Self->Log(__LINE__ . ": Page doesn't contain PPSX in the expected place.\n",stderr => 1); return 0; } # End of Michael Kelly patch. $Self->Log("PPSX detected as '$1'.\n", MinVerbosity => 10 ); $Fields{"PPSX"} = $1; # 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(__LINE__ . "Page doesn't contain input field PPFT as expected.\n",stderr => 1); return 0; } $Self->Log("PPFT detected : '$2'.\n",MinVerbosity => 10 ); $Fields{"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 ... $Fields{"type"} = "11"; $Fields{"NewUser"} = "1"; $Fields{"i1"} = "0"; $Fields{"i2"} = "0"; # Omar Ramadan Bug-fix for passwords containing '=' my $Password = $Self->{'Password'}; my @PassExplode = split("=", $Password); $Password = $PassExplode[0]; # Hope the password padding still works ... my $Padding = "BovenGentRijstEenzaamEnGrijsHetOudBelfort"; my $PwdPad = substr( $Padding, 0, length($Padding)-length($Password) ); $Self->Log("PwdPad constructed : '$PwdPad'.\n",MinVerbosity => 10 ); $Fields{"PwdPad"} = $PwdPad; #login and password. my $Login = $Self->{'Login'}; $Fields{"login"} = uri_escape($Login . '@' . $Domain, "^A-Za-z"); $Fields{"passwd"} = uri_escape($Password, "^A-Za-z"); # Construct the form with above in a temporary file. my $TmpFormDataFile = $Self->{'TmpFormDataFile'}; my $Success = open (FORMFILE,">$TmpFormDataFile"); if (!$Success) { $Self->Log(__LINE__ . ": Could not open $TmpFormDataFile : $!.\n",stderr => 1); return 0; } my $HaveAlreadyArgument = 0; foreach my $Key (keys %Fields) { if ($HaveAlreadyArgument) { print FORMFILE "\&"; } print FORMFILE "$Key=$Fields{$Key}"; $HaveAlreadyArgument = 1; } close FORMFILE; # Second step of login. The form is provided as a curl --data argumetn. $Self->Log("Logging in.\n",MinVerbosity => 1); ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => $LoginUrl,CurlDataArg => "\@$TmpFormDataFile",FollowForward => 1); # Eric 'EDLiN303' patch. if ($LoginPageAsString =~ m/form name="([^"]+)"[^>]*action="([^"]+)".*?id="t"\s*value="([^"]+)"/i) { my ($formurl,$formt) = ($2,$3); my $Success = open (FORMFILE,">$TmpFormDataFile"); if (!$Success) { $Self->Log(__LINE__ . ": Could not open $TmpFormDataFile : $!.\n",stderr => 1); return 0; } print FORMFILE "t=$formt"; close FORMFILE; ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => $formurl,CurlDataArg => "\@$TmpFormDataFile",FollowForward => 1); my $HotUrl = "http://www.hotmail.com"; ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => $HotUrl,FollowForward => 1); } # End Eric 'EDLiN303' patch. if ($LoginPageAsString !~ m/window\.location\.replace\(\"(.*)\"\);/i && $LoginPageAsString !~ m/Log(__LINE__ . ": Hotmail's login structure has changed! (redirloc).\n",stderr => 1); return 0; } $LoginUrl = $1; $Self->Log("LoginUrl 2 : '$LoginUrl'.\n",MinVerbosity => 10); # Following the redirect : Third step of login. $Self->Log("Following redirect.\n",MinVerbosity => 2); if( $LoginUrl =~ m/gfx2.hotmail.com\/mail\/uxp\/w4\/m3\/pr16\/h\/s4.png/ ) { $LoginUrl=$BaseUrl."?rru=inbox" ; } # libcurl error bypass on secure cookie WLSSC $Self->MakeCookieSecure("WLSSC"); ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => $LoginUrl,FollowForward => 1); $LoginUrl = $GetPageUrl; if ($LoginPageAsString =~ m/Log(__LINE __ . ": I suspect there to be a NAG screen. Please switch it off.\n",stderr => 1); return 0; }; if ($LoginUrl !~ m/(http[s]?:\/\/([^\/]+\/)+)/) { $Self->Log(__LINE__ . ": Could not detect BaseUrl.\n",stderr => 1); return 0; } $BaseUrl = $1; $Self->{'BaseUrl'} = $BaseUrl; if ($LoginUrl !~ m/(n=\d+)/) { $Self->Log(__LINE__ . ": Could not detect NParameter.\n",stderr => 1); return 0; } my $NParameter = $1; $LoginUrl = $BaseUrl."InboxLight.aspx?".$NParameter; ($LoginPageAsString,$GetPageUrl) = $Self->GetPage(Url => $LoginUrl,FollowForward => 1); $Self->Log("LoginUrl : $LoginUrl.\n",MinVerbosity => 10); $Self->Log("BaseUrl : $BaseUrl.\n",MinVerbosity => 10); $Self->Log("NParameter : $NParameter.\n",MinVerbosity => 10); # 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/href=\"ManageFoldersLight.aspx/) { $LoggedIn = 1; } elsif ($LoginPageAsString =~ m/MSNPlatform\/browsercompat.js/) { $LoggedIn = 1; } if (!$LoggedIn) { $Self->Log(__LINE__ . ": Could not log in. Maybe structure has changes or was not foreseen.\n",stderr => 1); return 0; } $BaseUrl .= "mail/" if ($BaseUrl !~ m/mail\/$/); $Self->{'BaseUrl'} = $BaseUrl; if ($LoginPageAsString =~ m/ManageFoldersLight\.aspx\?(n=\d+)/) { if ($1 ne $NParameter) { $Self->Log(__LINE__ . ": Expected change of NParameter from '$NParameter' to '$1'.\n",MinVerbosity => 10); } $NParameter = $1; $Self->{'NParameter'} = $NParameter; } if (!$NParameter) { $Self->Log(__LINE__ . ": Could not retrieve 'NParameter'.\n",stderr => 1); return 0; } $Self->Log("Got MainPage.\n",MinVerbosity => 1); return 1; } ######################################################################################################################## # # Class method. # Get a list of the folders we have to deal with and parse them one by one. # Return 1 on success, 0 on failure. # ######################################################################################################################## sub GetFolders { my $Self = shift; my $BaseUrl = $Self->{'BaseUrl'}; my $NParameter = $Self->{'NParameter'}; my ($FolderPageAsString,$GetPageUrl) = $Self->GetPage(Url => "${BaseUrl}ManageFoldersLight.aspx?$NParameter", FollowForward => 1); # Scan the line for all folders, their href and title. # NrFolders on the fly; my @FolderNames = (); my @FolderIds = (); my $NrFolders = 0; # Peel out the "ManageFoldersTable" table if ($FolderPageAsString !~ m/]*?>/i) { die "Could not detect ManageFoldersTable."; } $FolderPageAsString = $'; while ($FolderPageAsString =~ m//sgc) { my $FolderStuff = $&; # Part of a html table with all info for this folder. # do not die on this non match as the end of the table summary is just like this. next if ($FolderStuff !~ m/]*?>(.*?)<\/a>/gc); my $FolderHref = $1; my $Name = decode_entities($2); my $Nr = 0; if ($Name =~ m/([^\(]+)\(\s*(\d+)\s*\)/) { $Name = $1; $Nr = $2; } $Name =~ s/\s+$//; $Name =~ s/^\s+//; $FolderNames[$NrFolders] = $Name; if ( $FolderHref !~ m/fid=([^&]*)/ ) { die "Could not detect FolderId."; } $FolderIds[$NrFolders] = $1; $Self->Log( "Folder $NrFolders - $FolderIds[$NrFolders] - $FolderNames[$NrFolders].\n", MinVerbosity => 10); $NrFolders++; } if (!$NrFolders) { $Self->Log(__LINE__ . ": No folders detected. Likely the page structure has changed.\n",stderr => 1); return 0; } $Self->{'FolderNames'} = \@FolderNames; $Self->{'FolderIds'} = \@FolderIds; $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 $FolderNames = $Self->{'FolderNames'}; my $FolderIds = $Self->{'FolderIds'}; my $NParameter = $Self->{'NParameter'}; my $FolderName = $FolderNames->[$FolderIdx]; my $FolderId = $FolderIds->[$FolderIdx]; $Self->Log("Loading folder '$FolderName'.\n",MinVerbosity => 1); my $BaseUrl = $Self->{'BaseUrl'}; my $Page = 0; my $StillPageToGo = 1; my $CurrentPage = 1; my $PageAsString; my $GetPageUrl; my $PageUrl = $BaseUrl."InboxLight.aspx?$NParameter&fid=$FolderId"; # Reinitialize the global variable back to 0. my $NrMessages = 0; my @MessagesRead = (); my @MessagesFrom = (); my @MessagesSubject = (); my @MessagesId = (); my @MessagesDeleted = (); # In support of pop server. Marking deleted. my @MessagesPopped = (); # In support of pop server. Marking popped. my $SequenceOfDownloaded = 0; my $MessagesToScan = -1; my $MessagesScanned = 0; while ($StillPageToGo) { $Page++; $StillPageToGo = 0; ($PageAsString,$GetPageUrl) = $Self->GetPage(Url => $PageUrl,FollowForward => 1); $Self->Log("Handling page $Page.\n",MinVerbosity => 2); # Search and check on total number messages if ($PageAsString !~ m/
(\d*)/si) { $Self->Log(__LINE__ . ": Did not find 'mlRange'\n",stderr => 1); return; } if ($MessagesToScan != -1 && $1 != $MessagesToScan) { $Self->Log(__LINE__ . ": Inconsistent MessagesToScan\n",stderr => 1); return; } $MessagesToScan = $1; if ($PageAsString !~ m//si) { # Either there are no messages or we have an error. if ($PageAsString !~ m/Log(__LINE__ . ": Did not find 'InboxTable' nor 'NoMsgs'\n",stderr => 1); return; } last; # We just happen to be empty. } $PageAsString = $& . $'; # Everything from .*?<\/tr>)/sig) { my $OneMessageTable = $1; my $ReadIndicator = $2; $MessageId = uc($3); $MessagesId[$NrMessages] = $MessageId; my $Read = 1; if ($ReadIndicator =~ m/mlUnrd/) { $Read = 0; } $MessagesRead[$NrMessages] = $Read; if ($OneMessageTable !~ m/
.*?<\/span>/si) { $Self->Log(__LINE__ . ": Did not find 'Fm'\n",stderr => 1); return } my $From = decode_entities($1); $MessagesFrom[$NrMessages] = $From; if ($OneMessageTable !~ m/(.*?)<\/a>/si) { $Self->Log(__LINE__ . ": Did not find 'Sb'\n",stderr => 1); return; } my $Subject = decode_entities($1); $MessagesSubject[$NrMessages] = $Subject; # Mark undeleted/Unpopped. In support of pop server. $MessagesDeleted[$NrMessages] = 0; $MessagesPopped[$NrMessages] = 0; my $Downloaded = 0; if ($Self->{'MessagesDownloaded'}->{$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 = $BaseUrl; $PageUrl .= "InboxLight.aspx?$NParameter&fid=$FolderId&pdir=NextPage&paid=$MessageId&pidx=$CurrentPage"; } } $Self->{'NrMessages'} = $NrMessages; $Self->{'MessagesRead'} = \@MessagesRead; $Self->{'MessagesFrom'} = \@MessagesFrom; $Self->{'MessagesSubject'} = \@MessagesSubject; $Self->{'MessagesId'} = \@MessagesId; $Self->{'MessagesDeleted'} = \@MessagesDeleted; $Self->{'MessagesPopped'} = \@MessagesPopped; } ######################################################################################################################## # # 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 = ) { 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 $FolderNames = $Self->{'FolderNames'}; my $NrMessages = $Self->{'NrMessages'}; my $Strategy = $Self->{'Strategy'}; my $MessagesRead = $Self->{'MessagesRead'}; my $MessagesFrom = $Self->{'MessagesFrom'}; my $MessagesSubject = $Self->{'MessagesSubject'}; my $MessagesId = $Self->{'MessagesId'}; my $MarkRead = $Self->{'MarkRead'}; my $MoveToFolder = $Self->{'MoveToFolder'}; my $Delete = $Self->{'Delete'}; my $FolderName = $FolderNames->[$FolderIdx]; # Now let's run through all detected messages .. my $MessageIdx; for ($MessageIdx = 0; $MessageIdx < $NrMessages; $MessageIdx++) { # Identifying a bit the message for the log. $Self->Log("Handling mail\n". " from : '$MessagesFrom->[$MessageIdx]'\n". " subject : '$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 $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 = ; 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 = $MessagesId->[$MessageIdx]; $Self->{'MessagesDownloaded'}->{$MessageId} = 1; # Some safety saving for "crashing" boxes. if ( ($MessageIdx % 10) == 9) { $Self->SaveDownloadedIds(); } $Self->Log("Done.\n",MinVerbosity => 1); } } ######################################################################################################################## # # Search for Cookie in the CookiesFile. # Class method. # Argument : The cookie to be found. # Returns its value. # ######################################################################################################################## sub FindCookie { my $Self = shift; my $CookieToFind = shift; my $TmpCookiesFile = $Self->{'TmpCookiesFile'}; open (COOKIES,$TmpCookiesFile) || die "Could not open '$TmpCookiesFile'."; while () { chomp; next if m/^#/; # Comment next if m/^$/; # Empty line. my @SplittedLine = split /\t/; if ($SplittedLine[5] eq $CookieToFind) { close COOKIES; return $SplittedLine[6]; } } close COOKIES; return ""; } ######################################################################################################################## # # Change the cookies file into making a Cookie "Secure". # This due to a current libcurl bug on "secure=" not being interpreted as "secure". # See : http://sourceforge.net/tracker/?func=detail&atid=100976&aid=3349227&group_id=976 # Argument : The cookie to be made secure. # ######################################################################################################################## sub MakeCookieSecure { my $Self = shift; my $CookieToMakeSecure = shift; my $TmpCookiesFile = $Self->{'TmpCookiesFile'}; my $NewContents = ""; open (COOKIES, $TmpCookiesFile) || die "Could not open '$TmpCookiesFile'."; while () { chomp; my $Line = $_; # Comment or empty line case. if ($Line =~ m/^#/ || $Line =~ m/^$/) { $NewContents .= $Line . "\n"; next; } my @SplittedLine = split (/\t/,$Line); if ($SplittedLine[5] eq $CookieToMakeSecure) { $SplittedLine[3] = "TRUE"; $NewContents .= join("\t",@SplittedLine) . "\n"; } else { $NewContents .= $Line . "\n"; } } close COOKIES; open (COOKIES, ">$TmpCookiesFile"); print COOKIES $NewContents; close COOKIES; } ######################################################################################################################## # # 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 $MessagesId = $Self->{'MessagesId'}; my $FolderNames = $Self->{'FolderNames'}; my $FolderIds = $Self->{'FolderIds'}; my $NrFolders = $Self->{'NrFolders'}; my $NParameter = $Self->{'NParameter'}; my $MessageId = $MessagesId->[$MessageIdx]; # Find out which folder (the index in @FolderIds) is meant. my $TargetFolderIdx = 0; my $TargetFolderFound = 0; while ((not $TargetFolderFound) && $TargetFolderIdx<$NrFolders) { if (lc $TargetFolderName eq lc $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); # We go via the mobile part of the site for simplicity. my $MobBaseUrl = $Self->{'BaseUrl'}; if ($MobBaseUrl !~ s/\/mail\//\/md\//) { $Self->Log("Unexpected format in $MobBaseUrl.\n", stderr => 1); return } my $ToBox = $FolderIds->[$TargetFolderIdx]; my $FromBox = $FolderIds->[$SourceFolderIdx]; my $MT = $Self->FindCookie("mt"); my $Url = $MobBaseUrl . "movedeletemessage.aspx?state=Move&msglist=$MessageId&src=$FromBox&dest=$ToBox&mt=$MT&$NParameter"; # Do The move ... my ($EmailPageAsString,$GetPageUrl) = $Self->GetPage(Url => $Url); } ######################################################################################################################## # # Class method. # Delete the message. # MessageIdx as argument. # ######################################################################################################################## sub DeleteMessage { my $Self = shift; my $MessageIdx = shift; my $NParameter = $Self->{'NParameter'}; my $MessagesId = $Self->{'MessagesId'}; my $MessageId = $MessagesId->[$MessageIdx]; $Self->Log("Deleting email message.\n",MinVerbosity => 1); my $BaseUrl = $Self->{'BaseUrl'}; my $PageUrl = $BaseUrl."InboxLight.aspx?$NParameter&mid=$MessageId&aId=markAsUnread"; my ($PageAsString,$GetPageUrl) = $Self->GetPage(Url => $PageUrl,FollowForward => 1); } ######################################################################################################################## # # Class method. # Mark the email message as read # MessageIdxIdx as argument. # ######################################################################################################################## sub MarkRead { my $Self = shift; my $MessageIdx = shift; my $NParameter = $Self->{'NParameter'}; my $MessagesId = $Self->{'MessagesId'}; my $MessageId = $MessagesId->[$MessageIdx]; $Self->Log("Marking email message as read.\n",MinVerbosity => 1); my $BaseUrl = $Self->{'BaseUrl'}; # The day I guesstimated this simple combination, I'd better spent buying lots of lotto forms ! my $PageUrl = $BaseUrl."InboxLight.aspx?$NParameter&mid=$MessageId"; my ($PageAsString,$GetPageUrl) = $Self->GetPage(Url => $PageUrl,FollowForward => 1); } ######################################################################################################################## # # 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 $MessagesId = $Self->{'MessagesId'}; my $Login = $Self->{'Login'}; my $Domain = $Self->{'Domain'}; my $BaseUrl = $Self->{'BaseUrl'}; my $MessageId = $MessagesId->[$MessageIdx]; $Self->Log("Getting email message.\n",MinVerbosity => 1); my $Url = "${BaseUrl}GetMessageSource.aspx?msgid=$MessageId"; my ($EmailPageAsString,$GetPageUrl) = $Self->GetPage(Url => $Url,FollowForward => 1); $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 !~ /
[\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 = ;
      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();
      if ($LoggedIn) {
        $LoggedIn = $GetLive->GetFolders();
      }
      $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 $FolderNames       = $GetLive->{'FolderNames'};
        my $FolderIds         = $GetLive->{'FolderIds'};
        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($FolderNames->[$FolderIdx])));
          next if ($FolderIdToProcess && ($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 ($FolderIds->[$FolderIdx] =~ m/^(0|-)*4$/);
          print LOG "INFO - Client : $Remote - Processing folder $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'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};

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

  $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'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};

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

  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 $MessagesDeleted->[$i];
    }
    $Self->Respond(".\r\n");
  } else {
    $Self->Respond("+OK $MessageIdx 1302\r\n") unless $MessagesDeleted->[$MessageIdx-1];
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n") if $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'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};
  my $MessagesPopped  = $GetLive->{'MessagesPopped'};
  my $MessagesRead    = $GetLive->{'MessagesRead'};
 
  # Effectively delete messages that are marked deleted.
  # MarkRead if asked so.
  for (my $Message = 0; $Message < $NrMessages; $Message++) {
    if ($MessagesDeleted->[$Message]) {
      $GetLive->DeleteMessage($Message);
      print LOG "INFO - Client : $Remote - DeleteMessage($Message)\n" if ($Verbosity);
    }
    if (not $MessagesRead->[$Message] && $Self->{'MarkRead'} && $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'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};
  my $MessagesPopped  = $GetLive->{'MessagesPopped'};

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

  if ($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.
  $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'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};

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

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

  if ($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'};
  my $MessagesId      = $GetLive->{'MessagesId'};
  my $MessagesDeleted = $GetLive->{'MessagesDeleted'};

  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 $MessagesId->[$i]\r\n") unless $MessagesDeleted->[$i];
    }
    $Self->Respond(".\r\n");
  } else {
    $Self->Respond("+OK $MessageIdx $MessagesId->[$MessageIdx-1]\r\n") unless $MessagesDeleted->[$MessageIdx-1];
    $Self->Respond("-ERR No such message : $MessageIdx.\r\n") if $MessagesDeleted->[$MessageIdx-1];
  }
}

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

sub PopCmdFold {
  my $Self = shift;

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

  $Self->Respond("+OK Folders follow.\r\n");
  for (my $Folder=0;$Folder<$NrFolders;$Folder++) {
    $Self->Respond("$FolderIds->[$Folder] - $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:  $' . "\n".
    '$Id: GetLive.pl,v 2.19 2012/07/31 19:39:53 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->GetFolders();
$GetLive->LoadDownloadedIds();

my $NrFolders   = $GetLive->{'NrFolders'};
my $FolderNames = $GetLive->{'FolderNames'};
my $FolderIds   = $GetLive->{'FolderIds'};
my $SkipTrash   = $GetLive->{'SkipTrash'};
my $UserName    = $GetLive->{'Login'};

for (my $FolderIdx=0;$FolderIdx<$NrFolders;$FolderIdx++) {
  next if (scalar keys %FoldersToProcess && not exists $FoldersToProcess{lc $FolderNames->[$FolderIdx]});
  next if ( ($SkipTrash =~ m/^Yes$/i) && ($FolderIds->[$FolderIdx] eq $TrashFolderId) );
  # JDLA hack. Drafts folder does not work, also not in real. Assuming 000-...-4 is the draft folder.
  next if ($FolderIds->[$FolderIdx] =~ m/^(0|-)*4$/);
  Display("\nProcessing folder $FolderNames->[$FolderIdx] for $UserName.\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);

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