File: migrate_mail2.pl

package info (click to toggle)
libmail-imapclient-perl 3.43-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, sid, trixie
  • size: 1,556 kB
  • sloc: perl: 21,079; makefile: 23
file content (326 lines) | stat: -rwxr-xr-x 11,186 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
#!/usr/local/bin/perl
#$Id$
#
# An example of how to migrate from a Netscape server
# (which uses a slash as a separator and which does
# not allow subfolders under the INBOX, only next to it)
# to a Cyrus server (which uses a dot (.) as a separator
# and which requires subfolders to be under "INBOX").
# There are also some allowed-character differences taken
# into account but this is by no means complete AFAIK.
#
# This is an example. If you are doing mail migrations 
# then this may in fact be a very helpful example but
# it is unlikely to work 100% correctly as-is. 
# A good place to start is by testing a rather large-volume
# transfer of actual mail from the source server with the
# -v option turned on and redirect output to a file for
# perusal. Examine the output carefully for unexpected
# results, such as a number of messages being skipped because
# they're already in the target folder when you know darn
# well this is the first time you ran the script. This
# would indicate an incompatibility with the logic for
# detecting duplicates, unless for some reason the source 
# mailbox contains a lot of duplicate messages to begin with.
# (The latter case is an example of why you should use an
# actual mailbox stuffed with actual mail for test; if you
# generate test messages and then test migrating those you
# will only prove that your test messages are migratable.
# 
# Also, you may need to play with the rules
# for translating folder names based on what kind of
# names your target server and source server support.
#
# You may also need to play with the logic that determines
# whether or not a message has already been migrated, 
# especially if your source server has messages that 
# did not come from an SMTP gateway or something like that.
#
# Some servers allow folders to contain mail and subfolders,
# some allow folders to only contain either mail or subfolders.
# If you are migrating from a "mixed use" type to a "single use"
# type server then you'll have to figure out how to deal 
# with this. (This script deals with this by creating folders like
# "/blah_mail", "/blah/blah_mail", and "/blah/blah/blah_mail"
# to hold mail if the source folder contains mail and subfolders
# and the target server supports only single-use folders. 
# You may not choose a different strategy.)
#
# Finally, it's possible that in some server-to-server
# copies, the source server supports messages that the
# target server considers unacceptable. For example, some
# but not all IMAP servers flat out refuse to accept 
# messages with "base newlines", which is to say messages
# whose lines are match the pattern /[^\r]\n$/. There is
# no logic in this script that deals with the situation;
# you will have to identify it if it exists and figure
# out how you want to handle it.
# 
# This is probably not an exhaustive list of issues you'll
# face in a migration, but it's a start.
#
# If you're just migrating from an old version to a newer
# version of the same server then you'll probably have
# a much easier time of it.
#
#

use Mail::IMAPClient;
use Data::Dumper;
use IO::File;
use File::Basename ;
use Getopt::Std;
use strict;
use vars qw/ 	$opt_B $opt_D $opt_T $opt_U
		$opt_W $opt_b $opt_d $opt_h
		$opt_t $opt_u $opt_w $opt_v
		$opt_s $opt_S $opt_W $opt_p
		$opt_P $opt_f $opt_F $opt_m 
		$opt_M
/;

getopts('vs:S:u:U:dDb:B:f:F:w:W:p:P:t:T:hm:M:');

if ( $opt_h ) {
	print STDERR <<"HELP";

$0 - 	an example script demonstrating the use of the Mail::IMAPClient's
	migrate method.

Syntax:
	$0 -s source_server -u source_user -w source_password -p source_port \
	   -d debug_source -f source_debugging_file -b source_buffsize       \
	   -t source_timeout -m source_auth_mechanism                        \
	   -S target_server -U target_user -W target_password -P target_port \
	   -D debug_target -F target_debugging_file -B target_buffsize       \
	   -T target_timeout -M target_auth_mechanism                        \
	   -v

where "source" refers to the "copied from" mailbox, target is the 
"copied to" mailbox, and -v turns on verbose output.
Authentication mechanisms default to "PLAIN".

HELP
	exit;
}
$opt_v and ++$|;
print "$0: Started at ",scalar(localtime),"\n" if $opt_v;

$opt_p||=143;
$opt_P||=143;

# Make a connection to the source mailbox:
my $imap = Mail::IMAPClient->new(
				Server  => $opt_s,
				User    => $opt_u,
				Password=> $opt_w,
				Uid	=> 1,	
				Port	=> $opt_p,	
				Debug	=> $opt_d||0,
				Buffer	=> $opt_b||4096,
				Fast_io	=> 1,
				( $opt_m ? ( Authmechanism => $opt_m) : () ),
				Timeout	=> $opt_t,	 
				($opt_f ? ( Debug_fh=>IO::File->new(">$opt_f" )) : ()),
) or die "$@";

# Make a connection to the target mailbox:
my $imap2 = Mail::IMAPClient->new(	
				Server  => $opt_S,
				User    => $opt_U,
				Password=> $opt_W,
				Port	=> $opt_P,
				Uid	=> 1,	
				Debug	=> $opt_D||0,
				( $opt_M ? ( Authmechanism => $opt_M) : () ),
				($opt_F ? ( Debug_fh=>IO::File->new(">$opt_F")) : ()),
				Buffer	=> $opt_B||4096,
				Fast_io	=> 1,
				Timeout	=> $opt_T,			 # True value
) or die "$@";

# Turn off buffering on debug files:
$imap->Debug_fh->autoflush;
$imap2->Debug_fh->autoflush;

# Get folder hierarchy separator characters from source and target:
my $sep1 = $imap->separator;
my $sep2 = $imap2->separator;

# Find out if source and target support subfolders inside INBOX:
my $inferiorFlag1 = $imap->is_parent("INBOX");
my $inferiorFlag2 = $imap2->is_parent("INBOX");

# Set up a test folders to see if the source and target support mixed-use
# folders (i.e. folders with both subfolders and mail messages):
my $testFolder1 = "Migrate_Test_$$" ;		# Ex: Migrate_Test_1234
$testFolder1 = $inferiorFlag2 ? 
		"INBOX" . $sep2 . $testFolder1  : 
		$testFolder1 ;

# The following folder will be a subfolder of $testFolder1:
my $testFolder2 = "Migrate_Test_$$" . $sep2 . "Migrate_test_subfolder_$$" ;
$testFolder2 = $inferiorFlag2 ? "INBOX" . $sep2 . $testFolder2  : $testFolder2 ;

$imap2->create($testFolder2) ;	# Create the subfolder first; RFC2060 dictates that
				# the parent folder should be created at the same time


# The following line inspired the selectable method.  It was also made obsolete by it,
# but I'm leaving it as is to demonstrate use of lower-level method calls:
my $mixedUse2 = grep(/NoSelect/i,$imap2->list("",$testFolder1))? 0 : 1;

# Repeat the above with the source mailbox:
$testFolder2 = "Migrate_Test_$$" . $sep1 . "Migrate_test_subfolder_$$" ;
$testFolder2 = $inferiorFlag1 ? "INBOX" . $sep1 . $testFolder1  : $testFolder1 ;

$imap->create($testFolder2) ;

my $mixedUse1 = grep(/NoSelect/i,$imap->list("",$testFolder1))? 0 : 1;

print 	"Imap host $opt_s:$opt_p uses a '$sep1' as a separator and ",
	( defined($inferiorFlag1) ? "allows " : "does not allow "), 
	"children in the INBOX. It supports ",
	($mixedUse1?"mixed use ":"single use "), "folders.\n" if $opt_v;

print 	"Imap host $opt_S:$opt_P uses a '$sep2' as a separator and ",
	( defined($inferiorFlag2) ? "allows " : "does not allow "), 
	"children in the INBOX. It supports ",
	($mixedUse2?"mixed use ":"single use "), "folders.\n" if $opt_v;

for ($testFolder1,$testFolder2) {$imap->delete($_); $imap2->delete($_);}

my($totalMsgs, $totalBytes) = (0,0);

# Now we will migrate the folder. Here we are doing one message at a time
# so that we can do more granular status reporting and error checking.
# A lazier way would be to do all the messages in one migrate method call
# (specifying "ALL" as the message number) but then we wouldn't be able
# to print out which message we were migrating and it would be a little 
# bit tougher to control checking for duplicates and stuff like that.
# We could also check the size of the message on the target right after 
# the migrate as an extra safety check if we wanted to but I didn't bother
# here. (I saved as an exercise for the reader. Yeah! That's it! An exercise!)

# Iterate over all the folders in the source mailbox:
for my $f ($imap->folders) { 
	# Select the folder on the source side:
	$imap->select($f) ; 

	# Massage the foldername into an acceptable target-side foldername:
	my $targF = "";
	my $srcF = $f;
	$srcF =~ s/^INBOX$sep1//i;
	if ( $inferiorFlag2 ) {
		$targF = $srcF eq "INBOX" ? "INBOX" : "INBOX.$f" ;
	} else {
		$targF = $srcF ;
	}

	$targF =~ s/$sep1/$sep2/go unless $sep1 eq $sep2;
	$targF =~ tr/#\$\& '"/\@\@+_/;
	if ( $imap->is_parent($f) and !$mixedUse2 ) {
		$targF .= "_mail" ;
	}
	print "Migrating folder $f to $targF\n" if $opt_v;

	# Create the (massaged) folder on the target side:
	unless ( $imap2->exists($targF) ) {
		$imap2->create($imap2->Massage($targF))
			or warn "Cannot create $targF on " . $imap2->Server . ": $@\n" and next;
	}

	# ... and select it
	$imap2->select($imap2->Massage($targF))  
			or warn "Cannot select $targF on " . $imap2->Server . ": $@\n" and next;

	# now that we know the target folder is selectable, we can close it again:
	$imap2->close; 
	my $count = 0;
	my $expectedTotal = $imap->message_count($f) ;

	# Now start iterating over all the messages on the source side...
	for my $msg ($imap->messages) {
		++$count; 
		my $h = "";
		# Get some basic info about the message:
		eval { $h = ($imap->parse_headers($msg,"Message-id")||{})->{'Message-id'}[0]};
		my $tsize = $imap->size($msg);
		my $ret = 0 ; my $h2 = [];

		# Make sure we didn't already migrate the message in a previous pass:
		$imap2->select($targF);
		if ( 	$tsize and $h and $h2 = $imap2->search( 
					HEADER 	=> 'Message-id'	=> $imap2->Quote($h),
					NOT 	=>  SMALLER 	=> $tsize, 
					NOT	=>  LARGER	=> $tsize
			)
		) {
			print  	
				"Skipping $f/$msg to $targF. ",
				"One or more messages (" ,join(", ",@$h2),
				") with the same size and message id ($h) ",
				"is already on the server. ",
				"\n" 
			if $opt_v;
			$imap2->close;

		} else {

			print  	
				"Migrating $f/$msg to $targF. ",
				"Message #$count of $expectedTotal has ",
				$tsize , " bytes.",
				"\n" if $opt_v;
			$imap2->close;

			# Migrate the message:
			my $ret = $imap->migrate($imap2,$msg,"$targF") ;
			$ret and ( $totalMsgs++ , $totalBytes += $tsize);
			$ret or warn "Cannot migrate $f/$msg to $targF on " . $imap2->Server . ": $@\n" ;
		}
	}
}

print "$0: Finished migrating $totalMsgs messages and $totalBytes bytes at ",scalar(localtime),"\n" 
	if $opt_v;
exit;


=head1 AUTHOR 
	
David J. Kernen

The Kernen Group, Inc.

imap@kernengroup.com

=head1 COPYRIGHT

This example and Mail::IMAPClient are Copyright (c) 2003 
by The Kernen Group, Inc. All rights reserved.

This example is distributed with Mail::IMAPClient and 
subject to the same licensing requirements as Mail::IMAPClient.

imtest is a utility distributed with Cyrus IMAP server, 
Copyright (c) 1994-2000 Carnegie Mellon University.  
All rights reserved. 

=cut

#$Log: migrate_mail2.pl,v $
#Revision 19991216.4  2003/06/12 21:38:33  dkernen
#
#Preparing 2.2.8
#Added Files: COPYRIGHT
#Modified Files: Parse.grammar
#Added Files: Makefile.old
#	Makefile.PL Todo sample.perldb
#	BodyStructure.pm
#	Parse.grammar Parse.pod
# 	range.t
# 	Thread.grammar
# 	draft-crispin-imapv-17.txt rfc1731.txt rfc2060.txt rfc2062.txt
# 	rfc2221.txt rfc2359.txt rfc2683.txt
#