source: npl/mailserver/imapsync/imapsync @ ac00c8f

Last change on this file since ac00c8f was c5c522c, checked in by Edwin Eefting <edwin@datux.nl>, 8 years ago

initial commit, transferred from cleaned syn3 svn tree

  • Property mode set to 100644
File size: 83.6 KB
Line 
1#!/usr/bin/perl
2
3=pod
4
5=head1 NAME
6
7imapsync - IMAP synchronisation, sync, copy or migration
8tool. Synchronise mailboxes between two imap servers. Good
9at IMAP migration. More than 32 different IMAP server softwares
10supported with success.
11
12$Revision: 1.267 $
13
14=head1 INSTALL
15
16 imapsync works fine under any Unix OS with perl.
17 imapsync works fine under Windows (2000, XP) and ActiveState's 5.8 Perl
18
19 imapsync is already available directly on the following distributions (at least):
20 FreeBSD, Debian, Ubuntu, Gentoo, NetBSD, Darwin, Mandriva and OpenBSD (yeah!).
21
22 Get imapsync at
23 http://www.linux-france.org/prj/imapsync/dist/
24
25 You'll find a compressed tarball called imapsync-x.xx.tgz
26 where x.xx is the version number. Untar the tarball where
27 you want (on Unix):
28
29 tar xzvf  imapsync-x.xx.tgz
30
31 Go into the directory imapsync-x.xx and read the INSTALL file.
32 The INSTALL file is also at
33 http://www.linux-france.org/prj/imapsync/INSTALL (for windows users)
34 
35 The freshmeat record is at http://freshmeat.net/projects/imapsync/
36
37=head1 SYNOPSIS
38
39  imapsync [options]
40
41To get a description of each option just run imapsync like this :
42
43  imapsync --help
44  imapsync
45
46The option list :
47
48  imapsync [--host1 server1]  [--port1 <num>]
49           [--user1 <string>] [--passfile1 <string>]
50           [--host2 server2]  [--port2 <num>]
51           [--user2 <string>] [--passfile2 <string>]
52           [--ssl1] [--ssl2]
53           [--authmech1 <string>] [--authmech2 <string>]
54           [--noauthmd5]
55           [--folder <string> --folder <string> ...]
56           [--folderrec <string> --folderrec <string> ...]
57           [--include <regex>] [--exclude <regex>]
58           [--prefix2 <string>] [--prefix1 <string>]
59           [--regextrans2 <regex> --regextrans2 <regex> ...]
60           [--sep1 <char>]
61           [--sep2 <char>]
62           [--justfolders] [--justfoldersizes] [--justconnect]
63           [--syncinternaldates]
64           [--idatefromheader]
65           [--buffersize  <int>]
66           [--syncacls]
67           [--regexmess <regex>] [--regexmess <regex>]
68           [--maxsize <int>]
69           [--maxage <int>]
70           [--minage <int>]
71           [--skipheader <regex>]
72           [--useheader <string>] [--useheader <string>]
73           [--skipsize]
74           [--delete] [--delete2]
75           [--expunge] [--expunge1] [--expunge2]
76           [--subscribed] [--subscribe]
77           [--nofoldersizes]
78           [--dry]
79           [--debug] [--debugimap]
80           [--timeout <int>] [--fast]
81           [--split1] [--split2]
82           [--version] [--help]
83 
84=cut
85# comment
86
87=pod
88
89=head1 DESCRIPTION
90
91The command imapsync is a tool allowing incremental and
92recursive imap transfer from one mailbox to another.
93
94By default all folders are transfered, recursively.
95
96We sometimes need to transfer mailboxes from one imap server to
97another. This is called migration.
98
99imapsync is the adequate tool because it reduces the amount
100of data transferred by not transferring a given message if it
101is already on both sides. Same headers, same message size
102and the transfer is done only once. All flags are
103preserved, unread will stay unread, read will stay read,
104deleted will stay deleted. You can stop the transfer at any
105time and restart it later, imapsync is adapted to a bad
106connection. imapsync is CPU hungry so nice and renice
107commands can be a good help. imapsync can be memory hungry too,
108especially with large messages.
109
110You can decide to delete the messages from the source mailbox
111after a successful transfer (it is a good feature when migrating).
112In that case, use the --delete --expunge1 options.
113
114You can also just synchronize a mailbox A from another mailbox B
115in case you just want to keep a "live" copy of B in A.
116
117=head1 OPTIONS
118
119To get a description of each option just invoke:
120
121imapsync --help
122
123=head1 HISTORY
124
125I wrote imapsync because an enterprise (basystemes) paid me to install
126a new imap server without loosing huge old mailboxes located on a far
127away remote imap server accessible by a low bandwith link. The tool
128imapcp (written in python) could not help me because I had to verify
129every mailbox was well transferred and delete it after a good
130transfer. imapsync started its life being a copy_folder.pl patch.
131The tool copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl
132module tarball source (in the examples/ directory of the tarball).
133
134=head1 EXAMPLE
135
136While working on imapsync parameters please run imapsync in
137dry mode (no modification induced) with the --dry
138option. Nothing bad can be done this way.
139
140To synchronize the imap account "buddy" on host
141"imap.src.fr" to the imap account "max" on host
142"imap.dest.fr" (the passwords are located in two files
143"/etc/secret1" for "buddy", "/etc/secret2" for "max") :
144
145 imapsync --host1 imap.src.fr  --user1 buddy --passfile1 /etc/secret1 \
146          --host2 imap.dest.fr --user2 max   --passfile2 /etc/secret2
147
148Then, you will have max's mailbox updated from buddy's
149mailbox.
150
151=head1 SECURITY
152
153You can use --password1 instead of --passfile1 to give the
154password but it is dangerous because any user on your host
155can see the password by using the 'ps auxwwww'
156command. Using a variable (like $PASSWORD1) is also
157dangerous because of the 'ps auxwwwwe' command. So, saving
158the password in a well protected file (600 or rw-------) is
159the best solution.
160
161imasync is not totally protected against sniffers on the
162network since passwords may be transferred in plain text in
163case CRAM-MD5 is not supported by your imap servers.  Use
164--ssl1 and --ssl2 to enable encryption on host1 and host2.
165
166You may authenticate as one user (typically an admin user),
167but be authorized as someone else, which means you don't
168need to know every user's personal password.  Specify
169--authuser1 "adminuser" to enable this on host1.  In this
170case, --authmech1 PLAIN will be used by default since it
171is the only way to go for now. So don't use --authmech1 SOMETHING
172with --authuser1 "adminuser", it will not work.
173Same behavior with the --authuser2 option.
174
175
176=head1 EXIT STATUS
177
178imapsync will exit with a 0 status (return code) if everything went good.
179Otherwise, it exits with a non-zero status.
180
181So if you have a buggy internet connection, you can use this loop
182in a Bourne shell:
183
184        while ! imapsync ...; do
185              echo imapsync not complete
186        done
187
188=head1 AUTHOR
189
190Gilles LAMIRAL <lamiral@linux-france.org>
191
192Feedback good or bad is always welcome.
193
194The newsgroup comp.mail.imap is a good place to talk about
195imapsync. I read it when imapsync is concerned.
196
197Gilles LAMIRAL earn his living writing, installing,
198configuring and teaching free open and gratis
199softwares. Do not hesitate to pay him for that services.
200
201
202=head1 LICENSE
203
204imapsync is free, gratis and open source software cover by
205the GNU General Public License. See the GPL file included in
206the distribution or the web site
207http://www.gnu.org/licenses/licenses.html
208
209=head1 MAILING-LIST
210
211Here is the welcome message:
212
213Welcome on the imapsync mailing-list.
214
215This list is dedicated to the users of imapsync
216http://www.linux-france.org/prj/imapsync/
217
218To write on the list, the address is:
219mailto:imapsync@linux-france.org
220
221To unsubscribe, send a message to:
222mailto:imapsync-unsubscribe@listes.linux-france.org
223
224To subscribe, send a message to:
225mailto:imapsync-subscribe@listes.linux-france.org
226
227To contact the person in charge for the list:
228mailto:imapsync-request@listes.linux-france.org
229
230The list archives may be available at:
231http://www.linux-france.org/prj/imapsync_list/
232So consider that the list is public, anyone
233can see your post. Use a pseudonym or do not
234post to this list if you want to stay private.
235
236Thank you for your participation.
237
238=head1 BUGS and BUG REPORT
239
240No known serious bug. 
241
242Report any bug or feature request to the author
243or the mailing-list.
244
245Before reporting bugs, read the FAQ, this README and the
246TODO files.
247
248Don't write imapsync in uppercase in the email title, I'll
249know you run windows and you haven't read the README yet.
250
251Make a good title, not just "imapsync" or "problem",
252a good title is made of keywords summary,  not too long (one visible line).
253
254Before reporting bugs, read the FAQ, this README and the
255TODO files. http://www.linux-france.org/prj/imapsync/
256
257Help me to help you: in your report, please include:
258
259 - imapsync version.
260 - output given with --debug --debugimap near the failure point.
261 - operating system running imapsync.
262 - imap servers softwares on both side and their version number.
263 - imapsync with all the options you use,  the full command line
264   you use (except the passwords of course).
265 - IMAPClient.pm version.
266
267 Those values can be found as a copy/paste at the begining of the output.
268
269 And also, if it can help :
270
271 - operating systems on both sides and the third side in case
272   you run imapsync on a foreign host from the both.
273
274=head1 IMAP SERVERS
275
276Failure stories reported with the following 4 imap servers :
277
278 - MailEnable 1.54 (Proprietary) http://www.mailenable.com/
279 - DBMail 0.9, 2.0.7 (GPL). But DBMail 1.2.1 works.
280   Patient and confident testers are welcome.
281 - dkimap4 2.39
282 - Imail 7.04 (maybe).
283
284Success stories reported with the following 35 imap servers
285(softwares names are in alphabetic order) :
286
287 - Archiveopteryx 2.03, 2.04, 2.09, 2.10 [dest], 3.0.0 [dest]
288   (OSL 3.0) http://www.archiveopteryx.org/
289 - BincImap 1.2.3 (GPL) (http://www.bincimap.org/)
290 - CommuniGatePro server (Redhat 8.0)
291 - Courier IMAP 1.5.1, 2.2.0, 2.1.1, 2.2.1, 3.0.8, 3.0.3, 4.1.1 (GPL)
292   (http://www.courier-mta.org/)
293 - Critical Path (7.0.020)
294 - Cyrus IMAP 1.5, 1.6, 2.1, 2.1.15, 2.1.16, 2.1.18
295   2.2.1, 2.2.2-BETA, 2.2.10, 2.2.12,
296   v2.2.3-Invoca-RPM-2.2.3-8,
297   2.3-alpha (OSI Approved),
298   v2.2.12-Invoca-RPM-2.2.12-3.RHEL4.1,
299   2.2.13,
300   v2.3.1-Invoca-RPM-2.3.1-2.7.fc5,
301   v2.3.7,
302   (http://asg.web.cmu.edu/cyrus/)
303 - David Tobit V8 (proprietary Message system).
304 - DBMail 1.2.1, 2.0.4, 2.0.9, 2.2rc1 (GPL) (http://www.dbmail.org/).
305   2.0.7 seems buggy.
306 - Deerfield VisNetic MailServer 5.8.6 [from]
307 - Dovecot 0.99.10.4, 0.99.14, 0.99.14-8.fc4, 1.0-0.beta2.7,
308   1.0.0 [dest/source] (LGPL) (http://www.dovecot.org/)
309 - Domino (Notes) 4.61[from], 6.5, 5.0.6, 5.0.7, 7.0.2, 6.0.2CF1, 7.0.1 [from]
310 - Eudora WorldMail v2
311 - GMX IMAP4 StreamProxy.
312 - Groupwise IMAP (Novell) 6.x and 7.0. Buggy so see the FAQ.
313 - iPlanet Messaging server 4.15, 5.1, 5.2
314 - IMail 7.15 (Ipswitch/Win2003), 8.12
315 - MDaemon 7.0.1, 8.0.2, 8.1, 9.5.4 (Windows server 2003 R2 platform)
316 - Mercury 4.1 (Windows server 2000 platform)
317 - Microsoft Exchange Server 5.5, 6.0.6249.0[from], 6.5.7638.1 [dest]
318 - Netscape Mail Server 3.6 (Wintel !)
319 - Netscape Messaging Server 4.15 Patch 7
320 - OpenMail IMAP server B.07.00.k0 (Samsung Contact ?)
321 - OpenWave
322 - Qualcomm Worldmail (NT)
323 - Rockliffe Mailsite 5.3.11, 4.5.6
324 - Samsung Contact IMAP server 8.5.0
325 - Scalix v10.1, 10.0.1.3, 11.0.0.431
326 - SmarterMail
327 - SunONE Messaging server 5.2, 6.0 (SUN JES - Java Enterprise System)
328 - Sun Java System Messaging Server 6.2-2.05
329 - Surgemail 3.6f5-5
330 - UW-imap servers (imap-2000b) rijkkramer IMAP4rev1 2000.287
331   (RedHat uses UW like 2003.338rh), v12.264 Solaris 5.7 (OSI Approved)
332   (http://www.washington.edu/imap/)
333 - UW - QMail v2.1
334 - Imap part of TCP/IP suite of VMS 7.3.2
335 - Zimbra-IMAP 3.0.1 GA 160, 3.1.0 Build 279, 4.0.5, 4.5.2, 4.5.6, 5.5.
336
337Please report to the author any success or bad story with
338imapsync and do not forget to mention the IMAP server
339software names and version on both sides. This will help
340future users. To help the author maintaining this section
341report the two lines at the begining of the output if they
342are useful to know the softwares. Example:
343
344 From software :* OK louloutte Cyrus IMAP4 v1.5.19 server ready
345 To   software :* OK Courier-IMAP ready
346
347You can use option --justconnect to get those lines.
348Example :
349
350  imapsync --host1 imap.troc.org --host2 imap.trac.org --justconnect
351
352Please rate imapsync at http://freshmeat.net/projects/imapsync/
353or better give the author a book, he likes books:
354http://www.amazon.com/gp/registry/wishlist/1C9UNDIH3P7R7/
355(or its paypal account gilles.lamiral@laposte.net)
356
357=head1 HUGE MIGRATION
358
359
360Have a special attention on options
361--subscribed
362--subscribe
363--delete
364--delete2
365--expunge
366--expunge1
367--expunge2
368--maxage
369--minage
370--maxsize
371--useheader
372
373If you have many mailboxes to migrate think about a little
374shell program. Write a file called file.csv (for example)
375containing users and passwords.
376The separator used in this example is ';'
377
378The file.csv file content is :
379
380user0001;password0001;user0002;password0002
381user0011;password0011;user0012;password0012
382...
383
384And the shell program is just :
385
386 { while IFS=';' read  u1 p1 u2 p2; do
387        imapsync --user1 "$u1" --password1 "$p1" --user2 "$u2" --password2 "$p2" ...
388 done ; } < file.csv
389
390Welcome in shell programming !
391
392=head1 Hacking
393
394Feel free to hack imapsync as the GPL Licence permits it.
395
396=head1 Links
397
398Entries for imapsync:
399  http://www.imap.org/products/showall.php
400
401
402=head1 SIMILAR SOFTWARES
403
404  imap_tools    : http://www.athensfbc.com/imap_tools
405  offlineimap   : http://software.complete.org/offlineimap
406  mailsync      : http://mailsync.sourceforge.net/
407  imapxfer      : http://www.washington.edu/imap/
408                   part of the imap-utils from UW.
409  mailutil      : replace imapxfer in
410                   part of the imap-utils from UW.
411                  http://www.gsp.com/cgi-bin/man.cgi?topic=mailutil
412  imaprepl      : http://www.bl0rg.net/software/
413                  http://freshmeat.net/projects/imap-repl/
414  imap_migrate  : http://freshmeat.net/projects/imapmigration/
415  imapcopy      : http://home.arcor.de/armin.diehl/imapcopy/imapcopy.html
416  migrationtool : http://sourceforge.net/projects/migrationtool/
417  imapmigrate   : http://sourceforge.net/projects/cyrus-utils/
418  wonko_imapsync: http://wonko.com/article/554
419                  see also tools/wonko_ruby_imapsync
420  pop2imap      : http://www.linux-france.org/prj/pop2imap/
421
422
423Feedback (good or bad) will be always welcome.
424
425$Id: imapsync,v 1.267 2008/10/07 11:36:02 gilles Exp $
426
427
428
429=cut
430
431
432use warnings;
433++$|;
434use strict;
435use Carp;
436use Getopt::Long;
437use Mail::IMAPClient;
438use Digest::MD5  qw(md5_base64);
439#use Term::ReadKey;
440#use IO::Socket::SSL;
441use MIME::Base64;
442use English;
443use POSIX qw(uname);
444use Fcntl;
445
446#use Test::Simple tests => 1;
447use Test::More 'no_plan';
448
449eval { require 'usr/include/sysexits.ph' };
450
451
452my(
453        $rcs, $debug, $debugimap, $error,
454        $host1, $host2, $port1, $port2,
455        $user1, $user2, $password1, $password2, $passfile1, $passfile2,
456        @folder, @include, @exclude, @folderrec,
457        $prefix1, $prefix2,
458        @regextrans2, @regexmess, @regexflag,
459        $sep1, $sep2,
460        $syncinternaldates,
461        $idatefromheader,
462        $syncacls,
463        $fastio1, $fastio2,
464        $maxsize, $maxage, $minage,
465        $skipheader, @useheader,
466        $skipsize, $foldersizes, $buffersize,
467        $delete, $delete2,
468        $expunge, $expunge1, $expunge2, $dry,
469        $justfoldersizes,
470        $authmd5,
471        $subscribed, $subscribe,
472        $version, $VERSION, $help,
473        $justconnect, $justfolders,
474        $fast,
475        $nohash,
476        $mess_size_total_trans,
477        $mess_size_total_skipped,
478        $mess_size_total_error,
479        $mess_trans, $mess_skipped, $mess_skipped_dry,
480        $timeout,   # whr (ESS/PRW)
481        $timestart, $timeend, $timediff,
482        $timesize, $timebefore,
483        $ssl1, $ssl2,
484        $authuser1, $authuser2,
485        $authmech1, $authmech2,
486        $split1, $split2,
487        $tests, $test_builder,
488        $allow3xx,
489);
490
491use vars qw ($opt_G); # missing code for this will be option.
492
493
494$rcs = '$Id: imapsync,v 1.267 2008/10/07 11:36:02 gilles Exp $ ';
495$rcs =~ m/,v (\d+\.\d+)/;
496$VERSION = ($1) ? $1 : "UNKNOWN";
497
498my $VERSION_IMAPClient = $Mail::IMAPClient::VERSION;
499
500
501
502$mess_size_total_trans   = 0;
503$mess_size_total_skipped = 0;
504$mess_size_total_error   = 0;
505$mess_trans = $mess_skipped = $mess_skipped_dry = 0;
506
507
508sub check_lib_version {
509        $debug and print "VERSION_IMAPClient $1 $2 $3\n";
510        if ($VERSION_IMAPClient eq '2.2.9') {
511                override_imapclient();
512                return(1);
513        }
514        else{
515                # 3.x.x is still buggy with imapsync.
516                # uncomment "return 1" if you want to check it.
517                #return 1;
518                #return 0;
519                if ($allow3xx) {
520*Mail::IMAPClient::Ssl = sub {
521        my $self = shift;
522       
523        if (@_) { $self->{SSL} = shift }
524        return $self->{SSL};
525};
526                        return(1);
527
528                }else{
529                        return(0);
530                }
531        }
532}
533
534$error=0;
535
536sub modules_VERSION() {
537
538no warnings 'uninitialized';
539my $modules_releases = "
540Mail::IMAPClient  $Mail::IMAPClient::VERSION
541IO::Socket        $IO::Socket::VERSION
542IO::Socket::SSL   $IO::Socket::SSL::VERSION
543Digest::MD5       $Digest::MD5::VERSION
544Digest::HMAC_MD5  $Digest::HMAC_MD5::VERSION
545Term::ReadKey     $Term::ReadKey::VERSION
546Date::Manip       $Date::Manip::VERSION
547";     
548    return($modules_releases);
549
550}
551
552
553my $banner = join("",
554                  '$RCSfile: imapsync,v $ ',
555                  '$Revision: 1.267 $ ',
556                  '$Date: 2008/10/07 11:36:02 $ ',
557                  "\n",localhost_info(),
558                  " and the module Mail::IMAPClient version used here is ",
559                  $VERSION_IMAPClient,"\n",
560                  "Command line used :\n",
561                  "$0 @ARGV\n",
562                 );
563
564unless(defined(&_SYSEXITS_H)) {
565        # 64 on my linux box.
566        eval 'sub EX_USAGE () {64;}' unless defined(&EX_USAGE);
567}
568
569get_options();
570
571check_lib_version() or
572  die "imapsync needs perl lib Mail::IMAPClient release 2.2.9 exactly, future imapsync release may suppoort 3.0.x, but sorry not now. See file BUG_IMAPClient_3.xx\n";
573
574
575print $banner;
576
577sub missing_option {
578        my ($option) = @_;
579        die "$option option must be used, run $0 --help for help\n";
580}
581
582# By default, 1000 at a time, not more.
583$split1 ||= 1000;
584$split2 ||= 1000;
585
586$host1 || missing_option("--host1") ;
587$port1 ||= defined $ssl1 ? 993 : 143;
588
589$host2 || missing_option("--host2") ;
590$port2 ||= defined $ssl2 ? 993 : 143;
591
592
593
594sub connect_imap {
595        my($host, $port, $debugimap, $ssl) = @_;
596        my $imap = Mail::IMAPClient->new();
597        $imap->Server($host);
598        $imap->Port($port);
599        $imap->Debug($debugimap);
600        $imap->Ssl($ssl) if ($ssl);
601        $imap->connect()
602          or die "Can not open imap connection on [$host] : $@\n";     
603}
604
605sub localhost_info {
606       
607        my($infos) = join("",
608            "Here is a [$OSNAME] system (",
609            join(" ",
610                 uname(),
611                 ),
612                 ")\n",
613                 "with perl ",
614                 sprintf("%vd", $PERL_VERSION),
615                 modules_VERSION()
616            );   
617        return($infos);
618
619}
620
621if ($justconnect) {
622        my $from = ();
623        my $to = ();
624       
625        $from = connect_imap($host1, $port1, $debugimap, $ssl1);
626        print "From software : ", server_banner($from);
627        print "From capability : ", join(" ", $from->capability()), "\n";
628        $to   = connect_imap($host2, $port2, $debugimap, $ssl2);
629        print "To   software : ", server_banner($to);
630        print "To   capability : ", join(" ", $to->capability()), "\n";
631        $from->logout();
632        $to->logout();
633        exit(0);
634}
635
636$user1 || missing_option("--user1");
637$user2 || missing_option("--user2");
638
639$syncinternaldates = defined($syncinternaldates) ? defined($syncinternaldates) : 1;
640
641if($idatefromheader) {
642        print "Turned ON idatefromheader, ",
643              "will set the internal dates on host2 from the 'Date:' header line.\n";
644        $syncinternaldates = 0;
645
646}
647if ($syncinternaldates) {
648        print "Turned ON syncinternaldates, ",
649              "will set the internal dates on host2 same as host1.\n";
650}else{
651        print "Turned OFF syncinternaldates\n";
652}
653
654if ($syncinternaldates || $idatefromheader) {
655        no warnings 'redefine';
656        local *Carp::confess = sub { return undef; };
657        require Date::Manip;
658        Date::Manip->import(qw(ParseDate Date_Cmp UnixDate Date_Init Date_TimeZone));
659        #print "Date_init : [", join(" ",Date_Init()), "]\n";
660        print "TimeZone :[", Date_TimeZone(), "]\n";
661        if (not (Date_TimeZone())) {
662                warn "TimeZone not defined, setting it to GMT";
663                Date_Init("TZ=GMT");
664                print "TimeZone : [", Date_TimeZone(), "]\n";
665        }
666}
667
668
669if(defined($authmd5) and not($authmd5)) {
670        $authmech1 ||= 'LOGIN';
671        $authmech2 ||= 'LOGIN';
672}
673else{
674        $authmech1 ||= $authuser1 ? 'PLAIN' : 'CRAM-MD5';
675        $authmech2 ||= $authuser2 ? 'PLAIN' : 'CRAM-MD5';
676}
677
678$authmech1 = uc($authmech1);
679$authmech2 = uc($authmech2);
680
681$authuser1 ||= $user1;
682$authuser2 ||= $user2;
683
684print "Will try to use $authmech1 authentication on host1\n";
685print "Will try to use $authmech2 authentication on host2\n";
686
687$syncacls = (defined($syncacls)) ? $syncacls : 0;
688$foldersizes = (defined($foldersizes)) ? $foldersizes : 1;
689
690$fastio1 = (defined($fastio1)) ? $fastio1 : 0;
691$fastio2 = (defined($fastio2)) ? $fastio2 : 0;
692
693
694
695@useheader = ("ALL") unless (@useheader);
696
697print "From imap server [$host1] port [$port1] user [$user1]\n";
698print "To   imap server [$host2] port [$port2] user [$user2]\n";
699
700
701sub ask_for_password {
702        my ($user, $host) = @_;
703        print "What's the password for $user\@$host? ";
704        Term::ReadKey::ReadMode(2);
705        my $password = <>;
706        chomp $password;
707        printf "\n";
708        Term::ReadKey::ReadMode(0);
709        return $password;
710}
711
712
713$password1 || $passfile1 || do {
714        $password1 = ask_for_password($authuser1 || $user1, $host1);
715};
716
717$password1 = (defined($passfile1)) ? firstline ($passfile1) : $password1;
718
719$password2 || $passfile2 || do {
720        $password2 = ask_for_password($authuser2 || $user2, $host2);
721};
722
723$password2 = (defined($passfile2)) ? firstline ($passfile2) : $password2;
724
725my $from = ();
726my $to = ();
727
728$timestart = time();
729$timebefore = $timestart;
730
731$debugimap and print "From connection\n";
732$from = login_imap($host1, $port1, $user1, $password1,
733                   $debugimap, $timeout, $fastio1, $ssl1,
734                   $authmech1, $authuser1);
735
736$debugimap and print "To  connection\n";
737$to = login_imap($host2, $port2, $user2, $password2,
738                 $debugimap, $timeout, $fastio2, $ssl2,
739                 $authmech2, $authuser2);
740
741#  history
742
743$debug and print "From Buffer I/O : ", $from->Buffer(), "\n";
744$debug and print "To   Buffer I/O : ", $to->Buffer(), "\n";
745
746
747sub login_imap {
748        my($host, $port, $user, $password,
749           $debugimap, $timeout, $fastio,
750           $ssl, $authmech, $authuser) = @_;
751        my ($imap);
752       
753        $imap = Mail::IMAPClient->new();
754       
755        $imap->Ssl($ssl) if ($ssl);
756        $imap->Clear(20);
757        $imap->Server($host);
758        $imap->Port($port);
759        $imap->Fast_io($fastio);
760        $imap->Buffer($buffersize || 4096);
761        $imap->Uid(1);
762        $imap->Peek(1);
763        $imap->Debug($debugimap);
764        $timeout and $imap->Timeout($timeout);
765       
766        $imap->connect()
767          or die "Can not open imap connection on [$host] with user [$user] : $@\n";
768       
769        print "Banner : ", server_banner($imap);
770       
771        if ($imap->has_capability("AUTH=$authmech")
772            or $imap->has_capability($authmech)
773           ) {
774                printf("Host %s says it has CAPABILITY for AUTHENTICATE %s\n",
775                       $imap->Server, $authmech);
776        }
777        else {
778                printf("Host %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
779                       $imap->Server, $authmech);
780                if ($authmech eq 'PLAIN') {
781                        print "Frequently PLAIN is only supported with SSL, ",
782                          "try --ssl1 or --ssl2 option\n";
783                }
784        }
785       
786        $imap->Authmechanism($authmech) unless ($authmech eq 'LOGIN');
787        $imap->Authcallback(\&plainauth) if $authmech eq "PLAIN";
788
789        $imap->User($user);
790        $imap->Authuser($authuser);
791        $imap->Password($password);
792        unless ($imap->login()) {
793                print "Error login : [$host] with user [$user] auth [$authmech]: $@\n";
794                die if ($authmech eq 'LOGIN');
795                die if $imap->IsUnconnected();
796                print "Trying LOGIN Auth mechanism on [$host] with user [$user]\n";
797                $imap->Authmechanism("");
798                $imap->login() or
799                  die "Error login : [$host] with user [$user] auth [LOGIN] : $@";
800        }
801        print "Success login on [$host] with user [$user] auth [$authmech]\n";
802        return($imap);
803}
804
805sub plainauth() {
806        my $code = shift;
807        my $imap = shift;
808
809        my $string = sprintf("%s\x00%s\x00%s", $imap->User,
810                            $imap->Authuser, $imap->Password);
811        return encode_base64("$string", "");
812}
813
814
815sub server_banner {
816        my $imap = shift;
817        for my $line ($imap->Results()) {
818                #print "LR: $line";
819                return $line if $line =~ /^\* (OK|NO|BAD)/;
820        }
821        return "No banner\n";
822 }
823
824
825
826print "From capability : ", join(" ", $from->capability()), "\n";
827print "To   capability : ", join(" ", $to->capability()), "\n";
828
829die unless $from->IsAuthenticated();
830print "From state Authenticated\n";
831die unless   $to->IsAuthenticated();
832print "To   state Authenticated\n";
833
834$split1 and $from->Split($split1);
835$split2 and $to->Split($split2);
836
837#
838# Folder stuff
839#
840
841my (@f_folders, %requested_folder, @t_folders, @t_folders_list, %t_folders_list, %subscribed_folder, %t_folders);
842
843sub tests_folder_routines {
844        ok( !give_requested_folders()                ,"no requested folders"  );
845        ok( !is_requested_folder('folder_foo')                                );
846        ok(  add_to_requested_folders('folder_foo')                           );
847        ok(  is_requested_folder('folder_foo')                                );
848        ok( !is_requested_folder('folder_NO_EXIST')                           );
849        ok( !remove_from_requested_folders('folder_foo'), "removed folder_foo");
850        ok( !is_requested_folder('folder_foo')                                );
851        my @f;
852        ok(  @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f");
853        ok(  is_requested_folder('folder_bar')                                );
854        ok(  is_requested_folder('folder_toto')                               );
855        ok(  remove_from_requested_folders('folder_toto')                     );
856        ok( !is_requested_folder('folder_toto')                               );
857        ok( init_requested_folders()                 , 'empty requested folders');
858        ok( !give_requested_folders()                , 'no requested folders'  );
859}
860
861sub give_requested_folders {
862        return(keys(%requested_folder));
863}
864
865sub init_requested_folders {
866       
867        %requested_folder = ();
868        return(1);
869       
870}
871
872sub is_requested_folder {
873        my ( $folder ) = @_;
874       
875        defined( $requested_folder{ $folder } );
876}
877
878
879sub add_to_requested_folders {
880        my @wanted_folders = @_;
881       
882        foreach my $folder ( @wanted_folders ) {
883                ++$requested_folder{ $folder };
884        }
885        return( keys( %requested_folder ) );
886}
887
888sub remove_from_requested_folders {
889        my @wanted_folders = @_;
890       
891        foreach my $folder (@wanted_folders) {
892                delete $requested_folder{$folder};
893        }
894        return( keys(%requested_folder) );
895}
896
897
898# Make a hash of subscribed folders in source server.
899map { $subscribed_folder{$_} = 1 } $from->subscribed();
900
901
902my @all_source_folders = sort $from->folders();
903
904if (scalar(@folder) or $subscribed or scalar(@folderrec)) {
905        # folders given by option --folder
906        if (scalar(@folder)) {
907                add_to_requested_folders(@folder);
908        }
909       
910        # option --subscribed
911        if ($subscribed) {
912                add_to_requested_folders(keys (%subscribed_folder));
913        }
914       
915        # option --folderrec
916        if (scalar(@folderrec)) {
917                foreach my $folderrec (@folderrec) {
918                        add_to_requested_folders($from->folders($folderrec));
919                }
920        }
921}
922else {
923       
924        # no include, no folder/subscribed/folderrec options => all folders
925        if (not scalar(@include)) {
926                add_to_requested_folders(@all_source_folders);
927        }
928}
929
930
931# consider (optional) includes and excludes
932if (scalar(@include)) {
933        foreach my $include (@include) {
934                my @included_folders = grep /$include/, @all_source_folders;
935                add_to_requested_folders(@included_folders);
936                print "Including folders matching pattern '$include': @included_folders\n";
937        }
938}
939
940if (scalar(@exclude)) {
941        foreach my $exclude (@exclude) {
942                my @requested_folder = sort(keys(%requested_folder));
943                my @excluded_folders = grep /$exclude/, @requested_folder;
944                remove_from_requested_folders(@excluded_folders);
945                print "Excluding folders matching pattern '$exclude': @excluded_folders\n";
946        }
947}
948
949# Remove no selectable folders
950
951foreach my $folder (keys(%requested_folder)) {
952        if ( not $from->selectable($folder)) {
953                print "Warning : ignoring folder $folder because it is not selectable\n";
954                remove_from_requested_folders($folder);
955        }
956}
957
958
959my @requested_folder = sort(keys(%requested_folder));
960
961@f_folders = @requested_folder;
962
963sub compare_lists {
964        my ($list_1_ref, $list_2_ref) = @_;
965       
966        return(-1) if ((not defined($list_1_ref)) and defined($list_2_ref));
967        return(0)  if (! $list_1_ref); # end if no list
968        return(1)  if (! $list_2_ref); # end if only one list
969       
970        if (not ref($list_1_ref)) {$list_1_ref = [$list_1_ref]};
971        if (not ref($list_2_ref)) {$list_2_ref = [$list_2_ref]};
972
973
974        my $last_used_indice = 0;
975        ELEMENT:
976        foreach my $indice ( 0 .. $#$list_1_ref ) {
977                $last_used_indice = $indice;
978               
979                # End of list_2
980                return 1 if ($indice > $#$list_2_ref);
981               
982                my $element_list_1 = $list_1_ref->[$indice];
983                my $element_list_2 = $list_2_ref->[$indice];
984                my $balance = $element_list_1 cmp $element_list_2 ;
985                next ELEMENT if ($balance == 0) ;
986                return $balance;
987        }
988        # each element equal until last indice of list_1
989        return -1 if ($last_used_indice < $#$list_2_ref);
990       
991        # same size, each element equal
992        return 0
993}
994
995sub tests_compare_lists {
996
997       
998        my $empty_list_ref = [];
999       
1000        ok( 0 == compare_lists()               , 'compare_lists, no args');
1001        ok( 0 == compare_lists(undef)          , 'compare_lists, undef = nothing');
1002        ok( 0 == compare_lists(undef, undef)   , 'compare_lists, undef = undef');
1003        ok(-1 == compare_lists(undef , [])     , 'compare_lists, undef < []');
1004        ok(+1 == compare_lists([])             , 'compare_lists, [] > nothing');
1005        ok(+1 == compare_lists([], undef)      , 'compare_lists, [] > undef');
1006        ok( 0 == compare_lists([] , [])        , 'compare_lists, [] = []');
1007       
1008        ok( 0 == compare_lists([1],  1 )          , "compare_lists, [1] =  1 ") ;
1009        ok( 0 == compare_lists( 1 , [1])          , "compare_lists,  1  = [1]") ;
1010        ok( 0 == compare_lists( 1 ,  1 )          , "compare_lists,  1  =  1 ") ;
1011        ok(-1 == compare_lists( 1 ,  2 )          , "compare_lists,  1  =  1 ") ;
1012        ok(+1 == compare_lists( 2 ,  1 )          , "compare_lists,  1  =  1 ") ;
1013
1014
1015        ok( 0 == compare_lists([1,2], [1,2])   , "compare_lists, [1,2] = [1,2]") ;
1016        ok(-1 == compare_lists([1], [1,2])     , "compare_lists, [1] < [1,2]") ;
1017        ok(-1 == compare_lists([1], [1,1])     , "compare_lists, [1] < [1,1]") ;
1018        ok(+1 == compare_lists([1, 1], [1])    , "compare_lists, [1, 1] > [1]") ;
1019        ok( 0 == compare_lists([1 .. 20_000] , [1 .. 20_000])
1020                                               , "compare_lists, [1..20_000] = [1..20_000]") ;
1021        ok(-1 == compare_lists([1], [3])       , 'compare_lists, [1] < [3]') ;
1022        ok( 0 == compare_lists([2], [2])       , 'compare_lists, [0] = [2]') ;
1023        ok(+1 == compare_lists([3], [1])       , 'compare_lists, [3] > [1]') ;
1024       
1025        ok(-1 == compare_lists(["a"], ["b"])   , 'compare_lists, ["a"] < ["b"]') ;
1026        ok( 0 == compare_lists(["a"], ["a"])   , 'compare_lists, ["a"] = ["a"]') ;
1027        ok( 0 == compare_lists(["ab"], ["ab"]) , 'compare_lists, ["ab"] = ["ab"]') ;
1028        ok(+1 == compare_lists(["b"], ["a"])   , 'compare_lists, ["b"] > ["a"]') ;
1029        ok(-1 == compare_lists(["a"], ["aa"])  , 'compare_lists, ["a"] < ["aa"]') ;
1030        ok(-1 == compare_lists(["a"], ["a", "a"]), 'compare_lists, ["a"] < ["a", "a"]') ;
1031}
1032
1033
1034
1035
1036
1037my($f_sep,$t_sep);
1038# what are the private folders separators for each server ?
1039
1040
1041$debug and print "Getting separators\n";
1042$f_sep = get_separator($from, $sep1, "--sep1");
1043$t_sep = get_separator($to, $sep2, "--sep2");
1044
1045#my $f_namespace = $from->namespace();
1046#my $t_namespace = $to->namespace();
1047#$debug and print "From namespace:\n", Data::Dumper->Dump([$f_namespace]);
1048#$debug and print "To   namespace:\n", Data::Dumper->Dump([$t_namespace]);
1049
1050my($f_prefix,$t_prefix);
1051$f_prefix = get_prefix($from, $prefix1, "--prefix1");
1052$t_prefix = get_prefix($to, $prefix2, "--prefix2");
1053
1054sub get_prefix {
1055        my($imap, $prefix_in, $prefix_opt) = @_;
1056        my($prefix_out);
1057       
1058        $debug and print "Getting prefix namespace\n";
1059        if (defined($prefix_in)) {
1060                print "Using [$prefix_in] given by $prefix_opt\n";
1061                $prefix_out = $prefix_in;
1062                return($prefix_out);
1063        }
1064        $debug and print "Calling namespace capability\n";
1065        if ($imap->has_capability("namespace")) {
1066                my $r_namespace = $imap->namespace();
1067                $prefix_out = $r_namespace->[0][0][0];
1068                return($prefix_out);
1069        }
1070        else{
1071                print
1072                  "No NAMESPACE capability in imap server ",
1073                    $imap->Server(),"\n",
1074                      "Give the prefix namespace with the $prefix_opt option\n";
1075                exit(1);
1076        }
1077}
1078
1079
1080sub get_separator {
1081        my($imap, $sep_in, $sep_opt) = @_;
1082        my($sep_out);
1083       
1084       
1085        if ($sep_in) {
1086                print "Using [$sep_in] given by $sep_opt\n";
1087                $sep_out = $sep_in;
1088                return($sep_out);
1089        }
1090        $debug and print "Calling namespace capability\n";
1091        if ($imap->has_capability("namespace")) {
1092                $sep_out = $imap->separator();
1093                return($sep_out);
1094        }
1095        else{
1096                print
1097                  "No NAMESPACE capability in imap server ",
1098                    $imap->Server(),"\n",
1099                      "Give the separator caracter with the $sep_opt option\n";
1100                exit(1);
1101        }
1102}
1103
1104
1105print "From separator and prefix : [$f_sep][$f_prefix]\n";
1106print "To   separator and prefix : [$t_sep][$t_prefix]\n";
1107
1108
1109sub foldersizes {
1110
1111        my ($side, $imap, $folders_r) = @_;
1112        my $tot = 0;
1113        my $tmess = 0;
1114        my @folders = @{$folders_r};
1115        print "++++ Calculating sizes ++++\n";
1116        foreach my $folder (@folders)     {
1117                my $stot = 0;
1118                my $smess = 0;
1119                printf("$side Folder %-35s", "[$folder]");
1120                unless($imap->exists($folder)) {
1121                        print("does not exist yet\n");
1122                        next;
1123                }
1124                unless ($imap->select($folder)) {
1125                        warn
1126                          "$side Folder $folder : Could not select ",
1127                            $imap->LastError,  "\n";
1128                        #$error++;
1129                        next;
1130                }
1131                if (defined($maxage) or defined($minage)) {
1132                        # The pb is fetch_hash() can only be applied on ALL messages
1133                        my @msgs = select_msgs($imap);
1134                        $smess = scalar(@msgs);
1135                        foreach my $m (@msgs) {
1136                                my $s = $imap->size($m)
1137                                  or warn "Could not find size of message $m: $@\n";
1138                                $stot += $s;
1139                        }
1140                }
1141                else{
1142                        my $hashref = {};
1143                        $smess = $imap->message_count();
1144                        unless ($smess == 0) {
1145                                #$imap->Ranges(1);
1146                                $imap->fetch_hash("RFC822.SIZE",$hashref) or die "$@";
1147                                #$imap->Ranges(0);
1148                                #print map {$hashref->{$_}->{"RFC822.SIZE"}, " "} keys %$hashref;
1149                                map {$stot += $hashref->{$_}->{"RFC822.SIZE"}} keys %$hashref;
1150                        }
1151                }
1152                printf(" Size: %9s", $stot);
1153                printf(" Messages: %5s\n", $smess);
1154                $tot += $stot;
1155                $tmess += $smess;
1156        }
1157        print "Total size: $tot\n";
1158        print "Total messages: $tmess\n";
1159        print "Time : ", timenext(), " s\n";
1160}
1161
1162
1163foreach my $f_fold (@f_folders) {
1164        my $t_fold;
1165        $t_fold = to_folder_name($f_fold);
1166        $t_folders{$t_fold}++;
1167}
1168
1169@t_folders = sort keys(%t_folders);
1170
1171
1172if ($foldersizes) {
1173        foldersizes("From", $from, \@f_folders);
1174        foldersizes("To  ", $to,   \@t_folders);
1175}
1176
1177
1178
1179
1180sub timenext {
1181        my ($timenow, $timerel);
1182        # $timebefore is global, beurk !
1183        $timenow    = time;
1184        $timerel    = $timenow - $timebefore;
1185        $timebefore = $timenow;
1186        return($timerel);
1187}
1188
1189exit if ($justfoldersizes);
1190
1191# needed for setting flags
1192my $tohasuidplus = $to->has_capability("UIDPLUS");
1193
1194
1195@t_folders_list = sort @{$to->folders()};
1196foreach my $folder (@t_folders_list) {
1197        $t_folders_list{$folder}++;
1198}
1199
1200print
1201  "++++ Listing folders ++++\n",
1202  "From folders list : ", map("[$_] ",@f_folders),"\n",
1203  "To   folders list : ", map("[$_] ",@t_folders_list),"\n";
1204
1205print
1206  "From subscribed folders list : ",
1207  map("[$_] ", sort keys(%subscribed_folder)), "\n"
1208  if ($subscribed);
1209
1210sub separator_invert {
1211        # The separator we hope we'll never encounter
1212        my $o_sep="\000";
1213
1214        my($f_fold, $f_sep, $t_sep) = @_;
1215
1216        my $t_fold = $f_fold;
1217        $t_fold =~ s@\Q$t_sep@$o_sep@g;
1218        $t_fold =~ s@\Q$f_sep@$t_sep@g;
1219        $t_fold =~ s@\Q$o_sep@$f_sep@g;
1220        return($t_fold);
1221}
1222
1223sub to_folder_name {
1224        my ($t_fold);
1225        my ($x_fold) = @_;
1226        # first we remove the prefix
1227        $x_fold =~ s/^$f_prefix//;
1228        $debug and print "removed source prefix : [$x_fold]\n";
1229        $t_fold = separator_invert($x_fold,$f_sep, $t_sep);
1230        $debug and print "inverted   separators : [$t_fold]\n";
1231        # Adding the prefix supplied by namespace or the --prefix2 option
1232        $t_fold = $t_prefix . $t_fold
1233          unless(($t_prefix eq "INBOX.") and ($t_fold =~ m/^INBOX$/i));
1234        $debug and print "added   target prefix : [$t_fold]\n";
1235
1236        # Transforming the folder name by the --regextrans2 option(s)
1237        foreach my $regextrans2 (@regextrans2) {
1238                $debug and print "eval \$t_fold =~ $regextrans2\n";
1239                eval("\$t_fold =~ $regextrans2");
1240        }
1241        return($t_fold);
1242}
1243
1244sub flags_regex {
1245        my ($flags_f) = @_;
1246        foreach my $regexflag (@regexflag) {
1247                $debug and print "eval \$flags_f =~ $regexflag\n";
1248                eval("\$flags_f =~ $regexflag");
1249        }
1250        return($flags_f);
1251}
1252
1253sub acls_sync {
1254        my($f_fold, $t_fold) = @_;
1255        if ($syncacls) {
1256                my $f_hash = $from->getacl($f_fold)
1257                  or warn "Could not getacl for $f_fold: $@\n";
1258                my $t_hash = $to->getacl($t_fold)
1259                  or warn "Could not getacl for $t_fold: $@\n";
1260                my %users = map({ ($_, 1) } (keys(%$f_hash), keys(%$t_hash)));
1261                foreach my $user (sort(keys(%users))) {
1262                        my $acl = $f_hash->{$user} || "none";
1263                        print "acl $user : [$acl]\n";
1264                        next if ($f_hash->{$user} && $t_hash->{$user} &&
1265                                 $f_hash->{$user} eq $t_hash->{$user});
1266                        unless ($dry) {
1267                                print "setting acl $t_fold $user $acl\n";
1268                                $to->setacl($t_fold, $user, $acl)
1269                                  or warn "Could not set acl: $@\n";
1270                        }
1271                }
1272        }
1273}
1274
1275
1276print "++++ Looping on each folder ++++\n";
1277
1278FOLDER: foreach my $f_fold (@f_folders) {
1279        my $t_fold;
1280        print "From Folder [$f_fold]\n";
1281        $t_fold = to_folder_name($f_fold);
1282        print "To   Folder [$t_fold]\n";
1283
1284        last FOLDER if $from->IsUnconnected();
1285        last FOLDER if   $to->IsUnconnected();
1286       
1287        unless ($from->select($f_fold)) {
1288                warn
1289                "From Folder $f_fold : Could not select ",
1290                $from->LastError,  "\n";
1291                #$error++;
1292                next FOLDER;
1293        }
1294        if ( ! exists($t_folders_list{$t_fold})) {
1295                print "To   Folder $t_fold does not exist\n";
1296                print "Creating folder [$t_fold]\n";
1297                unless ($dry){
1298                        unless ($to->create($t_fold)){
1299                                warn "Couldn't create [$t_fold]",
1300                                $to->LastError,"\n";
1301                                $error++;
1302                                next FOLDER;
1303                        }
1304                }
1305                else{
1306                        next FOLDER;
1307                }
1308        }
1309       
1310        acls_sync($f_fold, $t_fold);
1311
1312        unless ($to->select($t_fold)) {
1313                warn
1314                "To   Folder $t_fold : Could not select ",
1315                $to->LastError, "\n";
1316                #$error++;
1317                next FOLDER;
1318        }
1319       
1320        if ($expunge){
1321                print "Expunging $f_fold and $t_fold\n";
1322                unless($dry) { $from->expunge() };
1323                #unless($dry) { $to->expunge() };
1324        }
1325       
1326        if ($subscribe and exists $subscribed_folder{$f_fold}) {
1327                print "Subscribing to folder $t_fold on destination server\n";
1328                unless($dry) { $to->subscribe($t_fold) };
1329        }
1330       
1331        next FOLDER if ($justfolders);
1332
1333        last FOLDER if $from->IsUnconnected();
1334        last FOLDER if   $to->IsUnconnected();
1335
1336        my @f_msgs = select_msgs($from);
1337
1338
1339
1340        $debug and print "LIST FROM : ", scalar(@f_msgs), " messages [@f_msgs]\n";
1341        # internal dates on "TO" are after the ones on "FROM"
1342        # normally...
1343        my @t_msgs = select_msgs($to);
1344       
1345        $debug and print "LIST TO   : ", scalar(@t_msgs), " messages [@t_msgs]\n";
1346
1347        my %f_hash = ();
1348        my %t_hash = ();
1349       
1350        print "++++ From [$f_fold] Parse 1 ++++\n";
1351        last FOLDER if $from->IsUnconnected();
1352        last FOLDER if   $to->IsUnconnected();
1353
1354        my $f_heads = $from->parse_headers([@f_msgs],
1355                                            @useheader)if (@f_msgs) ;
1356        $debug and print "Time headers: ", timenext(), " s\n";
1357        my $f_fir;
1358        if (!$nohash) {
1359            $f_fir=$from->fetch_hash("FLAGS",
1360                              "INTERNALDATE",
1361                              "RFC822.SIZE") if (@f_msgs);
1362            $debug and print "Time fir  : ", timenext(), " s\n";
1363        }
1364       
1365        foreach my $m (@f_msgs) {
1366                parse_header_msg1($from, $m, $f_heads, $f_fir, "F", \%f_hash);
1367        }
1368        $debug and print "Time headers: ", timenext(), " s\n";
1369       
1370        print "++++ To   [$t_fold] Parse 1 ++++\n";
1371        last FOLDER if $from->IsUnconnected();
1372        last FOLDER if   $to->IsUnconnected();
1373
1374        my $t_heads =   $to->parse_headers([@t_msgs],
1375                                            @useheader) if (@t_msgs);
1376        $debug and print "Time headers: ", timenext(), " s\n";
1377
1378        my $t_fir;
1379        if (!$nohash) {
1380                $t_fir=$to->fetch_hash("FLAGS",
1381                                       "INTERNALDATE",
1382                                       "RFC822.SIZE") if (@t_msgs);
1383                $debug and print "Time fir  : ", timenext(), " s\n";
1384        }
1385
1386        foreach my $m (@t_msgs) {
1387                parse_header_msg1($to, $m, $t_heads, $t_fir, "T", \%t_hash);
1388        }
1389        $debug and print "Time headers: ", timenext(), " s\n";
1390       
1391        print "++++ Verifying [$f_fold] -> [$t_fold] ++++\n";
1392        # messages in "from" that are not good in "to"
1393       
1394        my @f_hash_keys_sorted_by_uid
1395          = sort {$f_hash{$a}{'m'} <=> $f_hash{$b}{'m'}} keys(%f_hash);
1396       
1397        #print map { $f_hash{$_}{'m'} . " "} @f_hash_keys_sorted_by_uid;
1398       
1399        my @t_hash_keys_sorted_by_uid
1400          = sort {$t_hash{$a}{'m'} <=> $t_hash{$b}{'m'}} keys(%t_hash);
1401
1402       
1403        if($delete2) {
1404                foreach my $m_id (@t_hash_keys_sorted_by_uid) {
1405                        #print "$m_id ";
1406                        unless (exists($f_hash{$m_id})) {
1407                                my $t_msg  = $t_hash{$m_id}{'m'};
1408                                print "deleting message $m_id  $t_msg\n";
1409                                $to->delete_message($t_msg) unless ($dry);     
1410                        }
1411                }
1412        }
1413
1414        MESS: foreach my $m_id (@f_hash_keys_sorted_by_uid) {
1415                my $f_size = $f_hash{$m_id}{'s'};
1416                my $f_msg = $f_hash{$m_id}{'m'};
1417                my $f_idate = $f_hash{$m_id}{'D'};
1418               
1419                if (defined $maxsize and $f_size > $maxsize) {
1420                        print "+ Skipping msg #$f_msg:$f_size in folder $f_fold (exceeds maxsize limit $maxsize bytes)\n";
1421                        $mess_size_total_skipped += $f_size;
1422                        $mess_skipped += 1;
1423                        next MESS;
1424                }
1425                $debug and print "+ key     $m_id #$f_msg\n";
1426                unless (exists($t_hash{$m_id})) {
1427                        print "+ NO msg #$f_msg [$m_id] in $t_fold\n";
1428                        # copy
1429                        print "+ Copying msg #$f_msg:$f_size to folder $t_fold\n";
1430                        last FOLDER if $from->IsUnconnected();
1431                        my $string;
1432                        $string = $from->message_string($f_msg);
1433                        #print "AAAmessage_string[$string]ZZZ\n";
1434                        #my $message_file = "tmp_imapsync_$$";
1435                        #$from->select($f_fold);
1436                        #unlink($message_file);
1437                        #$from->message_to_file($message_file, $f_msg) or do {
1438                        #       warn "Could not put message #$f_msg to file $message_file",
1439                        #       $from->LastError;
1440                        #       $error++;
1441                        #       $mess_size_total_error += $f_size;
1442                        #       next MESS;
1443                        #};
1444                        #$string = file_to_string($message_file);
1445                        #print "AAA1[$string]ZZZ\n";
1446                        #unlink($message_file);
1447                        if (@regexmess) {
1448                                $string = regexmess($string);
1449                               
1450                                #string_to_file($string, $message_file);
1451                        }
1452
1453                        sub tests_regexmess {
1454                               
1455                                ok("blabla" eq regexmess("blabla"), "regexmess, nothing to do");
1456                                @regexmess = ('s/p/Z/g');
1457                                ok("ZoZoZo" eq regexmess("popopo"), "regexmess, s/p/Z/g");
1458                                @regexmess = 's{c}{C}gxms';
1459                                #print "RRR€\n", regexmess("H1: abc\nH2: cde\n\nBody abc"), "\n";
1460                                ok("H1: abC\nH2: Cde\n\nBody abC"
1461                                           eq regexmess("H1: abc\nH2: cde\n\nBody abc"),
1462                                   "regexmess, c->C");
1463                               
1464                        }
1465
1466                        sub regexmess {
1467                                my ($string) = @_;
1468                                foreach my $regexmess (@regexmess) {
1469                                        $debug and print "eval \$string =~ $regexmess\n";
1470                                        eval("\$string =~ $regexmess");
1471                                }
1472                                return($string);
1473                        }
1474
1475                        $debug and print
1476                                "=" x80, "\n",
1477                                "F message content begin next line\n",
1478                                $string,
1479                                "F message content ended on previous line\n", "=" x 80, "\n";
1480                        my $d = "";
1481                        if ($syncinternaldates) {
1482                                $d = $f_idate;
1483                                $debug and print "internal date from 1: [$d]\n";
1484                                $d = good_date($d);
1485                                $debug and print "internal date from 1: [$d] (fixed)\n";
1486                        }
1487                       
1488                        if ($idatefromheader) {
1489                               
1490                                $d = $from->get_header($f_msg,"Date");
1491                                $debug and print "header date from 1: [$d]\n";
1492                                $d = good_date($d);
1493                                $debug and print "header date from 1: [$d] (fixed)\n";
1494                        }
1495
1496                        sub good_date {
1497                                my ($d) = @_;
1498                                $d = UnixDate(ParseDate($d), "%d-%b-%Y %H:%M:%S %z");
1499                                $d = "\"$d\"";
1500                                return($d);
1501                        }
1502
1503                        my $flags_f = $f_hash{$m_id}{'F'} || "";
1504                        # RFC 2060 : This flag can not be altered by any client
1505                        $flags_f =~ s@\\Recent@@gi;
1506                        $flags_f = flags_regex($flags_f) if @regexflag;
1507                       
1508                        my $new_id;
1509                        print "flags from : [$flags_f][$d]\n";
1510                        last FOLDER if   $to->IsUnconnected();
1511                        unless ($dry) {
1512                               
1513                                if ($OSNAME eq "MSWin32") {
1514                                        $new_id = $to->append_string($t_fold,$string, $flags_f, $d);
1515                                }
1516                                else {
1517                                        # just back to append_string since append_file 3.05 does not work.
1518                                        #$new_id = $to->append_file($t_fold, $message_file, "", $flags_f, $d);
1519                                        # append_string 3.05 does not work too some times with $d unset.
1520                                        $new_id = $to->append_string($t_fold,$string, $flags_f, $d);
1521                                }
1522                                unless($new_id){
1523                                        warn "Couldn't append msg #$f_msg (Subject:[".
1524                                          $from->subject($f_msg)."]) to folder $t_fold: ",
1525                                          $to->LastError, "\n";
1526                                        $error++;
1527                                        $mess_size_total_error += $f_size;
1528                                        next MESS;
1529                                }
1530                                else{
1531                                        # good
1532                                        # $new_id is an id if the IMAP server has the
1533                                        # UIDPLUS capability else just a ref
1534                                        print "Copied msg id [$f_msg] to folder $t_fold msg id [$new_id]\n";
1535                                        $mess_size_total_trans += $f_size;
1536                                        $mess_trans += 1;
1537                                        if($delete) {
1538                                                print "Deleting msg #$f_msg in folder $f_fold\n";
1539                                                $from->delete_message($f_msg) unless ($dry);
1540                                                $from->expunge() if ($expunge and not $dry);
1541                                        }
1542                                }
1543                        }
1544                        else{
1545                                $mess_skipped_dry += 1;
1546                        }
1547                        #unlink($message_file);
1548                        next MESS;
1549                }
1550                else{
1551                        $debug and print "Message id [$m_id] found in t:$t_fold\n";
1552                        $mess_size_total_skipped += $f_size;
1553                        $mess_skipped += 1;
1554                }
1555               
1556                $fast and next MESS;
1557                #$debug and print "MESSAGE $m_id\n";
1558                my $t_size = $t_hash{$m_id}{'s'};
1559                my $t_msg  = $t_hash{$m_id}{'m'};
1560               
1561               
1562                $debug and print "Setting flags\n";
1563                last FOLDER if $from->IsUnconnected();
1564                last FOLDER if   $to->IsUnconnected();
1565
1566                my (@flags_f,@flags_t);
1567                my $flags_f_rv = $from->flags($f_msg);
1568                @flags_f = @{$flags_f_rv} if ref($flags_f_rv);
1569               
1570                # No flag \Recent here, no ?
1571                my $flags_f = join(" ", @flags_f);
1572               
1573                $flags_f = flags_regex($flags_f) if @regexflag;
1574               
1575                # This add or change flags but no flag are removed with this
1576                $to->store($t_msg,
1577                           "+FLAGS (" . $flags_f . ")"
1578                          ) unless ($dry) ;
1579               
1580                my $flags_t_rv = $to->flags($t_msg);
1581                @flags_t = @{$flags_t_rv} if ref($flags_t_rv);
1582                my $flags_t = join(" ", @flags_t);
1583                $debug and print
1584                  "flags from : $flags_f\n",
1585                  "flags to   : $flags_t\n";
1586               
1587
1588                $debug and do {
1589                        print "Looking dates\n";
1590                        #my $d_f = $from->internaldate($f_msg);
1591                        #my $d_t = $to->internaldate($t_msg);
1592                        my $d_f = $f_hash{$m_id}{'D'};
1593                        my $d_t = $t_hash{$m_id}{'D'};
1594                        print
1595                          "idate from : $d_f\n",
1596                            "idate to   : $d_t\n";
1597                       
1598                        #unless ($d_f eq $d_t) {
1599                        #       print "!!! Dates differ !!!\n";
1600                        #}
1601                };
1602                unless (($f_size == $t_size) or $skipsize) {
1603                        # Bad size
1604                        print
1605                        "Message $m_id SZ_BAD  f:$f_msg:$f_size t:$t_msg:$t_size\n";
1606                        # delete in to and recopy ?
1607                        # NO recopy CODE HERE. to be written if needed.
1608                        $error++;
1609                        if ($opt_G){
1610                                print "Deleting msg f:#$t_msg in folder $t_fold\n";
1611                                $to->delete_message($t_msg) unless ($dry);
1612                        }
1613                }
1614                else {
1615                        # Good
1616                        $debug and print
1617                        "Message $m_id SZ_GOOD f:$f_msg:$f_size t:$t_msg:$t_size\n";
1618                        if($delete) {
1619                                print "Deleting msg #$f_msg in folder $f_fold\n";
1620                                $from->delete_message($f_msg) unless ($dry);
1621                                $from->expunge() if ($expunge and not $dry);
1622                        }
1623                }
1624        }
1625        if ($expunge1){
1626                print "Expunging source folder $f_fold\n";
1627                unless($dry) { $from->expunge() };
1628        }
1629        if ($expunge2){
1630                print "Expunging target folder $t_fold\n";
1631                unless($dry) { $to->expunge() };
1632        }
1633
1634print "Time : ", timenext(), " s\n";
1635}
1636
1637
1638
1639$from->logout();
1640$to->logout();
1641
1642$timeend = time();
1643
1644$timediff = $timeend - $timestart;
1645
1646stats();
1647
1648
1649
1650
1651
1652exit(1) if($error);
1653
1654sub select_msgs {
1655        my ($imap) = @_;
1656        my (@msgs,@max,@min,@union,@inter);
1657       
1658        unless (defined($maxage) or defined($minage)) {
1659                @msgs = $imap->search("ALL");
1660                return(@msgs);
1661        }
1662        if (defined($maxage)) {
1663                @max = $imap->sentsince(time - 86400 * $maxage);
1664        }
1665        if (defined($minage)) {
1666                @min = $imap->sentbefore(time - 86400 * $minage);
1667        }
1668      SWITCH: {
1669                unless(defined($minage)) {@msgs = @max; last SWITCH};
1670                unless(defined($maxage)) {@msgs = @min; last SWITCH};
1671                my (%union, %inter);
1672                foreach my $m (@min, @max) {$union{$m}++ && $inter{$m}++}
1673                @inter = keys(%inter);
1674                @union = keys(%union);
1675                # normal case
1676                if ($minage <= $maxage)  {@msgs = @inter; last SWITCH};
1677                # just exclude messages between
1678                if ($minage > $maxage)  {@msgs = @union; last SWITCH};
1679               
1680        }
1681        return(@msgs);
1682}
1683
1684sub stats {
1685        print "++++ Statistics ++++\n";
1686        print "Time                   : $timediff sec\n";
1687        print "Messages transferred   : $mess_trans ";
1688        print "(could be $mess_skipped_dry without dry mode)" if ($dry);
1689        print "\n";
1690        print "Messages skipped       : $mess_skipped\n";
1691        print "Total bytes transferred: $mess_size_total_trans\n";
1692        print "Total bytes skipped    : $mess_size_total_skipped\n";
1693        print "Total bytes error      : $mess_size_total_error\n";
1694        print "Detected $error errors\n\n";
1695        print thank_author();
1696}
1697
1698sub thank_author {
1699
1700        return(join("", "Happy with this free, open and gratis GPL software?\n",
1701          "Please, thank the author (Gilles LAMIRAL) by giving him a book:\n",
1702          "http://www.amazon.com/gp/registry/wishlist/1C9UNDIH3P7R7/\n",
1703          "and rate imapsync at http://freshmeat.net/projects/imapsync/\n"));
1704
1705}
1706
1707sub get_options
1708{
1709        my $numopt = scalar(@ARGV);
1710        my $opt_ret = GetOptions(
1711                                   "debug!"       => \$debug,
1712                                   "debugimap!"   => \$debugimap,
1713                                   "host1=s"     => \$host1,
1714                                   "host2=s"     => \$host2,
1715                                   "port1=i"     => \$port1,
1716                                   "port2=i"     => \$port2,
1717                                   "user1=s"     => \$user1,
1718                                   "user2=s"     => \$user2,
1719                                   "password1=s" => \$password1,
1720                                   "password2=s" => \$password2,
1721                                   "passfile1=s" => \$passfile1,
1722                                   "passfile2=s" => \$passfile2,
1723                                   "authmd5!"    => \$authmd5,
1724                                   "sep1=s"      => \$sep1,
1725                                   "sep2=s"      => \$sep2,
1726                                   "folder=s"    => \@folder,
1727                                   "folderrec=s" => \@folderrec,
1728                                   "include=s"   => \@include,
1729                                   "exclude=s"   => \@exclude,
1730                                   "prefix1=s"   => \$prefix1,
1731                                   "prefix2=s"   => \$prefix2,
1732                                   "regextrans2=s" => \@regextrans2,
1733                                   "regexmess=s" => \@regexmess,
1734                                   "regexflag=s" => \@regexflag,
1735                                   "delete!"     => \$delete,
1736                                   "delete2!"    => \$delete2,
1737                                   "syncinternaldates!" => \$syncinternaldates,
1738                                   "idatefromheader!" => \$idatefromheader,
1739                                   "syncacls!"   => \$syncacls,
1740                                   "maxsize=i"   => \$maxsize,
1741                                   "maxage=i"    => \$maxage,
1742                                   "minage=i"    => \$minage,
1743                                   "buffersize=i" => \$buffersize,
1744                                   "foldersizes!" => \$foldersizes,
1745                                   "dry!"        => \$dry,
1746                                   "expunge!"    => \$expunge,
1747                                   "expunge1!"    => \$expunge1,
1748                                   "expunge2!"    => \$expunge2,
1749                                   "subscribed!" => \$subscribed,
1750                                   "subscribe!"  => \$subscribe,
1751                                   "justconnect!"=> \$justconnect,
1752                                   "justfolders!"=> \$justfolders,
1753                                   "justfoldersizes!" => \$justfoldersizes,
1754                                   "fast!"       => \$fast,
1755                                   "nohash!"       => \$nohash,
1756                                   "version"     => \$version,
1757                                   "help"        => \$help,
1758                                   "timeout=i"   => \$timeout,
1759                                   "skipheader=s" => \$skipheader,
1760                                   "useheader=s" => \@useheader,
1761                                   "skipsize!"   => \$skipsize,
1762                                   "fastio1!"     => \$fastio1,
1763                                   "fastio2!"     => \$fastio2,
1764                                   "ssl1!"        => \$ssl1,
1765                                   "ssl2!"        => \$ssl2,
1766                                   "authmech1=s" => \$authmech1,
1767                                   "authmech2=s" => \$authmech2,
1768                                   "authuser1=s" => \$authuser1,
1769                                   "authuser2=s" => \$authuser2,
1770                                   "split1=i"    => \$split1,
1771                                   "split2=i"    => \$split2,
1772                                   "tests"       => \$tests,
1773                                   "allow3xx!"   => \$allow3xx,
1774                                  );
1775       
1776        $debug and print "get options: [$opt_ret]\n";
1777
1778        $test_builder = Test::More->builder;
1779        $test_builder->no_ending(1);
1780               
1781        # just the version
1782        print "$VERSION\n" and exit if ($version) ;
1783       
1784        if ($tests) {
1785                $test_builder->no_ending(0);
1786                tests();
1787                exit;
1788        }
1789
1790
1791        load_modules();
1792
1793        # exit with --help option or no option at all
1794        usage() and exit if ($help or ! $numopt) ;
1795
1796        # don't go on if options are not all known.
1797        exit(EX_USAGE()) unless ($opt_ret) ;
1798       
1799       
1800       
1801}
1802
1803
1804sub load_modules {
1805
1806        require IO::Socket::SSL if ($ssl1 or $ssl2);
1807        require Date::Manip if ($syncinternaldates || $idatefromheader) ;
1808
1809#       require Term::ReadKey if (
1810#               (not($password1 or $passfile1))
1811#            or (not($password2 or $passfile2))
1812#        or (not $help));
1813
1814        #require Data::Dumper if ($debug);
1815}
1816
1817
1818
1819sub parse_header_msg1 {
1820        my ($imap, $m_uid, $s_heads, $s_fir, $s, $s_hash) = @_;
1821       
1822        my $head = $s_heads->{$m_uid};
1823        my $headnum =  scalar(keys(%$head));
1824        $debug and print "Head NUM:", $headnum, "\n";
1825        unless($headnum) { print "Warning : no header used or found for message $m_uid\n"; }
1826        my $headstr;
1827       
1828        foreach my $h (sort keys(%$head)){
1829                foreach my $val (sort @{$head->{$h}}) {
1830                        # no 8-bit data in headers !
1831                        $val =~ s/[\x80-\xff]/X/g;
1832                       
1833                        # remove the first blanks (dbmail bug ?)
1834                        # and uppercase  header keywords
1835                        # (dbmail and dovecot)
1836                        $val =~ s/^\s*(.+)$/$1/;
1837                       
1838                        #my $H = uc($h);
1839                        my $H = "$h: $val";
1840                        # show stuff in debug mode
1841                        $debug and print "${s}H $H:", $val, "\n";
1842                       
1843                        if ($skipheader and $H =~ m/$skipheader/i) {
1844                                $debug and print "Skipping header $H\n";
1845                                next;
1846                        }
1847                        #$headstr .= "$H:". $val;
1848                        $headstr .= "$H";
1849                }
1850        }
1851        #return unless ($headstr);
1852        unless ($headstr){
1853                # taking everything is too heavy,
1854                # should take only 1 Ko
1855                #print "no header so taking everything\n";
1856                #$headstr = $imap->message_string($m_uid);
1857               
1858                print "no header so we ignore this message\n";
1859                return;
1860        }
1861        my $size  = $s_fir->{$m_uid}->{"RFC822.SIZE"};
1862        my $flags = $s_fir->{$m_uid}->{"FLAGS"};
1863        my $idate = $s_fir->{$m_uid}->{"INTERNALDATE"};
1864        $size = length($headstr) unless ($size);
1865        my $m_md5 = md5_base64($headstr);       
1866        $debug and print "$s msg $m_uid:$m_md5:$size\n";
1867        my $key;
1868        if ($skipsize) {
1869                $key = "$m_md5";
1870        }
1871        else {
1872                $key = "$m_md5:$size";
1873        }
1874        $s_hash->{"$key"}{'5'} = $m_md5;
1875        $s_hash->{"$key"}{'s'} = $size;
1876        $s_hash->{"$key"}{'D'} = $idate;
1877        $s_hash->{"$key"}{'F'} = $flags;
1878        $s_hash->{"$key"}{'m'} = $m_uid;
1879}
1880
1881
1882sub  firstline {
1883        # extract the first line of a file (without \n)
1884
1885        my($file) = @_;
1886        my $line  = "";
1887       
1888        open FILE, $file or die("error [$file]: $! ");
1889        chomp($line = <FILE>);
1890        close FILE;
1891        $line = ($line) ? $line : "error !EMPTY! [$file]";
1892        return $line;
1893}
1894
1895
1896sub file_to_string {
1897        my($file) = @_;
1898        my @string;
1899        open FILE, $file or die("error [$file]: $! ");
1900        @string = <FILE>;
1901        close FILE;
1902        return join("", @string);
1903}
1904
1905
1906sub string_to_file {
1907        my($string, $file) = @_;
1908        sysopen(FILE, $file,O_WRONLY|O_TRUNC|O_CREAT, 0600) or die("$! $file");
1909        print FILE $string;
1910        close FILE;
1911}
1912
1913
1914
1915sub usage {
1916        my $localhost_info = localhost_info();
1917        my $thank = thank_author();
1918        print <<EOF;
1919
1920usage: $0 [options]
1921
1922Several options are mandatory.
1923
1924--host1       <string> : "from" imap server. Mandatory.
1925--port1       <int>    : port to connect on host1. Default is 143.
1926--user1       <string> : user to login on host1. Mandatory.
1927--authuser1   <string> : user to auth with on host1 (admin user).
1928                         Avoid using --authmech1 SOMETHING with --authuser1.
1929--password1   <string> : password for the user1. Dangerous, use --passfile1
1930--passfile1   <string> : password file for the user1. Contains the password.
1931--host2       <string> : "destination" imap server. Mandatory.
1932--port2       <int>    : port to connect on host2. Default is 143.
1933--user2       <string> : user to login on host2. Mandatory.
1934--authuser2   <string> : user to auth with on host2 (admin user).
1935--password2   <string> : password for the user2. Dangerous, use --passfile2
1936--passfile2   <string> : password file for the user2. Contains the password.
1937--noauthmd5            : don't use MD5 authentification.
1938--authmech1   <string> : auth mechanism to use with host1:
1939                         PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
1940--authmech2   <string> : auth mechanism to use with host2. See --authmech1
1941--ssl1                 : use an SSL connection on host1.
1942--ssl2                 : use an SSL connection on host2.
1943--folder      <string> : sync this folder.
1944--folder      <string> : and this one, etc.
1945--folderrec   <string> : sync this folder recursively.
1946--folderrec   <string> : and this one, etc.
1947--include     <regex>  : sync folders matching this regular expression
1948--include     <regex>  : or this one, etc.
1949                         in case both --include --exclude options are
1950                         use, include is done before.
1951--exclude     <regex>  : skips folders matching this regular expression
1952                         Several folders to avoid:
1953                          --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
1954--exclude     <regex>  : or this one, etc.
1955--prefix1     <string> : remove prefix to all destination folders
1956                         (usually INBOX. for cyrus imap servers)
1957                         you can use --prefix1 if your source imap server
1958                         does not have NAMESPACE capability.
1959--prefix2     <string> : add prefix to all destination folders
1960                         (usually INBOX. for cyrus imap servers)
1961                         use --prefix2 if your target imap server does not
1962                         have NAMESPACE capability.
1963--regextrans2 <regex>  : Apply the whole regex to each destination folders.
1964--regextrans2 <regex>  : and this one. etc.
1965                         When you play with the --regextrans2 option, first
1966                         add also the safe options --dry --justfolders
1967                         Then, when happy, remove --dry, remove --justfolders
1968--regexmess   <regex>  : Apply the whole regex to each message before transfer.
1969                         Example : 's/\\000/ /g' # to replace null by space.
1970--regexmess   <regex>  : and this one.
1971--regexmess   <regex>  : and this one, etc.
1972--regexflag   <regex>  : Apply the whole regex to each flags list.
1973                         Example : 's/\"Junk"//g' # to remove "Junk" flag.
1974--regexflag   <regex>  : and this one, etc.
1975--sep1        <string> : separator in case namespace is not supported.
1976--sep2        <string> : idem.
1977--delete               : delete messages on source imap server after
1978                         a successful transfer. Useful in case you
1979                         want to migrate from one server to another one.
1980                         With imap, delete tags messages as deleted, they
1981                         are not really deleted. See expunge.
1982--delete2              : delete messages on the destination imap server that
1983                         are not on the source server.
1984--expunge              : expunge messages on source account.
1985                         expunge really deletes messages marked deleted.
1986                         expunge is made at the beginning on the
1987                         source server only. newly transferred messages
1988                         are expunged if option --expunge is given.
1989                         no expunge is done on destination account but
1990                         it will change in future releases.
1991--expunge1             : expunge messages on source account.
1992--expunge2             : expunge messages on target account.
1993--syncinternaldates    : sets the internal dates on host2 same as host1.
1994                         Turned on by default.
1995--idatefromheader      : sets the internal dates on host2 same as the
1996                         "Date:" headers.
1997--buffersize  <int>    : sets the size of a block of I/O.
1998--maxsize     <int>    : skip messages larger than <int> bytes
1999--maxage      <int>    : skip messages older than <int> days.
2000                         final stats (skipped) don't count older messages
2001                         see also --minage
2002--minage      <int>    : skip messages newer than <int> days.
2003                         final stats (skipped) don't count newer messages
2004                         You can do (+ are the messages selected):
2005                         past|----maxage+++++++++++++++>now
2006                         past|+++++++++++++++minage---->now
2007                         past|----maxage+++++minage---->now (intersection)
2008                         past|++++minage-----maxage++++>now (union)
2009--skipheader  <regex>  : Don't take into account header keyword
2010                         matching <string> ex: --skipheader 'X.*'
2011--useheader   <string> : Use this header to compare messages on both sides.
2012                         Ex: Message-ID or Subject or Date.
2013--useheader   <string>   and this one, etc.
2014--skipsize             : Don't take message size into account.
2015--dry                  : do nothing, just print what would be done.
2016--subscribed           : transfers subscribed folders.
2017--subscribe            : subscribe to the folders transferred on the
2018                         "destination" server that are subscribed
2019                         on the "source" server.
2020--(no)foldersizes      : Calculate the size of each "From" folder in bytes
2021                         and message counts. Meant to be used with
2022                         --justfoldersizes. Turned on by default.
2023--justfoldersizes      : exit after printed the folder sizes.
2024--syncacls             : Synchronises acls (Access Control Lists).
2025--nosyncacls           : Does not synchronise acls. This is the default.
2026--debug                : debug mode.
2027--debugimap            : imap debug mode.
2028--version              : print software version.
2029--justconnect          : just connect to both servers and print useful
2030                         information. Need only --host1 and --host2 options.
2031--justfolders          : just do things about folders (ignore messages).
2032--fast                 : be faster (just does not sync flags).
2033--nohash               : be even faster: dont fetch hashen, requires much less cpu and resources. (i dont know what it breaks!)
2034--split1     <int>     : split the requests in several parts on source server.
2035                         <int > is the number of messages handled per request.
2036                         default is like --split1 1000
2037--split2     <int>     : same thing on the "destination" server.
2038--fastio1              : use fastio with the "from" server.
2039--fastio2              : use fastio with the "destination" server.
2040--timeout     <int>    : imap connect timeout.
2041--help                 : print this.
2042
2043Example: to synchronise imap account "foo" on "imap.truc.org"
2044                     to imap account "bar" on "imap.trac.org"
2045
2046$0 \\
2047   --host1 imap.truc.org --user1 foo --passfile1 /etc/secret1 \\
2048   --host2 imap.trac.org --user2 bar --passfile2 /etc/secret2
2049
2050$localhost_info
2051$rcs
2052
2053$thank
2054EOF
2055}
2056
2057
2058
2059sub tests {
2060       
2061      SKIP: {
2062                skip "No test in normal run" if (not $tests);
2063                tests_folder_routines();
2064                tests_compare_lists();
2065                tests_regexmess();
2066        }
2067}
2068
2069sub override_imapclient {
2070no warnings 'redefine';
2071no strict 'subs';
2072
2073use constant Unconnected => 0;
2074use constant Connected         => 1;            # connected; not logged in
2075use constant Authenticated => 2;                # logged in; no mailbox selected
2076use constant Selected => 3;                     # mailbox selected
2077use constant INDEX => 0;                        # Array index for output line number
2078use constant TYPE => 1;                         # Array index for line type
2079                                                #    (either OUTPUT, INPUT, or LITERAL)
2080use constant DATA => 2;                         # Array index for output line data
2081use constant NonFolderArg => 1;                 # Value to pass to Massage to
2082                                                # indicate non-folder argument
2083
2084
2085
2086*Mail::IMAPClient::append_file = sub  {
2087
2088        my $self        = shift;
2089        my $folder      = $self->Massage(shift);
2090        my $file        = shift;
2091        my $control     = shift || undef;
2092        my $count       = $self->Count($self->Count+1);
2093        my $flags       = shift || undef;
2094        my $date        = shift || undef;
2095       
2096        if (defined($flags)) {
2097                $flags =~ s/^\s+//g;
2098                $flags =~ s/\s+$//g;
2099        }
2100       
2101        if (defined($date)) {
2102                $date =~ s/^\s+//g;
2103                $date =~ s/\s+$//g;
2104        }
2105       
2106        $flags = "($flags)"  if $flags and $flags !~ /^\(.*\)$/ ;
2107        $date  = qq/"$date"/ if $date  and $date  !~ /^"/       ;
2108       
2109
2110        unless ( -f $file ) {
2111                $self->LastError("File $file not found.\n");
2112                return undef;
2113        }
2114
2115        my $fh = IO::File->new($file) ;
2116
2117        unless ($fh) {
2118                $self->LastError("Unable to open $file: $!\n");
2119                $@ = "Unable to open $file: $!" ;
2120                carp "unable to open $file: $!";
2121                return undef;
2122        }
2123
2124        my $bare_nl_count = scalar grep { /^\x0a$|[^\x0d]\x0a$/} <$fh>;
2125
2126        seek($fh,0,0);
2127
2128        my $clear = $self->Clear;
2129
2130        $self->Clear($clear)
2131                if $self->Count >= $clear and $clear > 0;
2132
2133        my $length = ( -s $file ) + $bare_nl_count;
2134
2135        my $string = "$count APPEND $folder " .
2136                     ( $flags ? "$flags " : ""       ) .
2137                     ( $date ? "$date " : ""         ) .
2138                     "{" . $length  . "}\x0d\x0a" ;
2139       
2140        $self->_record($count,[ $self->_next_index($count), "INPUT", "$string" ] );
2141
2142        my $feedback = $self->_send_line("$string");
2143
2144        unless ($feedback) {
2145                $self->LastError("Error sending '$string' to IMAP: $!\n");
2146                $fh->close;
2147                return undef;
2148        }
2149
2150        my ($code, $output) = ("","");
2151
2152        until ( $code ) {
2153                $output = $self->_read_line or $fh->close, return undef;
2154                foreach my $o (@$output) {
2155                        $self->_record($count,$o);              # $o is already an array ref
2156                      ($code) = $o->[DATA] =~ /(^\+|^\d+\sNO|^\d+\sBAD)/i;
2157                      if ($o->[DATA] =~ /^\*\s+BYE/) {
2158                              carp $o->[DATA];
2159                                $self->State(Unconnected);
2160                                $fh->close;
2161                                return undef ;
2162                      } elsif ( $o->[DATA]=~ /^\d+\s+(NO|BAD)/i ) {
2163                              carp $o->[DATA];
2164                                $fh->close;
2165                                return undef;
2166                        }
2167                }
2168        }
2169
2170        {       # Narrow scope
2171                # Slurp up headers: later we'll make this more efficient I guess
2172                local $/ = "\x0d\x0a\x0d\x0a";
2173                my $text = <$fh>;
2174                $text =~ s/\x0d?\x0a/\x0d\x0a/g;
2175                $self->_record($count,[ $self->_next_index($count), "INPUT", "{From file $file}" ] ) ;
2176                $feedback = $self->_send_line($text);
2177
2178                unless ($feedback) {
2179                        $self->LastError("Error sending append msg text to IMAP: $!\n");
2180                        $fh->close;
2181                        return undef;
2182                }
2183                _debug($self, "control points to $$control\n") if ref($control) and $self->Debug;
2184                $/ =    ref($control) ?  "\x0a" : $control ? $control :         "\x0a";
2185                while (defined($text = <$fh>)) {
2186                        $text =~ s/\x0d?\x0a/\x0d\x0a/g;
2187                        $self->_record( $count,
2188                                        [ $self->_next_index($count), "INPUT", "{from $file}\x0d\x0a" ]
2189                        );
2190                        $feedback = $self->_send_line($text,1);
2191
2192                        unless ($feedback) {
2193                                $self->LastError("Error sending append msg text to IMAP: $!\n");
2194                                $fh->close;
2195                                return undef;
2196                        }
2197                }
2198                $feedback = $self->_send_line("\x0d\x0a");
2199
2200                unless ($feedback) {
2201                        $self->LastError("Error sending append msg text to IMAP: $!\n");
2202                        $fh->close;
2203                        return undef;
2204                }
2205        }
2206
2207        # Now for the crucial test: Did the append work or not?
2208        ($code, $output) = ("","");
2209
2210        my $uid = undef;
2211        until ( $code ) {
2212                $output = $self->_read_line or return undef;
2213                foreach my $o (@$output) {
2214                        $self->_record($count,$o);              # $o is already an array ref
2215                      $self->_debug("append_file: Deciding if " . $o->[DATA] . " has the code.\n")
2216                                if $self->Debug;
2217                      ($code) = $o->[DATA]  =~ /^\d+\s(NO|BAD|OK)/i;
2218                        # try to grab new msg's uid from o/p
2219                      $o->[DATA]  =~ m#UID\s+\d+\s+(\d+)\]# and $uid = $1;
2220                      if ($o->[DATA] =~ /^\*\s+BYE/) {
2221                              carp $o->[DATA];
2222                                $self->State(Unconnected);
2223                                $fh->close;
2224                                return undef ;
2225                      } elsif ( $o->[DATA]=~ /^\d+\s+(NO|BAD)/i ) {
2226                              carp $o->[DATA];
2227                                $fh->close;
2228                                return undef;
2229                        }
2230                }
2231        }
2232        $fh->close;
2233
2234        if ($code !~ /^OK/i) {
2235                return undef;
2236        }
2237
2238
2239        return defined($uid) ? $uid : $self;
2240};
2241
2242
2243
2244
2245*Mail::IMAPClient::fetch_hash = sub {
2246        # taken from original lib,
2247        # just added split code.
2248        my $self = shift;
2249        my $hash = ref($_[-1]) ? pop @_ : {};
2250        my @words = @_;
2251        for (@words) {
2252                s/([\( ])FAST([\) ])/${1}FLAGS INTERNALDATE RFC822\.SIZE$2/i  ;
2253                s/([\( ])FULL([\) ])/${1}FLAGS INTERNALDATE RFC822\.SIZE ENVELOPE BODY$2/i  ;
2254        }
2255        my $msgref_all = scalar($self->messages);
2256        my $split = $self->Split() || scalar(@$msgref_all);
2257        while(my @msgs = splice(@$msgref_all, 0, $split)) {
2258        #print "SPLIT: @msgs\n";
2259        my $msgref = \@msgs;
2260        my $output = scalar($self->fetch($msgref,"(" . join(" ",@_) . ")"))
2261        ; #     unless grep(/\b(?:FAST|FULL)\b/i,@words);
2262        my $x;
2263        for ($x = 0;  $x <= $#$output ; $x++) {
2264                my $entry = {};
2265                my $l = $output->[$x];
2266                if ($self->Uid) {       
2267                        my($uid) = $l =~ /\((?:.* )?UID (\d+).*\)/i;
2268                        next unless $uid;
2269                        if ( exists $hash->{$uid} ) {
2270                                $entry = $hash->{$uid} ;
2271                        }
2272                        else {
2273                                $hash->{$uid} ||= $entry;
2274                        }
2275                }
2276                else {
2277                        my($mid) = $l =~ /^\* (\d+) FETCH/i;
2278                        next unless $mid;
2279                        if ( exists $hash->{$mid} ) {
2280                                $entry = $hash->{$mid} ;
2281                        }
2282                        else {
2283                                $hash->{$mid} ||= $entry;
2284                        }
2285                }
2286                       
2287                foreach my $w (@words) {
2288                   if ( $l =~ /\Q$w\E\s*$/i ) {
2289                        $entry->{$w} = $output->[$x+1];
2290                        $entry->{$w} =~ s/(?:\x0a?\x0d)+$//g;
2291                        chomp $entry->{$w};
2292                   }
2293                   else {
2294                        $l =~ /\(           # open paren followed by ...
2295                                (?:.*\s)?   # ...optional stuff and a space
2296                                \Q$w\E\s    # escaped fetch field<sp>
2297                                (?:"        # then: a dbl-quote
2298                                  (\\.|   # then bslashed anychar(s) or ...
2299                                   [^"]+)   # ... nonquote char(s)
2300                                "|          # then closing quote; or ...
2301                                \(          # ...an open paren
2302                                  (\\.|     # then bslashed anychar or ...
2303                                   [^\)]+)  # ... non-close-paren char
2304                                \)|         # then closing paren; or ...
2305                                (\S+))      # unquoted string
2306                                (?:\s.*)?   # possibly followed by space-stuff
2307                                \)          # close paren
2308                        /xi;
2309                        $entry->{$w}=defined($1)?$1:defined($2)?$2:$3;
2310                   }
2311                }
2312        }
2313}
2314        return wantarray ? %$hash : $hash;
2315};
2316
2317
2318
2319*Mail::IMAPClient::login = sub {
2320        my $self = shift;
2321        return $self->authenticate($self->Authmechanism,$self->Authcallback)
2322                if $self->{Authmechanism};
2323
2324        my $id   = $self->User;
2325        my $has_quotes = $id =~ /^".*"$/ ? 1 : 0;
2326        my $string =    "Login " . ( $has_quotes ? $id : qq("$id") ) .
2327                        " " . $self->Password . "\r\n";
2328        $self->_imap_command($string)
2329                and $self->State(Authenticated);
2330        # $self->folders and $self->separator unless $self->NoAutoList;
2331        unless ( $self->IsAuthenticated) {
2332                my($carp)       =  $self->LastError;
2333                $carp           =~ s/^[\S]+ ([^\x0d\x0a]*)\x0d?\x0a/$1/;
2334                carp $carp unless defined wantarray;
2335                return undef;
2336        };
2337        return $self;
2338};
2339
2340
2341*Mail::IMAPClient::get_header = sub {
2342        my($self , $msg, $header ) = @_;
2343        my $val;
2344       
2345        #eval { $val = $self->parse_headers([$msg],$header)->{$header}[0] };
2346        my $h = $self->parse_headers([$msg],$header);
2347        #require Data::Dumper;
2348        #print Data::Dumper->Dump([$h]);
2349        #$val = $self->parse_headers([$msg],$header)->{$header}[0];
2350       
2351        $val = $h->{$msg}{$header}[0];
2352        return defined($val)? $val : undef;
2353};
2354
2355
2356*Mail::IMAPClient::parse_headers = sub {
2357        my($self,$msgspec_all,@fields) = @_;
2358        my(%fieldmap) = map { ( lc($_),$_ )  } @fields;
2359        my $msg; my $string; my $field;
2360        #print ref($msgspec_all), "\n";
2361        #if(ref($msgspec_all) eq 'HASH') {
2362    #    print ref($msgspec_all), "\n";
2363                #$msgspec_all = [$msgspec_all];
2364        #}
2365
2366        unless(ref($msgspec_all) eq 'ARRAY') {
2367                print "parse_headers want an ARRAY ref\n";
2368                #exit 1;
2369                return undef;
2370        }
2371       
2372        my $headers = {};       # hash from message ids to header hash
2373        my $split = $self->Split() || scalar(@$msgspec_all);
2374        while(my @msgs = splice(@$msgspec_all, 0, $split)) {
2375                $debug and print "SPLIT: @msgs\n";
2376                my $msgspec = \@msgs;
2377
2378        # Make $msg a comma separated list, of messages we want
2379        $msg = $self->Range($msgspec);
2380               
2381        if ($fields[0]  =~      /^[Aa][Ll]{2}$/         ) {
2382
2383                $string =       "$msg body" .
2384                # use ".peek" if Peek parameter is a) defined and true,
2385                #       or b) undefined, but not if it's defined and untrue:
2386
2387                (       defined($self->Peek)            ?
2388                        ( $self->Peek ? ".peek" : "" )  :
2389                        ".peek"
2390                ) .  "[header]"                         ;
2391
2392        }else {
2393                $string =       "$msg body" .
2394                # use ".peek" if Peek parameter is a) defined and true, or
2395                # b) undefined, but not if it's defined and untrue:
2396
2397                ( defined($self->Peek)                  ?
2398                        ( $self->Peek ? ".peek" : "" )  :
2399                        ".peek"
2400                ) .  "[header.fields (" . join(" ",@fields)     . ')]' ;
2401        }
2402
2403        my @raw=$self->fetch(   $string ) or return undef;
2404
2405       
2406        my $h = 0;              # reference to hash of current msgid, or 0 between msgs
2407       
2408        for my $header (map { split(/(?:\x0d\x0a)/,$_) } @raw) {
2409               
2410                no warnings;
2411                if ( $header =~ /^\*\s+\d+\s+FETCH\s+\(.*BODY\[HEADER(?:\]|\.FIELDS)/i) {
2412                        if ($self->Uid) {
2413                                if ( my($msgid) = $header =~ /UID\s+(\d+)/ ) {
2414                                        $h = {};
2415                                        $headers->{$msgid} = $h;
2416                                }
2417                                else {
2418                                        $h = {};
2419                                }
2420                        }
2421                        else {
2422                                if ( my($msgid) = $header =~ /^\*\s+(\d+)/ ) {
2423                                        #start of new message header:
2424                                        $h = {};
2425                                        $headers->{$msgid} = $h;
2426                                }
2427                        }
2428                }
2429                next if $header =~ /^\s+$/;
2430
2431                # ( for vi
2432                if ($header =~ /^\)/) {           # end of this message
2433                        $h = 0;                   # set to be between messages
2434                        next;
2435                }
2436                # check for '<optional_white_space>UID<white_space><UID_number><optional_white_space>)'
2437                # when parsing headers by UID.
2438                if ($self->Uid and my($msgid) = $header =~ /^\s*UID\s+(\d+)\s*\)/) {
2439                        $headers->{$msgid} = $h;        # store in results against this message
2440                        $h = 0;                         # set to be between messages
2441                        next;
2442                }
2443
2444                if ($h != 0) {                    # do we expect this to be a header?
2445                        my $hdr = $header;
2446                        chomp $hdr;
2447                        $hdr =~ s/\r$//;
2448                        #print "W[$hdr]", ref($hdr), "!\n";
2449                        #next if ( ! defined($hdr));
2450                        #print "X[$hdr]\n";
2451
2452                        if (defined($hdr) and ($hdr =~ s/^(\S+):\s*//)) {
2453                        # if ($hdr =~ s/^(\S+):\s*//) {
2454                                #print "X1\n";
2455                                $field = exists $fieldmap{lc($1)} ? $fieldmap{lc($1)} : $1 ;
2456                                push @{$h->{$field}} , $hdr ;
2457                        } elsif ($hdr =~ s/^.*FETCH\s\(.*BODY\[HEADER\.FIELDS.*\)\]\s(\S+):\s*//) {
2458                                #print "X2\n";
2459                                $field = exists $fieldmap{lc($1)} ? $fieldmap{lc($1)} : $1 ;
2460                                push @{$h->{$field}} , $hdr ;
2461                        } elsif ( ref($h->{$field}) eq 'ARRAY') {
2462                                #print "X3\n";
2463                               
2464                                        $hdr =~ s/^\s+/ /;
2465                                        $h->{$field}[-1] .= $hdr ;
2466                        }
2467                }
2468        }
2469        use warnings;
2470#       my $candump = 0;
2471#       if ($self->Debug) {
2472#                eval {
2473#                        require Data::Dumper;
2474#                        Data::Dumper->import;
2475#                };
2476#                $candump++ unless $@;
2477#        }
2478
2479        }
2480        # if we asked for one message, just return its hash,
2481        # otherwise, return hash of numbers => header hash
2482        # if (ref($msgspec) eq 'ARRAY') {
2483       
2484        return $headers;
2485       
2486};
2487
2488
2489*Mail::IMAPClient::authenticate = sub {
2490
2491        my $self        = shift;
2492        my $scheme      = shift;
2493        my $response    = shift;
2494
2495        $scheme   ||= $self->Authmechanism;
2496        $response ||= $self->Authcallback;
2497        my $clear = $self->Clear;
2498
2499        $self->Clear($clear)
2500                if $self->Count >= $clear and $clear > 0;
2501
2502        my $count       = $self->Count($self->Count+1);
2503
2504
2505        my $string = "$count AUTHENTICATE $scheme";
2506
2507        $self->_record($count,[ $self->_next_index($self->Transaction),
2508                                "INPUT", "$string\x0d\x0a"] );
2509
2510        my $feedback = $self->_send_line("$string");
2511
2512        unless ($feedback) {
2513                $self->LastError("Error sending '$string' to IMAP: $!\n");
2514                return undef;
2515        }
2516
2517        my ($code, $output);
2518
2519        until ($code) {
2520                $output = $self->_read_line or return undef;
2521               
2522                foreach my $o (@$output) {
2523                        $self->_record($count,$o);      # $o is a ref
2524                        ($code) = $o->[DATA] =~ /^\+(.*)$/ ;
2525                        if ($o->[DATA] =~ /^\*\s+BYE/) {
2526                                $self->State(Unconnected);
2527                                return undef ;
2528                        }
2529                        if ($o->[DATA]=~ /^\d+\s+(NO|BAD)/i) {
2530                                return undef ;
2531                        }
2532                }
2533        }
2534
2535        if ('CRAM-MD5' eq $scheme && ! $response) {
2536          if ($Mail::IMAPClient::_CRAM_MD5_ERR) {
2537            $self->LastError($Mail::IMAPClient::_CRAM_MD5_ERR);
2538            carp $Mail::IMAPClient::_CRAM_MD5_ERR;
2539          }
2540          else {
2541            $response = \&Mail::IMAPClient::_cram_md5;
2542          }
2543        }
2544
2545        $feedback = $self->_send_line($response->($code, $self));
2546
2547        unless ($feedback) {
2548                $self->LastError("Error sending append msg text to IMAP: $!\n");
2549                return undef;
2550        }
2551
2552        $code = "";     # clear code
2553        until ($code) {
2554                $output = $self->_read_line or return undef;
2555                foreach my $o (@$output) {
2556                        $self->_record($count,$o);      # $o is a ref
2557                        if ( ($code) = $o->[DATA] =~ /^\+ (.*)$/ ) {
2558                                $feedback = $self->_send_line($response->($code,$self));
2559                                unless ($feedback) {
2560                                        $self->LastError("Error sending append msg text to IMAP: $!\n");
2561                                        return undef;
2562                                }
2563                                $code = "" ;            # Clear code; we're still not finished
2564                        } else {
2565                                $o->[DATA] =~ /^$count (OK|NO|BAD)/ and $code = $1;
2566                                if ($o->[DATA] =~ /^\*\s+BYE/) {
2567                                        $self->State(Unconnected);
2568                                        return undef ;
2569                                }
2570                        }
2571                }
2572        }
2573
2574        $code =~ /^OK/ and $self->State(Authenticated) ;
2575        return $code =~ /^OK/ ? $self : undef ;
2576
2577};
2578
2579
2580
2581*Mail::IMAPClient::_cram_md5 = sub  {
2582  my ($code, $client) = @_;
2583  my $hmac = Digest::HMAC_MD5::hmac_md5_hex(MIME::Base64::decode($code),
2584                                            $client->Password());
2585  return MIME::Base64::encode($client->User() . " $hmac", "");
2586};
2587
2588*Mail::IMAPClient::message_string = sub {
2589        my $self = shift;
2590        my $msg  = shift;
2591        my $expected_size = $self->size($msg);
2592        return undef unless(defined $expected_size);    # unable to get size
2593        my $cmd  =      $self->has_capability('IMAP4REV1')                              ?
2594                                "BODY" . ( $self->Peek ? '.PEEK[]' : '[]' )             :
2595                                "RFC822" .  ( $self->Peek ? '.PEEK' : ''  )             ;
2596
2597        $self->fetch($msg,$cmd) or return undef;
2598
2599        my $string = "";
2600
2601        foreach my $result  (@{$self->{"History"}{$self->Transaction}}) {
2602              $string .= $result->[DATA]
2603                if defined($result) and $self->_is_literal($result) ;
2604        }
2605
2606        # BUG? should probably return undef if length != expected
2607        # No bug, somme servers are buggy.
2608
2609        if ( length($string) != $expected_size ) {
2610                warn "message_string: " .
2611                        "expected $expected_size bytes but received " .
2612                        length($string) . "\n";
2613                $self->LastError("message_string: expected ".
2614                        "$expected_size bytes but received " .
2615                        length($string)."\n");
2616        }
2617        return $string;
2618};
2619
2620*Mail::IMAPClient::Ssl = sub {
2621        my $self = shift;
2622       
2623        if (@_) { $self->{SSL} = shift }
2624        return $self->{SSL};
2625};
2626
2627
2628*Mail::IMAPClient::connect = sub {
2629        my $self = shift;
2630       
2631        $self->Port(143)
2632                if      defined ($IO::Socket::INET::VERSION)
2633                and     $IO::Socket::INET::VERSION eq '1.25'
2634                and     !$self->Port;
2635        %$self = (%$self, @_);
2636
2637        my $sock = ($self->Ssl ? IO::Socket::SSL->new : IO::Socket::INET->new);
2638        my $dp = ($self->Ssl ? 'imaps(993)' : 'imap(143)');
2639
2640        my $ret = $sock->configure({
2641                PeerAddr => $self->Server               ,
2642                PeerPort => $self->Port||$dp            ,
2643                Proto    => 'tcp'                       ,
2644                Timeout  => $self->Timeout||0           ,
2645                Debug   => $self->Debug                 ,
2646        });
2647        unless ( defined($ret) ) {
2648                $self->LastError( "$@\n");       
2649                $@              = "$@";   
2650                carp              "$@"
2651                                unless defined wantarray;       
2652                return undef;
2653        }
2654        $self->Socket($sock);
2655        $self->State(Connected);
2656        $sock->autoflush(1)                             ;
2657        my ($code, $output);
2658        $output = "";
2659        until ( $code ) {
2660
2661                $output = $self->_read_line or return undef;
2662                for my $o (@$output) {
2663                        $self->_debug("Connect: Received this from readline: " .
2664                                        join("/",@$o) . "\n");
2665                        $self->_record($self->Count,$o);        # $o is a ref
2666                      next unless $o->[TYPE] eq "OUTPUT";
2667                      ($code) = $o->[DATA] =~ /^\*\s+(OK|BAD|NO)/i  ;
2668                }
2669
2670        }
2671
2672        if ($code =~ /BYE|NO /) {
2673                $self->State(Unconnected);
2674                return undef ;
2675        }
2676
2677        if ($self->User and $self->Password) {
2678                return $self->login ;
2679        }
2680        else {
2681                return $self;   
2682        }
2683}
2684       
2685
2686
2687}
2688
2689package Mail::IMAPClient;
2690
2691
2692sub Authuser {
2693        my $self = shift;
2694       
2695        if (@_) { $self->{AUTHUSER} = shift }
2696        return $self->{AUTHUSER};
2697}
2698
2699
2700sub Split {
2701        my $self = shift;
2702       
2703        if (@_) { $self->{SPLIT} = shift }
2704        return $self->{SPLIT};
2705}
Note: See TracBrowser for help on using the repository browser.