summaryrefslogtreecommitdiff
path: root/scripts/Maildir++2IMAPdir.pl
blob: 0ff47c1159f36cac35d6dd36540951182789131f (plain)
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
#! /usr/bin/perl -w
#
# Maildir++2IMAPdir.pl (v0.2) - convert Maildir++ depots to IMAPdir depots
# Copyright (C) 2004  Henry Baragar mailto:Henry.Baragar@Instantiated.ca
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# WARNING:
#	All activity on the Maildir must be stopped before running this
#	script.  MAIL MAY BE LOST if you do not.  This includes both MTA's
#	(e.g. qmail, postfix) and MUA's (e.g. bincimap).
#
# WARNING:
#	There is no attempt to convert .qmail* files, .mailfilter or any 
# 	other files that affect mail delivery:  that is YOUR RESPONSIBILITY.
#
# This script converts a Maildir++ depot to an IMAPdir depot.  It is 
# invoked as follows:
#	Maildir++2IMAPdir Maildir IMAPdir
# where "-d" is used for printing debuggin information.
# Running this script produces the following directories:
#	Maildir.bak	- The original Maildir (untouched)
#	IMAPdir		- the new IMAPdir mail depot 
#	Maildir		- a new diretory with links into IMAPdir
# The purpose of the new Maildir directory is allow the conversion of 
# .qmail* and related scripts to be postponed until a less stressful time.
#
# The Maildir.bak directory can be deleted once you are comfortable that the
# IMAPdir is operational.  The Maildir directory can be removed once you are
# comfortable that you have converted all the mail delivery files.
#
# NOTE:
#	When run invoked from root, this script changes ID to the
#	owner of the IMAPdir as soon as possible.  That is, most
#	of the script is not run as root.
#

#
# We start by making sure that we are called correctly.
# Note that "Maildir" and "IMAPdir" will be supplied if they are missing.
#
$USAGE = "Usage: $0 [-d] [Maildir [IMAPdir]]\n";
$DEBUG = shift if @ARGV && $ARGV[0] eq "-d";
die $USAGE if grep /^-/, @ARGV;
$Maildir = shift || "Maildir";
$IMAPdir = shift || "IMAPdir";
die $USAGE if $ARGV[0];

#
# Make sure the environment is ready for conversion:
# - drop root privileges
# - make sure we don't clobber anything
# - make sure that we don't get messed up by a previous run that was aborted
#
@stats  = stat $Maildir or die "Can't stat $Maildir: $!";
$)=$stats[5];			# Change gid  (when root) 
$>=$stats[4];			# Change user (must do after setgid)
$mode = 07777 & $stats[2];
maildir("$Maildir")		or  die "$Maildir not a Maildir";
-e $IMAPdir			and die "$IMAPdir exists";
$Maildir_bak = "$Maildir.bak";
-e $Maildir_bak 		and die "$Maildir_bak exists";
$SHADOWdir = "$Maildir-conv-$$";
-e $SHADOWdir 			and die "$SHADOWdir exists";

#
# Find the source folders that need migrating and determine their targets.
# NB.  Linked folders need to be migrated after real folders since we might
#      need to create a real folder before creating the link 
#
opendir MAILDIR, $Maildir;
for my $folder (readdir MAILDIR) {
    next if $folder =~ /^[.]{2}$/;		# skip ".."
    next unless $folder =~ /^[.]/;
    my($source) = "$Maildir/$folder";
    my($target) = $IMAPdir."/".mapname($folder);
    next unless maildir($source);
    if (-l $source)	{ $linked{$source}  = $target; }
    else		{ $IMAPdir{$source} = $target; }
    $SHADOW{$target} = "$SHADOWdir/$folder";
    }
close MAILDIR;
if ($DEBUG) {
    print "IMAPdirs:\n";  print "\t$_ -> $IMAPdir{$_}\n" for sort keys %IMAPdir;
    print "Links:\n";     print "\t$_ <- $linked{$_}\n" for sort keys %linked;
    print "Shadow:\n";    print "\t$_ <- $SHADOW{$_}\n" for sort keys %SHADOW;
    }

#
# Migrate the folders, create the links and copy the .subscribe 
#
mkdir $SHADOWdir 		or  die "Can't make $SHADOWdir: $!";
mkdir $IMAPdir			or  die "Can't make $IMAPdir: $!";
migratefolder($_,$IMAPdir{$_}) for keys %IMAPdir;
linkfolder($_,$linked{$_}) for keys %linked;
symlink(abspath($_),$SHADOW{$_}) for keys %SHADOW;
symlink(abspath("$IMAPdir/INBOX/$_"),"$SHADOWdir/$_") for ("tmp","new","cur");
cpfiles(<$Maildir/.subscribed>,$IMAPdir);
chmod $mode, $SHADOWdir		or  die "Can't chmod $mode $SHADOWdir: $!";
chmod $mode, $IMAPdir		or  die "Can't chmod $mode $IMAPdir: $!";

#
# Success! (since we would have died if we had an error)
#
rename $Maildir, $Maildir_bak;
rename $SHADOWdir, $Maildir;

#
# maildir returns true if the suplied directory looks like IMAPdir
#
sub maildir {
    my ($dir) = shift;
    return unless -d $dir;
    return unless -d "$dir/tmp" && -d "$dir/new" && -d "$dir/cur";
    return $dir;
    }

#
# mapname maps a Maildir++ folder name to a IMAPdir folder name
#
sub mapname {
    my ($folder) = shift;
    return "INBOX" if $folder =~ /^[.]?$/;
    return "INBOX$folder" if $folder =~ /^[.]/;
    return "BOTCHED.$folder";
    }
#
#
# migratefolder migrates a Maildir folder from a Maildir++ depot to
# an IMAPdir depot
#
# Note that linkfolder should be used if the Maildir++ Maildir is a
# symbolic link
#
# Note that we link data files to preserve space (since they do not change).
#
sub migratefolder {
    my ($source,$target) = @_;
    mkdir $target || die "Can't create $target: $!";
    for my $subdir (("tmp","cur","new")) {
	my $tsub = "$target/$subdir";
	my $ssub = "$source/$subdir";
	mkdir $tsub or die "Can't create $tsub: $!";
	opendir SUBDIR, $ssub;
	for $file (readdir SUBDIR) {
	    next if $file =~ /^[.]{1,2}$/;		# skip "." and ".."
	    my ($sfile) = "$ssub/$file";
	    my ($tfile) = "$tsub/$file";
	    next unless -f $sfile;
	    link($sfile, $tfile) or die "Can't create link $sfile $tfile: $!";
	    }
	closedir SUBDIR;
	copyperms($ssub,$tsub);
	}
    cpfiles(bincfiles($source), "$target/");
    copyperms($source,$target);
    }

#
# copyperms copies the perms of one maildir to another
#
sub copyperms {
    my ($template,$target) = @_;
    my ($mode)  = 0777& (stat $template)[2] or die "stat'ing $template: $!";
    chmod $mode, $target;
    return if -l $target;
    chmod $mode, "$target/tmp", "$target/new", "$target/cur";
    }

#
# linkfolder
#
# Doing the right thing for symbolic links is tricky.
# There are three cases:
#   1.  The link points to another folder in the source's parents directory 
#	(i.e. the Maildir); in which case we need to make it point to the
#	new folder in the IMAPdir directory
#   2.	The link points to a subsubdirectory (or deeper) of the source's 
#	parent directory; in which case we need to copy the directory to
#	the IMAPdir directory (effectively eliminating the link)
#   3.	The link points somewhere else outside the source's parent directory;
#       in which case we simply make a new link to point to that directory
#
sub linkfolder {
    my ($source,$target) = @_;
    my ($abspath) = abspath($source);
    my ($sparent) = "$source/";
    $sparent =~ s{[^/]*/$}{};
    $sparent = abspath($sparent);
    print "$source->$target:\n\t$abspath\n\t$sparent\n\t" if $DEBUG;
    if ($abspath =~ m{^$sparent}) {
        my($relpath) = $abspath =~ m{^$sparent/(.*)};
	$relpath = "." unless $relpath;
	print "$relpath\n\t" if $DEBUG;
	if ($relpath eq "" or $relpath =~ m{^[.][^/]*$}) {
	    print "  case 1  \n" if $DEBUG;
	    symlink mapname($relpath)||"." , $target;
	    }
	else {
	    print "  case 2  \n" if $DEBUG;
	    migratefolder($source,$target);
	    }
	}
    else {
	print "  case 3  \n" if $DEBUG;
	symlink $abspath, $target;
	}
    }

#
# abspath determines the absolute path of a path, particular one that is 
# a symbolic link
#
sub abspath {
    my ($path) = @_;
    my ($cwd) = `pwd`;
    chomp $cwd;
    chdir $path or die "Can't cd to $path: $!";
    my ($abspath) = `pwd`;
    chomp $abspath;
    chdir $cwd or die "Can't cd to $cwd: $!";
    return $abspath;
    }

#
# cpfiles just calls the system cp
#
sub cpfiles {
    return if @_ < 2;
    system "cp", "-p", @_;
    }

#
# binfiles returns the list of binc* files in the supplied directory
#
sub bincfiles {
    my ($dir) = shift;
    return unless -d $dir;
    opendir DIR, $dir;
    my @bincfiles = grep /^binc/, readdir DIR;
    close DIR;
    return grep {s{^}{$dir/}} @bincfiles;
    }