#! /usr/bin/perl
#
#uw-mbox2courier-maildir.pl
# Version 0.1 by Ben Brillat <brillat-os@mail.brillat.net>
#             26 Mar 2001
#
# based on code by Bruce Guenter <bruceg@em.ca>
# and by Russell Nelson <nelson@qmail.org>
#
#Description:
# goes through a given home directory (first argument)
# and converts the messages in $HOME/Mailbox to messages in:
# $HOME/Maildir/
# After that, it loops through $HOME/mail and converts any
# MBOX files and directories that it finds in there to:
# $HOME/Maildir/.mbox-that-was-found/
# it does this recursively.
#
# This should successfully convert your average qmail/uw-imap user
# to a qmail/courier-imap user.  Which, from personal experience,
# is WAY faster.
#
#
#License:
#Copyright (C) 2001 Benjamin Brillat
#
#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.
#
#################################################################

require 'stat.pl';
use Cwd; #directory manipulation utilities
use User::pwent; #to get uid, gid from username

sub error {
    print STDERR join("\n", @_), "\n";
    exit(1);
}

sub open_msg {
    my($flags,$header,$xflags) = @_;
    if($flags) {
        if($flags =~ /RO/) { 
	    if($xflags =~ /A/){
		$fn = "cur/$stamp.$$.mbox:2,RS";
	    }
	    else{
		$fn = "cur/$stamp.$$.mbox:2,S"; 
	    }
	}
        elsif($flags =~ /O/) { $fn = "cur/$stamp.$$.mbox"; }
        else { $fn = "new/$stamp.$$.mbox"; }
    } else {
        $fn = "new/$stamp.$$.mbox";
    }
    $stamp++;
    close(OUT);
    open(OUT, ">$fn") || &error("unable to create new message");
    chown($uid,$gid,$fn) if defined($uid) && defined($gid);
    print OUT @$header, "\n";
}

sub convert_single {
    
    my($mbox, $mdir) = @_;

    &error("can't open mbox '$mbox'") unless
	open(SPOOL, $mbox);
    
    -d $mdir || mkdir $mdir,0700 ||
    &error("maildir '$mdir' doesn't exist and can't be created.");
    chown($uid,$gid,$mdir) if defined($uid) && defined($gid);
    chdir($mdir) || &error("fatal: unable to chdir to $mdir.");
    -d "tmp" || mkdir("tmp",0700) || &error("unable to make tmp/ subdir");
    -d "new" || mkdir("new",0700) || &error("unable to make new/ subdir");
    -d "cur" || mkdir("cur",0700) || &error("unable to make cur/ subdir");
    chown($uid,$gid,"tmp","new","cur") if defined($uid) && defined($gid);
    
    $stamp = time;
    
    
    $in_header = 0;
    while(<SPOOL>) {
	if(/^From /) {
	    open_msg($flags, \@header, $xflags) if $in_header;
	    undef $flags;
	    undef $xflags;
	    undef @header;
	    $in_header = 1;
	    push @header, "MBOX-Line: $_";
	}
	elsif($in_header) {
	    if(/^\s+$/o) {
		$in_header = 0;
		open_msg($flags, \@header, $xflags);
	    } else {
		$flags = $1 if /^Status:\s+(\S+)/oi;
		$xflags = $1 if /^X-Status:\s+(\S+)/oi;
		push @header, $_;
	    }
	} else {
	    s/^>From /From /;
	    print OUT || &error("unable to write to new message");
	}
    }
    close(SPOOL);
    open_msg($flags, \@header, $xflags) if $in_header;
    close(OUT);
    
}

#make a single empty maildir
#args: path, mode
sub make_empty_maildir {
    my($path, $mode) = @_;
    
    if(! -d $path) {
	mkdir($path, $mode) || &error("unable to make " . $path);
	mkdir($path . "/tmp", $mode) || &error("unable to make " . $path . "/tmp");
	mkdir($path . "/new", $mode) || &error("unable to make " . $path . "/new");
	mkdir($path . "/cur", $mode) || &error("unable to make " . $path . "/cur");
	chown($uid, $gid, $path, $path . "/tmp", $path . "/new", $path . "/cur");
    }
    else {
	&error("unable to make_maildir because " . $path . " already exists.");
    }
}

#this is a recursive function that will convert every mbox
#in a directory and all sub-directories.
#args: original_mbox_directory, new_maildir_directory, prefix_for_the_maildir
sub process_directory {
    my($mbdir, $mddir, $prefix) = @_;
    my(@contents);

    opendir(MB, $mbdir);
   # @contents = sort(grep !/^\.\.?$/, readdir(MB)); #from Prog in Perl pg 203
    @contents = sort(grep !/^\./, readdir(MB));
    closedir(MB);  #close the directory handle

    foreach(@contents){
	if( -d $mbdir . "/" . $_){ #if we found a directory
	    make_empty_maildir($mddir . $prefix . $_, 0700); #make a new empty maildir
	    process_directory($mbdir . "/" . $_, 
			      $mddir, 
			      $prefix . $_ . "."); #recurse
	}
	else { #it's not a directory
	    convert_single($mbdir . "/" . $_, $mddir . $prefix . $_); #convert the mbox
	}
    }

}


#this deletes any messges that say "DO NOT DELETE THIS MESSAGE -- FOLDER INTERNAL DATA"
#args: directory to process, message to look for
sub delete_old_folder_data {
    my($dir, $dmessage) = @_;
    my(@contents, $deleteme, $file2);

    $deleteme = 0;

    opendir(D2, $dir);
    @contents = sort(grep !/^\.\.?$/, readdir(D2)); #from Prog in Perl pg 203
    closedir(D2);  #close the directory handle

    foreach(@contents){
	if( -d $dir . "/" . $_){ #if we found a directory
	    #then recurse through its subdirectories
	    delete_old_folder_data($dir . "/" . $_, $dmessage);
	}
	else { #must be a file.  look for the message
	    $file2 = $dir . "/" . $_;
	    open(T2, $file2) || &error("unable to open file to look for DELETE THIS MESSAGE");
	    while(<T2>){
		if(/$dmessage/){
		    $deleteme = 1;
		}
	    }
	    close(T2);
	    if($deleteme){
		unlink($file2);
	    }
	}
	$deleteme = 0; #reset the deleting.
    }

    chown($uid, $gid, $subfile);
}


#this subscribes to all mailboxes.
#args: directory to process, file to save subscription info, subscription prefix
sub subscribe {
    my($dir, $subfile, $subpre) = @_;
    my(@contents);

    # unlink($subfile);

    opendir(D1, $dir);
    @contents = sort(grep !/^\.\.?$/, readdir(D1)); #from Prog in Perl pg 203
    closedir(D1);  #close the directory handle

    foreach(@contents){
	if(/new/ || /cur/ || /tmp/){
	    #do nothing
	}
	elsif( -d $dir . "/" . $_){ #if we found a directory
	    #then subscribe to it.
	    open(T1, ">>$subfile") || &error("unable to open subscribe file");
	    print T1 $subpre . $_ . "\n";
	    close(T1);
	    #and try to process subdirectories
	    subscribe($dir . "/" . $_, $subfile, $subpre . $_ . ".");
	}
    }

    chown($uid, $gid, $subfile);
}


sub usage {
    print STDERR "Usage: " . $0 . " username\n";
    exit -3;
}

##############################
# main
##############################


&usage(1) if ($#ARGV != 0);

#get their uid, gid, and home directory.
$pw = getpwnam($ARGV[0]) or die "No such user " . $ARGV[0] . "\n";
$uid = $pw->uid;
$gid = $pw->gid;
$home = $pw->dir;

#cd to the home directory
chdir($home);

#set up the primary Maildir
convert_single("Mailbox", "Maildir");

#make the Sent, Drafts, Trash, and Templates folders ONLY if they don't need
# to be converted.
chdir($home);
unless(-f "mail/Sent") {
    make_empty_maildir("Maildir/.Sent", 0700)
}
unless(-f "mail/Drafts") {
    make_empty_maildir("Maildir/.Drafts", 0700)
}
unless(-f "mail/Trash") {
    make_empty_maildir("Maildir/.Trash", 0700)
}
unless(-f "mail/Templates") {
    make_empty_maildir("Maildir/.Templates", 0700)
}


#iterate through the mailboxes - this is the recursive part.
process_directory($home . "/mail", $home . "/Maildir", "/.");

#then subscribe to everything
subscribe($home . "/Maildir", $home . "/Maildir/courierimapsubscribed", "INBOX");

#then look for the "DO NOT DELETE THIS MESSAGE -- FOLDER INTERNAL DATA" message
#delete any files that say that.
delete_old_folder_data($home . "/Maildir", "FOLDER INTERNAL DATA");


