Inhaltsverzeichnis
Dovecot Migration - cyrus2dovecot
Diese Dokumentation ist nach dem Kurs: Dovecot bei www.heinlein-support.de - Peer Heinlein entstanden. Hier noch einmal meinen Dank für die Informationen und das ☛ Buch: Dovecot ☚ |
HINWEIS - Die Nachfolgende Konfiguration von Dovecot setzt eine lauffähige Installation von Dovecot voraus, wie unter nachfolgendem internen Link beschrieben !!!
Dovecot ist ein Open-Source-IMAP-und POP3-E-Mail-Server für Linux bzw. UNIX-ähnlichen Systeme, entwickelt mit dem Hauptaugenmerk auf Sicherheit. Dovecot ist eine ausgezeichnete Wahl für kleine und große Installationen. Dovecot ist schnell und einfach zu installieren, erfordert keine besonderen Voraussetzungen und ist Ressourcenschonend.
Dovecot wird von Timo Sirainen entwickelt.
Beschreibung | Externer Link |
---|---|
Homepage | http://dovecot.org |
Dokumentation | http://dovecot.org/documentation.html |
Wiki Dovecot2 | http://wiki2.dovecot.org/ |
Ab hier werden root
-Rechte zur Ausführung der nachfolgenden Befehle benötigt. Um root
zu werden geben Sie bitte folgenden Befehl ein:
$ su - Password:
Vorbereitung
Es gibt wie immer verschiedene Möglichkeiten wie von einem zum anderen IMAP-Server eine Migration durchgeführt werden kann.
Nachfolgend sollen einige dieser Möglichkeiten aufgezeigt werden:
- Migration auf Dateiebene
- Migration auf Basis von IMAP-(Befehlen) wie z.B. einem Client der an beide IMAP-Server angebunden ist.
- Migration mit
doveadm
oder anderen Werkzeugen wie z.B. imapsync
Nachfolgende Gegebenheiten sollten bei einer Migration jedoch beachtet werden:
- UID'S sollten beibehalten werden
- Falls die UID's nicht erhalten bleiben, werden alle Clients sich erneut versuchen sich zu synchronisieren, was bei POP3 sicherlich problematischer wäre als bei IMAP ist.
- IMAP-Namesräume sollten sich nicht ändern
- Wie sind die Postfächer (Mailboxes) aufgebaut, mit . (Punkt) oder / (Schrägstrich) als Hirarchietrenner.
- Kann eine
offline
oder muss eineonline
Migration durchgeführt werden- Bei kleineren Installation ist ggf.
offline
möglich
Migrationsbeispiel
- Kleine Installation die
offline
durchgeführt werden kann - Es soll von einem Cyrus IMAPd zu Dovecot migriert werden
- Die Migration erfolgt vom Cyrus IMAPd IP: 192.168.0.180 zu Dovecot IP: 192.168.0.80
- Die Migration erfolgt auf Dateiebene
- Es wird nachfolgendes Skript der Freie Universität Berlin Cyrus2Dovecot verwendet
Voraussetzungen
Als Voraussetzung für die Installation des nachfolgenden Skriptes der Freie Universität Berlin Cyrus2Dovecot, ist folgende Komponente erforderlich:
- Ein installierte Version der Script-Sprache Perl ab Version 5.006
Herunterladen
Wie bereits erwähnt soll ein Skript das an der Freie Universität Berlin entwickelt wurde zur Migration verwendet werden.
Dieses Skript kann unter nachfolgendem externen Link heruntergeladen werden:
oder nachfolgender kompletter Quell-Code kann auf Textbasis kopiert werden.
#!/usr/bin/env perl # # $Id: cyrus2dovecot,v 1.2 2008/09/24 09:52:33 holger Exp $ # # Convert Cyrus folders to Dovecot. # # Written by Holger Weiss <holger@ZEDAT.FU-Berlin.DE> at Freie Universitaet # Berlin, Germany, Zentraleinrichtung fuer Datenverarbeitung (ZEDAT). # # ------------------------------------------------------------------------------ # Copyright (c) 2008 Freie Universitaet Berlin. # All rights reserved. # # This program is free software; you can redistribute it and/or modify it under # the same terms as Perl itself. See perlartistic(1). 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. # ------------------------------------------------------------------------------ # require 5.006; # We need Perl >= 5.6.0 for our open() calls. use warnings; use strict; use Data::Dumper; use File::Basename; use Fcntl qw(:seek); use Getopt::Long qw(:config gnu_getopt auto_help auto_version); use Sys::Hostname; # # Default settings (can be overridden on the command line). # # Within pathnames, any occurrence of "%u" will be replaced by the current user # name, any occurrence of "%<n>u" will be replaced by the <n>'th character of # that user name, any occurrence of "%h" will be replaced by Cyrus' directory # "hash" character for that user name (i.e., "%h" is equivalent to "%1u" if the # first character of the user name is a lowercase letter), and any occurrence of # "%x" will be replaced by Cyrus' "fulldirhash" character for that user name. # # See "perldoc cyrus2dovecot" for details. # my %DEFAULT = ( dovecot_inbox => '/tmp/dovecot/%u/Maildir', cyrus_inbox => '/var/spool/imap/user/%u', cyrus_sub => '/var/imap/user/%h/%u.sub', cyrus_seen => '/var/imap/user/%h/%u.seen', cyrus_quota => undef, cyrus_quota_format => 1, # Cyrus quota format (1 for legacy). dovecot_uidlist_format => 3, # Create this dovecot-uidlist format. dovecot_host => hostname, # The host name for Maildir++ filenames. dovecot_crlf => 0, # Use CR+LF instead of LF in Dovecot? default_quota => 0, # Use this quota as a fallback. dump_meta => 0, # Print the metadata structure? debug => 0, # Print debug output? quiet => 0, # Suppress the standard output? edit_foldernames => [] # List of folder name substitutions. ); # # Plan of attack: Basically, we convert the folders of a user in two steps. # # 1) We call c_read_mailbox() which reads Cyrus' metadata for all folders of the # user and creates a data structure such as the following. In this example, # the user has the folder "ac" and the subfolder "ac/dc" next to his INBOX, # he is subscribed to these folders, and there are two e-mails per folder. # The user also defined a few IMAP keywords (a.k.a. user flags). Both the # system flags and user keywords are saved as bitmasks for each e-mail. # # $meta->{subscriptions} = [ 'ac', 'ac/dc' ] # ->{quota} = 2147483648 # ->{box}->{'INBOX'}->{uidvalidity} = 1107601073 # ->{uidnext} = 3 # ->{nonrecent} = 2 # Last non-recent UID. # ->{keywords} = [ 'Junk', '$Label1' ] # ->{mail}->{1}->{internaldate} = 1107601472 # ->{sysflags} = $sysmask # ->{usrflags} = $usrmask # {2}->{internaldate} = 1107601543 # ->{sysflags} = $sysmask # ->{usrflags} = $usrmask # ->{'ac'} ->{uidvalidity} = 1108639232 # ->{uidnext} = 3 # ->{nonrecent} = 1 # Last non-recent UID. # ->{keywords} = [ 'Private', 'Work' ] # ->{mail}->{1}->{internaldate} = 1108639290 # ->{sysflags} = $sysmask # ->{usrflags} = $usrmask # {2}->{internaldate} = 1108639299 # ->{sysflags} = $sysmask # ->{usrflags} = $usrmask # ->{'ac/dc'}->{uidvalidity} = 1109821442 # ->{uidnext} = 3 # ->{nonrecent} = 1 # Last non-recent UID. # ->{keywords} = [ 'Rock', 'Pop' ] # ->{mail}->{1}->{internaldate} = 1109821455 # ->{sysflags} = $sysmask # ->{usrflags} = $usrmask # {2}->{internaldate} = 1109821500 # ->{sysflags} = $sysmask # ->{usrflags} = $usrmask # # 2) We call d_write_mailbox() which creates a Dovecot Maildir++ directory # including all subfolders, writes the metadata, and converts the actual # e-mails. # sub main (); sub usage (); sub debug (@); sub info (@); sub warning (@); sub error (@); sub fatal (@); sub message ($@); sub makedir ($); sub readint ($$); sub xread ($$$); sub slurp ($); sub fixpath ($@); sub c_fulldirhash ($); sub c_read_skiplist ($); sub c_read_mailbox ($$$$$$); sub d_write_mailbox ($$$); # # IMAP flags. # use constant FLAG_ANSWERED => (1 << 0); # As stored in Cyrus' index database. use constant FLAG_FLAGGED => (1 << 1); # As stored in Cyrus' index database. use constant FLAG_DELETED => (1 << 2); # As stored in Cyrus' index database. use constant FLAG_DRAFT => (1 << 3); # As stored in Cyrus' index database. use constant FLAG_SEEN => (1 << 4); # Stored in Cyrus' seen database. # # Cyrus skiplist database constants. # use constant INORDER => 1; use constant ADD => 2; use constant DELETE => 4; use constant COMMIT => 255; use constant DUMMY => 257; use constant HEADER_SIZE => 28; use constant HEADER_MAGIC_SIZE => 20; use constant HEADER_MAGIC => "\241\002\213\015skiplist file\0\0\0"; # # Cyrus mailbox header constants. # use constant MAILBOX_HEADER_MAGIC_SIZE => 115; use constant MAILBOX_HEADER_MAGIC => "\241\002\213\015Cyrus mailbox header\n" . "\"The best thing about this system was that it had lots of goals.\"\n" . "\t--Jim Morris on Andrew\n"; # Gesundheit! # # Miscellaneous constants. # use constant UINT32_MAX => 4294967295; our $VERSION = sprintf('%d.%d (%04d-%02d-%02d)', q$Revision: 1.2 $ =~ /(\d+)/g, q$Date: 2008/09/24 09:52:33 $ =~ /(\d{4})\/(\d{2})\/(\d{2})/); my $MYSELF = basename($0); my ($FOLDERS, $MAILS, $SIZE, $USER, $FROM_STDIN, $QUOTADATA, %CONF); GetOptions( \%CONF, 'edit_foldernames|edit-foldernames|E=s@', 'cyrus_inbox|cyrus-inbox|C=s', 'cyrus_quota|cyrus-quota|Q=s', 'cyrus_quota_format|cyrus-quota-format|O=i', 'cyrus_seen|cyrus-seen|S=s', 'cyrus_sub|cyrus-sub|U=s', 'dovecot_host|dovecot-host|H=s', 'dovecot_inbox|dovecot-inbox|D=s', 'default_quota|default-quota|N=s', 'dovecot_uidlist_format|dovecot-uidlist-format|F=i', 'dovecot_crlf|dovecot-crlf|c', 'dump_meta|dump-meta|m', 'debug|d', 'quiet|q', 'h', 'v' ) or usage; if ($CONF{h}) { exec($0, '--help') or die; } if ($CONF{v}) { exec($0, '--version') or die; } foreach my $opt (keys %DEFAULT) { $CONF{$opt} = $DEFAULT{$opt} if not exists($CONF{$opt}); } fatal('Option dovecot-uidlist-format must be set to: 1 or 3') if ($CONF{dovecot_uidlist_format} != 1 and $CONF{dovecot_uidlist_format} != 3); $SIG{__WARN__} = sub { fatal('Caught exception:', @_) }; # Perl warnings. $FROM_STDIN = (@ARGV == 0) ? 1 : 0; $QUOTADATA = c_read_skiplist($CONF{cyrus_quota}) if ($CONF{cyrus_quota} and $CONF{cyrus_quota_format} != 1); main; exit 0; # ----- Generic subroutines. --------------------------------------------------- # # Loop over the specified users and convert their e-mails. This is done within # a subroutine in order to make it callable from error(). # sub main () { while (my $user = $FROM_STDIN ? <STDIN> : shift(@ARGV)) { chomp($user); my $start = time; my $meta = {}; my $dovecot_inbox = $CONF{dovecot_inbox}; my $cyrus_inbox = $CONF{cyrus_inbox}; my $cyrus_seen = $CONF{cyrus_seen}; my $cyrus_sub = $CONF{cyrus_sub}; my $cyrus_quota = $CONF{cyrus_quota}; debug("Converting the e-mail folders of $user."); # (Re)set "global" variables. $USER = $user; $FOLDERS = $MAILS = $SIZE = 0; # Resolve "%u", "%h", "x", and "%<n>u" within pathnames. fixpath($user, $dovecot_inbox, $cyrus_inbox, $cyrus_seen, $cyrus_sub); fixpath($user, $cyrus_quota) if ($CONF{cyrus_quota} and $CONF{cyrus_quota_format} == 1); # Do the actual conversion. c_read_mailbox($meta, $user, $cyrus_inbox, $cyrus_seen, $cyrus_sub, $cyrus_quota); d_write_mailbox($meta, $cyrus_inbox, $dovecot_inbox); # Give some feedback. print Dumper($meta) if $CONF{dump_meta}; info(sprintf('%u messages in %u folders (%.1f MiB, %u s)', $MAILS, $FOLDERS, $SIZE / 1024 / 1024, time - $start)) unless $CONF{quiet}; debug("Done converting the e-mail folders of $user."); } } # # Print usage information to the standard error output and die. # sub usage () { exec("$0 --help >&2") or die; } # # Print a message to the standard output if we were called with "--debug", do # nothing otherwise. # sub debug (@) { return unless $CONF{debug}; my @message = @_; info(@message); } # # Print a message to the standard output. # sub info (@) { my @message = @_; message(\*STDOUT, @message); } # # Print a message to the standard error output. # sub warning (@) { my @message = @_; unshift(@message, '(warning)'); message(\*STDERR, @message); } # # Print a message to the standard error output. Then, try to continue with the # next user (if any). When done, exit >0. # sub error (@) { my @message = @_; unshift(@message, '(error)'); message(\*STDERR, @message); main; # Continue with the next user (if any). exit 1; } # # Print a message to the standard error output and exit >0 immediately. # sub fatal (@) { my @message = @_; unshift(@message, '(fatal)'); message(\*STDERR, @message); exit 1; } # # Print a message. # sub message ($@) { my $handle = shift; my @message = @_; my $prefix = $MYSELF; $prefix .= " [$USER]" if defined($USER); chomp(@message); print $handle "$prefix: @message\n"; } # # Create the specified directory, and recursively create parent directories as # needed. Die on error. # sub makedir ($) { my $dir = shift; unless (-d $dir) { my $parent = dirname($dir); makedir($parent) if not -d $parent; mkdir($dir) or error("Cannot create directory $dir: $!"); debug('Created directory:', $dir); } } # # Read and return a 32-bit integer which is in "network" (big-endian) order. # Die on error. # sub readint ($$) { my ($file, $handle) = @_; my $buf = xread($file, $handle, 4); return unpack('N', $buf); } # # Read the specified number of bytes or die. # sub xread ($$$) { my ($file, $handle, $size) = @_; my ($buf, $n); defined($n = read($handle, $buf, $size)) or error("Cannot read $file: $!"); error("Read $n instead of $size bytes from $file.") if $n != $size; return $buf; } # # Read (the rest of) a file into memory and return the read content. # sub slurp ($) { my $handle = shift; local $/; # Slurp mode. return <$handle>; } # # Resolve "%u", "%h", "x", and "%<n>u" within pathnames. Modify the specified # arguments directly (as opposed to returning the new pathnames). # sub fixpath ($@) { my $user = shift; my @char = split(//, $user); my $hash = $char[0]; my $fullhash = c_fulldirhash($user); if ($hash !~ /^[a-z]$/) { # This is how Cyrus "hashes" non-[a-z]-characters. $hash = ($hash =~ /^[A-Z]$/) ? lc($hash) : 'q'; } for (@_) { s/%u/$user/g; s/%h/$hash/g; s/%x/$fullhash/g; s/%(\d+)u/$char[$1-1]/g; } } # ----- Cyrus subroutines. ----------------------------------------------------- sub _c_read_folders ($$$$); sub _c_read_header ($); sub _c_read_index ($); sub _c_read_old_seen ($); sub _c_read_legacy_quota ($); sub _c_read_skiplist_item ($$); sub _c_parse_seendata ($); sub _c_seen ($$); sub _c_make_uid ($$); # # Return Cyrus' "fulldirhash" character for the given user name. See also the # "dir_hash_c" subroutine in tools/rehash. # sub c_fulldirhash ($) { my $user = shift; my $n = 0; $n = (($n << 3) ^ ($n >> 5)) ^ ord($_) for split(/ */, $user); return chr(ord('A') + ($n % 23)); } # # Read a "skiplist" database file (or a flat text file) and return a reference # to a hash containing the records. Die on error. # sub c_read_skiplist ($) { # # | /* # | * disk format; all numbers in network byte order # | * # | * there's the data file, consisting of the multiple records of "key", # | * "data", and "skip pointers", where skip pointers are the record number of # | * the data pointer. [...] # | */ # | # | /* # | header "skiplist file\0\0\0" # | version (4 bytes) # | version_minor (4 bytes) # | maxlevel (4 bytes) # | curlevel (4 bytes) # | listsize (4 bytes) # | in active items # | log start (4 bytes) # | offset where log records start, used mainly to tell when to compress # | last recovery (4 bytes) # | seconds since unix epoch # | # | 1 or more skipnodes, one of: # | # | record type (4 bytes) [DUMMY, INORDER, ADD] # | key size (4 bytes) # | key string (bit string, rounded to up to 4 byte multiples w/ 0s) # | data size (4 bytes) # | data string (bit string, rounded to up to 4 byte multiples w/ 0s) # | skip pointers (4 bytes each) # | least to most # | padding (4 bytes, must be -1) # | # | record type (4 bytes) [DELETE] # | record ptr (4 bytes; record to be deleted) # | # | record type (4 bytes) [COMMIT] # | # | record type is either # | DUMMY (first node is of this type) # | INORDER # | ADD # | DELETE # | COMMIT (commit the previous records) # | */ # | # | enum { # | INORDER = 1, # | ADD = 2, # | DELETE = 4, # | COMMIT = 255, # | DUMMY = 257 # | }; # | # | #define HEADER_MAGIC ("\241\002\213\015skiplist file\0\0\0") # | #define HEADER_MAGIC_SIZE (20) # # [ lib/cyrusdb_skiplist.c ] # my $file = shift; my $skiplist = {}; my ($buf, $n); if (not -e $file) { # # The seen or subscription database will not be created until # the user saw an e-mail or subscribed a folder. So, we just # return an empty skiplist. # debug("$file doesn't exist, I'll pretend it's empty."); return $skiplist; } debug('Reading:', $file); open(my $handle, '<', $file) or error("Cannot open $file: $!"); # Read and check the header magic. defined($n = read($handle, $buf, HEADER_MAGIC_SIZE)) or error("Cannot read $file: $!"); if ($n == HEADER_MAGIC_SIZE and $buf eq HEADER_MAGIC) { # Read the actual header. $buf = xread($file, $handle, HEADER_SIZE); my @header = unpack('N7', $buf); error('Unknown skiplist DB version:', $header[0]) if $header[0] != 1; debug('Minor skiplist DB version:', $header[1]); # Read the records. while ($n = read($handle, $buf, 4)) { error("Read $n instead of 4 bytes from $file.") if $n != 4; my $rectype = unpack('N', $buf); # Parse the record type. if ($rectype == COMMIT) { debug('Record type: COMMIT'); next; } elsif ($rectype == DELETE) { debug('Record type: DELETE'); seek($handle, 4, SEEK_CUR) or error('Cannot seek in:', $file); next; } elsif ($rectype == INORDER) { debug('Record type: INORDER'); } elsif ($rectype == ADD) { debug('Record type: ADD'); } elsif ($rectype == DUMMY) { debug('Record type: DUMMY'); } else { error('Unknown record type:', $rectype); } # Read and save the key and the data, if any. my $key = _c_read_skiplist_item($file, $handle); my $data = _c_read_skiplist_item($file, $handle); if (defined($key)) { $skiplist->{$key} = $data; $data = '(undef)' if not defined($data); debug("Saved skiplist record: $key = $data"); } # # Skip the "skip pointers" (4 bytes each), terminated by # 4 bytes of -1 padding. # 1 while readint($file, $handle) != 0xFFFFFFFF; } } else { # Assume it's a flat text file if the header magic is missing. debug('Parsing as a flat text file:', $file); seek($handle, 0, SEEK_SET) or error('Cannot seek in:', $file); while (<$handle>) { my ($key, $data) = /(.*)\t(.*)/; error("Cannot parse $file.") if not (defined($key) and defined($data)); $skiplist->{$key} = $data; debug("Saved flat file record: $key = $data"); } } close($handle) or error("Cannot close $file: $!"); debug('Done reading:', $file); return $skiplist; } # # Read all global and folder-specific metadata of a user into the given data # structure. Die on error. # sub c_read_mailbox ($$$$$$) { my ($meta, $user, $rootdir, $seenfile, $subfile, $quotafile) = @_; my (%existing, @subscriptions); my $seendata = ($seenfile eq 'cyrus.seen') ? undef : c_read_skiplist($seenfile); debug('Reading Cyrus metadata.'); _c_read_folders($meta, $rootdir, $rootdir, $seendata); # # Cyrus' subscription skiplist keys are in the form "user.<name>.<box>", # where <name> is the user name and <box> the name of the subscribed # folder. We only save the latter (or "INBOX" for "user.<name>"). Note # that the subscription skiplist values are empty. Cyrus doesn't clean # up subscriptions of folders which no longer exist (as per RFC 3501, # 6.3.6.). While at it, we check whether a folder exist before adding # it to the list in order to straighten the subscriptions up. # foreach my $folder (keys %{ $meta->{box} }) { $folder =~ s/\//./g; $existing{$folder} = 1; } foreach my $folder (keys %{ c_read_skiplist($subfile) }) { $folder =~ s/^user\.$user$/INBOX/; $folder =~ s/^user\.[^\.]+\.(.+)$/$1/; $folder =~ s/\//./g; # Probably not necessary. push(@subscriptions, $folder) if $existing{$folder}; } $meta->{subscriptions} = \@subscriptions; debug('Subscribed folders:', @subscriptions); # # Save the user's quota limit, either from Cyrus or using the specified # default quota. # if ($quotafile) { if ($CONF{cyrus_quota_format} == 1) { $meta->{quota} = _c_read_legacy_quota($quotafile); } elsif (defined($QUOTADATA->{"user.$user"})) { $meta->{quota} = $QUOTADATA->{"user.$user"}; $meta->{quota} =~ s/^\d+\s+(\d+)$/$1/; error('Cannot parse quota:', $QUOTADATA->{"user.$user"}) if $meta->{quota} !~ /^\d+$/; } warning('No quota information available.') if (not $meta->{quota} and not $CONF{default_quota}); } if ($meta->{quota}) { $meta->{quota} *= 1024; # Kilobytes to bytes, } else { $meta->{quota} = $CONF{default_quota} || 0; } debug('Quota:', $meta->{quota}); debug('Done reading Cyrus metadata.'); } # # Read and save the folder-specific metadata for the given mailbox into the # given data structure. Recurse into subdirectories. Die on error. # sub _c_read_folders ($$$$) { my ($meta, $rootdir, $boxpath, $seendata) = @_; my $box = ($boxpath eq $rootdir) ? 'INBOX' : $boxpath; my ($mailfolder, $index, $seen); # # Currently, we wouldn't be able to distinguish user.foo.INBOX from # user.foo. While this could be fixed, a Maildir/.INBOX folder could # not be accessed via Dovecot, anyway. # error('Cannot convert non-INBOX folder named INBOX.') if $box =~ /^$rootdir\/+INBOX$/; # Let $box hold the path relative to the root directory (or "INBOX"). $box =~ s/^$rootdir\/+//; debug('Looking at folder:', $box); if (-e "$boxpath/cyrus.header" and -e "$boxpath/cyrus.index") { # Collect this folder's metadata. my $header = _c_read_header($boxpath); $index = _c_read_index($boxpath); $header->{folderuid} = _c_make_uid($index->{uidvalidity}, $box) if not $header->{folderuid}; $seen = defined($seendata) ? _c_parse_seendata($seendata->{$header->{folderuid}}) : _c_parse_seendata(_c_read_old_seen($boxpath)); # Autovivify "mail" in case this folder contains no e-mails. $meta->{box}->{$box}->{mail} = {}; $meta->{box}->{$box}->{nonrecent} = $seen->{nonrecent}; $meta->{box}->{$box}->{uidvalidity} = $index->{uidvalidity}; $meta->{box}->{$box}->{uidnext} = $index->{lastuid} + 1; $meta->{box}->{$box}->{keywords} = [ split(/\s+/, $header->{userflags}) ]; debug('The UIDVALIDITY is:', $index->{uidvalidity}); debug('The last message UID is:', $index->{lastuid}); debug('The last non-recent message UID is:', $seen->{nonrecent}); $mailfolder = 1; } else { # # The folder we're in is not in a Cyrus mailbox. However, we # don't simply return here because this folder might contain # other folders which might be Cyrus mailboxes (unless we're in # the INBOX, in which case something is going wrong). # error('No Cyrus INBOX at:', $boxpath) if $box eq 'INBOX'; debug("Skipping $boxpath as it's not a Cyrus mailbox."); $mailfolder = 0; } opendir(my $handle, $boxpath) or error("Cannot open $boxpath: $!"); while (my $file = readdir($handle)) { next if $file =~ /^\.\.?$/; next if $file =~ /^cyrus\.(?:header|index|cache|seen)$/; my $path = "$boxpath/$file"; if (-d $path) { # Recurse into subfolders. _c_read_folders($meta, $rootdir, $path, $seendata); } elsif ($mailfolder and $file =~ /^(\d+)\.$/) { my $uid = $1; my $attr; if ($index->{$uid}) { # Save the e-mail's flags and its INTERNALDATE. debug("Saving attributes of: $box/$file"); $attr->{usrflags} = $index->{$uid}->{usrflags}; $attr->{sysflags} = $index->{$uid}->{sysflags}; $attr->{internaldate} = $index->{$uid}->{internaldate}; } else { my @statlist = stat($path); warning("Index record missing for: $box/$file"); error('Cannot stat(2) message file:', $path) if not defined($statlist[9]); $attr->{usrflags} = 0; $attr->{sysflags} = 0; $attr->{internaldate} = $statlist[9]; } $attr->{sysflags} |= FLAG_SEEN if _c_seen($uid, $seen); $meta->{box}->{$box}->{mail}->{$uid} = $attr; } else { warning("Skipping $box/$file, dunno what it is."); } } closedir($handle) or error("Cannot close $boxpath: $!"); debug('Done with folder:', $box); } # # Read a mailbox header file and return a reference to a hash which holds the # data. Die on error. See doc/internal/mailbox-format.html for details on the # format of the mailbox header file. # sub _c_read_header ($) { # # | This file contains mailbox-wide information that does not change that often. # | Its format: # | # | <Mailbox Header Magic String> # | <Quota Root>\t<Mailbox Unique ID String>\n # | <Space-separated list of user flags>\n # | <Mailbox ACL>\n # # [ doc/internal/mailbox-format.html ] # my $boxpath = shift; my $file = "$boxpath/cyrus.header"; my ($header, $buf); debug('Reading:', $file); open(my $handle, '<', $file) or error("Cannot open $file: $!"); # Read and check the header magic. $buf = xread($file, $handle, MAILBOX_HEADER_MAGIC_SIZE); error("Cannot parse $file: Mailbox header magic incorrect") if $buf ne MAILBOX_HEADER_MAGIC; # Slurp the rest of the file into memory and close it. $buf = slurp($handle); close($handle) or error("Cannot close $file: $!"); # Guess the header file format and save the data into a hash. if ($buf =~ /^([^\t\n]*)\t([^\n]+)\n([^\n]*)\n([^\n]*)\n$/) { $header->{quotaroot} = $1; $header->{folderuid} = $2; $header->{userflags} = $3; $header->{folderacl} = $4; } elsif ($buf =~ /^([^\n]*)\n([^\n]*)\n([^\n]*)\n$/) { $header->{quotaroot} = $1; $header->{folderuid} = 0; # No mailbox UID provided. $header->{userflags} = $2; $header->{folderacl} = $3; } else { error("Cannot parse $file: One or more fields missing"); } $header->{userflags} =~ s/\s+$//; # Cyrus adds a trailing space. debug('Mailbox UID:', $header->{folderuid}); debug('Quota root:', $header->{quotaroot}); # Unused. debug('Mailbox ACL:', $header->{folderacl}); # Unused. debug('User-defined keywords:', $header->{userflags}); debug('Done reading:', $file); return $header; } # # Read a mailbox index file and return a reference to a hash which holds the # interesting data. Die on error. See doc/internal/mailbox-format.html for # details on the format of the mailbox index file. See also: # # imap/mailbox.h and imap/mailbox.c:mailbox_read_index_header() # sub _c_read_index ($) { my $boxpath = shift; my $file = "$boxpath/cyrus.index"; my ($data, $buf, $n); debug('Reading:', $file); open(my $handle, '<', $file) or error("Cannot open $file: $!"); # Read and save the interesting header fields. seek($handle, 8, SEEK_SET) or error('Cannot seek in:', $file); my $version = readint($file, $handle); my $headersize = readint($file, $handle); my $recordsize = readint($file, $handle); debug('Index format version:', $version); seek($handle, 8, SEEK_CUR) or error('Cannot seek in:', $file); $data->{lastuid} = readint($file, $handle); # Skip 4 additional bytes for 64-bit quotas in Cyrus 2.2 and newer. seek($handle, ($version < 6) ? 8 : 12, SEEK_CUR) or error('Cannot seek in:', $file); $data->{uidvalidity} = readint($file, $handle); # # As we try to parse future index file formats (i.e., we don't bail out # if the $version is unknown), we do at least a dumb consistency check: # Cyrus sets the UIDVALIDITY of a folder to its creation date, so let's # make sure its value is >= 600000000 (1989-01-05 11:40:00). # error("Cannot parse $file: UIDVALIDITY is", $data->{uidvalidity}) if $data->{uidvalidity} < 600000000; seek($handle, $headersize, SEEK_SET) or error('Cannot seek in:', $file); # Read and save the interesting fields of all records. while ($n = read($handle, $buf, 4)) { error("Read $n instead of 4 bytes from $file.") if $n != 4; my $uid = unpack('N', $buf); debug('Reading index record for message UID:', $uid); # Read, save, and "check" (see above) the INTERNALDATE. $data->{$uid}->{internaldate} = readint($file, $handle); error("Cannot parse $file: INTERNALDATE is", $data->{$uid}->{internaldate}) if $data->{$uid}->{internaldate} < 600000000; # # Read and save the system and user flags. Note that the size # of the user flags bitmask is MAX_USER_FLAGS / 32. At least in # Cyrus 2.2.12, MAX_USER_FLAGS is 128 by default, so we'll read # 4 bytes. If this is ever changed, the number of bytes to skip # at the end of this loop (currently $recordsize - 40) must be # adjusted accordingly. # seek($handle, 24, SEEK_CUR) or error('Cannot seek in:', $file); $data->{$uid}->{sysflags} = readint($file, $handle); $data->{$uid}->{usrflags} = readint($file, $handle); # Skip the rest of the record. seek($handle, $recordsize - 40, SEEK_CUR) or error('Cannot seek in:', $file); } close($handle) or error("Cannot close $file: $!"); debug('Done reading:', $file); return $data; } # # Read a Cyrux 1.x seen file and return the seen UIDs string or undef if the # seen file is empty. Die on error. # sub _c_read_old_seen ($) { my $boxpath = shift; my $file = "$boxpath/cyrus.seen"; debug('Reading:', $file); open(my $handle, '<', $file) or error("Cannot open $file: $!"); my @seen = <$handle>; close($handle) or error("Cannot close $file: $!"); chomp(@seen); error("Cannot parse $file: File contains multiple lines") if @seen > 1; debug('Done reading:', $file); return $seen[0]; } # # Read a legacy quota file and return the quota limit (specified in kilobytes) # or undef if the specified file does not exist. Die on error. # sub _c_read_legacy_quota ($) { my $file = shift; if (not -e $file) { debug('Legacy quota file does not exist:', $file); return undef; } debug('Reading:', $file); open(my $handle, '<', $file) or error("Cannot open $file: $!"); my @quota = <$handle>; close($handle) or error("Cannot close $file: $!"); chomp(@quota); error("Cannot parse $file: Not in legacy quota format") if (@quota != 2 or $quota[1] !~ /^\d+$/); debug('Done reading:', $file); return $quota[1]; } # # Read a skiplist string and return the string or undef if the string's length # is zero. Die on error. # sub _c_read_skiplist_item ($$) { my ($file, $handle) = @_; # Read the item size and the actual item. my $size = readint($file, $handle); my $item = xread($file, $handle, $size); # Skip four-byte-alignment padding, if any. seek($handle, (($size + 3) & 0xFFFFFFFC) - $size, SEEK_CUR) or error("Cannot seek skiplist (item size: $size)."); return ($size > 0) ? $item : undef; } # # Parse the seen data for a mailbox and return a reference to a hash which holds # the seen message UIDs (saved in a format optimized for fast _c_seen() lookups) # and the last non-recent message UID. Die on error. # sub _c_parse_seendata ($) { # # The third field of the seen data contains the last non-recent message UID, and # the fifth field contains a seen UIDs string which is in the following format # (see doc/internal/database-formats.html for a description of all seen data # fields): # # | /* # | * Format of the seenuids string: # | * # | * no whitespace, n:m indicates an inclusive range (n to m), otherwise # | * list is comma separated of single messages, e.g.: # | * # | * 1:16239,16241:17015,17019:17096,17098,17100 # | */ # # [ imap/index.c ] # # See also: imap/seen_db.c:seen_readit() # my $seendata = shift; my $seen; if (defined($seendata)) { my @fields = split(/\s+/, $seendata, 5); debug('Parsing seen data:', $seendata); error('Cannot parse seen data:', $seendata) if @fields != 5; $fields[4] =~ s/\s+//g; # Cyrus sometimes adds a trailing tab. foreach my $uid (split(/,/, $fields[4])) { debug('Parsing seen UID(s):', $uid); if ($uid =~ /^\d+$/) { $seen->{$uid} = 1; } elsif ($uid =~ /^(\d+):(\d+)$/) { my ($n, $m) = ($2 > $1) ? ($1, $2) : ($2, $1); push(@{ $seen->{ranges} }, { min => $n, max => $m }); } else { error('Cannot parse seen UID(s):', $uid); } } $seen->{nonrecent} = $fields[2]; } else { $seen->{nonrecent} = 0; $seen->{ranges} = []; } return $seen; } # # Return true if the message with the given UID is seen, false otherwise. # sub _c_seen ($$) { my ($uid, $seen) = @_; return 1 if $seen->{$uid}; foreach my $range (@{ $seen->{ranges} }) { return 1 if ($uid >= $range->{min} and $uid <= $range->{max}); } return 0; } # # Calculate and return Cyrus' internal mailbox UID. See also: # # imap/mailbox.c:mailbox_make_uniqueid() # sub _c_make_uid ($$) { my ($uidvalidity, $box) = @_; my $hash = 0; if ($box eq 'INBOX') { $box = "user.$USER"; } else { $box =~ s/\//./g; $box = "user.$USER.$box"; } foreach my $character (split(//, $box)) { $hash *= 251; $hash += ord($character); $hash %= 2147484043; } my $uid = sprintf("%08lx%08lx", $hash, $uidvalidity); debug("Calculated mailbox UID for $box: $uid"); return $uid; } # ----- Dovecot subroutines. --------------------------------------------------- sub _d_make_maildir ($); sub _d_touch_maildirfolder ($); sub _d_write_maildirsize ($$); sub _d_write_subscriptions ($$); sub _d_write_keywords ($$); sub _d_create_filename ($$$$); # # Create the Maildir++ folder including all subfolders, write the metadata, and # convert the actual e-mails. # sub d_write_mailbox ($$$) { my ($meta, $c_rootdir, $d_rootdir) = @_; # # The Maildir++ filenames we create include a random number. We call # srand(3) in order to re-create the same filenames if the conversion # is repeated for some reason. # srand($meta->{box}->{'INBOX'}->{uidvalidity}); debug('Writing Dovecot folders.'); foreach my $c_box (keys %{ $meta->{box} }) { my ($c_boxpath, $d_boxpath); my $d_box = $c_box; if ($d_box eq 'INBOX') { $c_boxpath = $c_rootdir; } else { $c_boxpath = "$c_rootdir/$c_box"; $d_box =~ s/\//./g; $d_box = ".$d_box"; } # Edit the Maildir++ folder name if desired. foreach my $operation (@{ $CONF{edit_foldernames} }) { debug("Editing folder name $d_box: $operation"); my $r = eval("\$d_box =~ $operation"); error("Cannot evaluate $operation: $@") if $@; error("Cannot evaluate $operation.") if not defined($r); debug('New folder name:', $d_box); } $d_boxpath = ($d_box eq 'INBOX') ? $d_rootdir : "$d_rootdir/$d_box"; # Create an empty Maildir. _d_make_maildir($d_boxpath); _d_touch_maildirfolder($d_boxpath) unless $d_box eq 'INBOX'; my $box = $meta->{box}->{$c_box}; my $uidfile = "$d_boxpath/dovecot-uidlist"; # Open the dovecot-uidlist file. open(my $ufh, '>', $uidfile) or error("Cannot open $uidfile: $!"); # Write the dovecot-uidlist header line. if ($CONF{dovecot_uidlist_format} == 1) { print $ufh "1 $box->{uidvalidity} $box->{uidnext}\n" or error("Cannot write $uidfile: $!"); } elsif ($CONF{dovecot_uidlist_format} == 3) { print $ufh "3 V$box->{uidvalidity} N$box->{uidnext}\n" or error("Cannot write $uidfile: $!"); } else { error('Unknown dovecot-uidlist format:', $CONF{dovecot_uidlist_format}); } # Handle all e-mails in this folder. foreach my $uid (sort {$a <=> $b} keys %{ $box->{mail} }) { my $c_mailpath = "$c_boxpath/$uid."; my $d_temppath = "$d_boxpath/tmp/$MYSELF.$$.$MAILS"; # Convert the e-mail. open(my $cfh, '<', $c_mailpath) or error("Cannot open $c_mailpath: $!"); open(my $dfh, '>', $d_temppath) or error("Cannot open $d_temppath: $!"); while (<$cfh>) { s/\r\n/\n/ unless $CONF{dovecot_crlf}; print $dfh $_ or error("Cannot write $d_temppath: $!"); } close($cfh) or error("Cannot close $c_mailpath: $!"); close($dfh) or error("Cannot close $d_temppath: $!"); # # Create the Maildir++ e-mail filename. Include the # size fields used by Dovecot: # # | ,S=<size>: <size> contains the file size. Getting # | the size from the filename avoids doing a stat(), # | which may improve the performance. This is # | especially useful with Maildir++ quota. # | # | ,W=<vsize>: <vsize> contains the file's RFC822.SIZE, # | i.e. the file size with linefeeds being CR+LF # | characters. If the message was stored with CR+LF # | linefeeds, <size> and <vsize> are the same. Setting # | this may give a small speedup because now Dovecot # | doesn't need to calculate the size itself. # # [ http://wiki.dovecot.org/MailboxFormat/Maildir ] # my @c_stat = stat($c_mailpath); my @d_stat = stat($d_temppath); error('Cannot stat(2) message file:', $c_mailpath) if not defined($c_stat[7]); error('Cannot stat(2) message file:', $d_temppath) if not defined($d_stat[7]); my $attr = $box->{mail}->{$uid}; my $size = $d_stat[7]; # File size with LF. my $vsize = $c_stat[7]; # File size with CR+LF. my $subdir = ($uid > $box->{nonrecent}) ? 'new' : 'cur'; my $d_mail = _d_create_filename($attr, $size, $vsize, $#{ $box->{keywords} }); my $d_mailpath = "$d_boxpath/$subdir/$d_mail"; error("Cannot rename $d_temppath to: $d_mailpath") if not rename($d_temppath, $d_mailpath); # Set the e-mail's last access and modification times. utime($attr->{internaldate}, $attr->{internaldate}, $d_mailpath); # Add the e-mail to the dovecot-uidlist. if ($CONF{dovecot_uidlist_format} == 1) { print $ufh "$uid $d_mail\n" or error("Cannot write $uidfile: $!"); } elsif ($CONF{dovecot_uidlist_format} == 3) { print $ufh "$uid :$d_mail\n" or error("Cannot write $uidfile: $!"); } else { error('Unknown dovecot-uidlist format:', $CONF{dovecot_uidlist_format}); } $SIZE += $size; $MAILS++; } close($ufh) or error("Cannot close $uidfile: $!"); _d_write_keywords($box->{keywords}, $d_boxpath); $FOLDERS++; } _d_write_subscriptions($meta->{subscriptions}, $d_rootdir); _d_write_maildirsize($d_rootdir, $meta->{quota}) if $meta->{quota}; debug('Done writing Dovecot folders.'); } # # Create the specified Maildir, including the "new, "cur", and "tmp" Maildir # subdirectories as well as all parent directories as needed. Die on error. # sub _d_make_maildir ($) { my $maildir = shift; my $new = "$maildir/new"; my $cur = "$maildir/cur"; my $tmp = "$maildir/tmp"; debug('Creating Maildir:', $maildir); makedir($_) for $maildir, $new, $cur, $tmp; } # # Create an empty "maildirfolder" file within the given Maildir. Die on error. # sub _d_touch_maildirfolder ($) { my $maildir = shift; my $file = "$maildir/maildirfolder"; open(my $handle, '>', $file) or error("Cannot touch $file: $!"); close($handle) or error("Cannot close $file: $!"); debug('Touched:', $file); } # # Write the maildirsize file. Die on error. See also: # # http://www.inter7.com/courierimap/README.maildirquota.html # sub _d_write_maildirsize ($$) { my ($rootdir, $quota) = @_; my $file = "$rootdir/maildirsize"; debug('Writing:', $file); open(my $handle, '>', $file) or error("Cannot open $file: $!"); print $handle $quota . "S\n"; print $handle $SIZE . " $MAILS\n"; close($handle) or error("Cannot close $file: $!"); debug('Done writing:', $file); } # # Write the subscriptions file. Die on error. # sub _d_write_subscriptions ($$) { my ($subscriptions, $rootdir) = @_; my $file = "$rootdir/subscriptions"; debug('Writing:', $file); open(my $handle, '>', $file) or error("Cannot open $file: $!"); foreach my $subscription (@$subscriptions) { # Add a leading dot for "--edit-foldernames". $subscription = ".$subscription" unless $subscription eq 'INBOX'; # Usually not necessary. # Edit the subscribed folder name if desired. foreach my $operation (@{ $CONF{edit_foldernames} }) { debug("Editing subscription $subscription: $operation"); my $r = eval("\$subscription =~ $operation"); error("Cannot evaluate $operation: $@") if $@; error("Cannot evaluate $operation.") if not defined($r); debug('New subscription:', $subscription); } # Remove the leading dot we added (if there still is one). $subscription =~ s/^\.//; print $handle "$subscription\n" or error("Cannot write $file: $!"); debug('Subscribed:', $subscription); } close($handle) or error("Cannot close $file: $!"); debug('Done writing:', $file); } # # Write the dovecot-keywords file. Die on error. # sub _d_write_keywords ($$) { my ($keywords, $boxpath) = @_; my $file = "$boxpath/dovecot-keywords"; return if @$keywords == 0; # No keywords defined. debug('Writing:', $file); open(my $handle, '>', $file) or error("Cannot open $file: $!"); for (my $i = 0; $i <= $#{ $keywords }; $i++) { print $handle "$i $keywords->[$i]\n" or error("Cannot write $file: $!"); debug('Added keyword:', $keywords->[$i]); } close($handle) or error("Cannot close $file: $!"); debug('Done writing:', $file); } # # Create and return a Maildir++ e-mail filename including the flags and the size # fields used by Dovecot. # sub _d_create_filename ($$$$) { my ($attr, $size, $vsize, $maxkeyword) = @_; my @alphabet = ('a' .. 'z'); my $filename = sprintf('%u.R%08xQ%u.%s,S=%u,W=%u:2,', $attr->{internaldate}, int(rand(UINT32_MAX)), $MAILS + 1, $CONF{dovecot_host}, $size, $vsize); $filename .= 'S' if $attr->{sysflags} & FLAG_SEEN; $filename .= 'R' if $attr->{sysflags} & FLAG_ANSWERED; $filename .= 'F' if $attr->{sysflags} & FLAG_FLAGGED; $filename .= 'T' if $attr->{sysflags} & FLAG_DELETED; $filename .= 'D' if $attr->{sysflags} & FLAG_DRAFT; $maxkeyword = $#alphabet if $maxkeyword > $#alphabet; for (my $i = 0; $i <= $maxkeyword; $i++) { $filename .= $alphabet[$i] if $attr->{usrflags} & (1 << $i); } debug('Created new Maildir++ filename:', $filename); return $filename; } __END__ =head1 NAME cyrus2dovecot - convert Cyrus folders to Dovecot =head1 SYNOPSIS B<cyrus2dovecot> [B<-cdmq>] S<[B<-C> I<cyrus-inbox>]> S<[B<-D> I<dovecot-inbox>]> S<[B<-E> I<edit-foldernames>]> S<[B<-F> I<dovecot-uidlist-format>]> S<[B<-H> I<dovecot-host>]> S<[B<-N> I<default-quota>]> S<[B<-O> I<cyrus-quota-format>]> S<[B<-Q> I<cyrus-quota>]> S<[B<-S> I<cyrus-seen>]> S<[B<-U> I<cyrus-sub>]> [I<user> ...] B<cyrus2dovecot> B<-h> E<verbar> B<-v> =head1 DESCRIPTION B<cyrus2dovecot> converts the e-mails of one or more I<user>s from Cyrus format to Dovecot Maildir++ folders. If no I<user> is specified, the I<user> names are read from the standard input, one per line. Message C<UID>s, C<INTERNALDATE>s, IMAP folder subscriptions, the C<UIDVALIDITY> and C<UIDNEXT> values for each folder, as well as all IMAP flags (including the first 26 user-defined keywords) are preserved during the conversion. The generated e-mail filenames include the Maildir++ extensions C<S=E<lt>sizeE<gt>> and C<W=E<lt>vsizeE<gt>> (which are used by Dovecot for better performance). Optionally, Maildir++ F<maildirsize> files are created. =head1 OPTIONS Within the specified I<PATH>s, any occurrence of C<%u> will be replaced by the current I<user> name, any occurrence of C<%I<n>u> will be replaced by the I<n>'th character of that I<user> name, any occurrence of C<%h> will be replaced by Cyrus' directory "hash" character for that I<user> name (i.e., C<%h> is equivalent to C<%1u> if the first character of the I<user> name is a lowercase letter), and any occurrence of C<%x> will be replaced by Cyrus' "fulldirhash" character for that I<user> name. However, within the specified B<--cyrus-quota> I<PATH> (if any), these replacements will only be done if the B<--cyrus-quota-format> I<VERSION> is set to C<1>. The default settings can be found (and modified) at the top of the B<cyrus2dovecot> script. =over 8 =item B<-C>, B<--cyrus-inbox=>I<PATH> Use this I<PATH> to the I<user>'s INBOX folder in Cyrus. =item B<-c>, B<--dovecot-crlf> Store e-mails with C<CR+LF> instead of plain C<LF>. This flag should be specified if the C<mail_save_crlf> option is set to C<yes> in the Dovecot configuration. =item B<-D>, B<--dovecot-inbox=>I<PATH> Use this I<PATH> to the I<user>'s INBOX folder in Dovecot. =item B<-d>, B<--debug> Print information which is usually only useful for debugging to the standard output. =item B<-E>, B<--edit-foldernames=>I<SUBSTITUTION> Apply the specified I<SUBSTITUTION> to the name of each Maildir++ folder and subscription using Perl code such as S<C<eval('$name=~'.$substitution)>>, where $name holds either the string F<INBOX> (which denotes the main Maildir) or the full Maildir++ folder name (e.g., F<.sub.folder>), and $substitution holds the specified I<SUBSTITUTION>. The resulting $name will be used as the Maildir++ folder's name. This option may be specified multiple times, in which case each of the I<SUBSTITUTION>s will be applied to each Maildir++ folder name in the order specified on the command line. Note that while Dovecot stores the subscribed folder names without the leading "." of Maildir++ subfolders, B<cyrus2dovecot> adds a leading "." to each subscribed subfolder name before applying the specified I<SUBSTITUTION>(s) and removes it afterwards (if it still exists) in order to simplify the matching. =item B<-F>, B<--dovecot-uidlist-format=>I<VERSION> Create the F<dovecot-uidlist> files using this format I<VERSION>. For Dovecot releases older than 1.0.2, I<VERSION> 1 must be specified; otherwise, I<VERSION> 3 can be used. =item B<-H>, B<--dovecot-host=>I<NAME> Use this host I<NAME> for the Maildir++ e-mail file's basename. =item B<-h>, B<--help> Print usage information to the standard output and exit. =item B<-m>, B<--dump-meta> Print a dump of the data structure which holds the metadata gathered from scanning the Cyrus folders of a user to the standard output. =item B<-N>, B<--default-quota=>I<BYTES> Create a Maildir++ F<maildirsize> file for each I<user>, and set the quota limit to the specified number of I<BYTES> unless B<--cyrus-quota> is also specified, in which case a I<user>-specific quota would override the B<--default-quota> limit. Specifying C<0> I<BYTES> disables the creation of F<maildirsize> files unless B<--cyrus-quota> is also specified. =item B<-O>, B<--cyrus-quota-format=>I<VERSION> Expect the quota database file specified via B<--cyrus-quota> to be present in this format I<VERSION>, where I<VERSION> C<1> denotes the "quotalegacy" format and I<VERSION> C<2> denotes the "skiplist" or the "flat" text format (B<cyrus2dovecot> will autodetect which of those two formats is used if I<VERSION> 2 is specified). This option is ignored if B<--cyrus-quota> is not specified. =item B<-Q>, B<--cyrus-quota=>I<PATH> Use this I<PATH> to the quota database file in Cyrus, and create a Maildir++ F<maildirsize> file for each I<user> whose quota limit is found in that file. =item B<-q>, B<--quiet> Suppress the line usually printed to the standard output for each I<user> whose e-mails were successfully converted. Error messages, if any, will still be printed to the standard error output. =item B<-S>, B<--cyrus-seen=>I<PATH> Use this I<PATH> to the I<user>'s seen database file in Cyrus. If F<cyrus.seen> is specified as the I<PATH>, B<cyrus2dovecot> expects an old-style F<cyrus.seen> file in every Cyrus folder. =item B<-U>, B<--cyrus-sub=>I<PATH> Use this I<PATH> to the I<user>'s subscription database file in Cyrus. =item B<-v>, B<--version> Print version information to the standard output and exit. =back =head1 RETURN VALUE B<cyrus2dovecot> exits 0 on success. If a non-fatal error occurs, B<cyrus2dovecot> prints a message to the standard error output and then tries to convert the e-mails of the remaining I<user>s (if any), but it exits E<gt>0 regardless of whether or not those conversions succeed. If a fatal error occurs, B<cyrus2dovecot> exits E<gt>0 immediately. =head1 EXAMPLES Given that the default settings specified at the top of the B<cyrus2dovecot> script are correct and that F</tmp/users> holds the names of all I<user>s whose e-mails should be converted (one per line), the following command would convert all e-mails of those I<user>s from Cyrus to Dovecot: cyrus2dovecot < /tmp/users Given that the path to the INBOX in Cyrus is F</var/spool/imap/user/%u> (where C<%u> denotes the I<user> name), that Cyrus stores the seen and subscription databases within the directory F</var/imap/user/%h>, and that Cyrus stores "quotalegacy" files within the directory F</var/imap/quota/%h> (where C<%h> denotes Cyrus' directory "hash" character for that I<user> name, respectively), the following command would convert all e-mails of the I<user>s "bill" and "george" from Cyrus to Dovecot, and the result would be stored below F</tmp/dovecot> (including F<maildirsize> files for both users if their quota limits are found): cyrus2dovecot --cyrus-inbox /var/spool/imap/user/%u \ --cyrus-seen /var/imap/user/%h/%u.seen \ --cyrus-sub /var/imap/user/%h/%u.sub \ --cyrus-quota /var/imap/quota/%h/user.%u \ --cyrus-quota-format 1 \ --dovecot-inbox /tmp/dovecot/%u/Maildir \ bill george A script such as the following could be used in order to convert all e-mails of all I<user>s (of course, the pathnames and the desired quota limit may have to be adjusted, and if the C<hashimapspool> option is enabled in the Cyrus configuration, F</?> must be appended to the $in path): #!/bin/sh in=/var/spool/imap/user # Cyrus INBOXes. db=/var/imap/user/? # Cyrus seen/subscription files. out=/tmp/dovecot # Dovecot Maildirs. log=/tmp/conversion.log # Log of successful conversions. err=/tmp/error.log # Log of conversion errors. quota=2147483648 # 2 GiB quota (for maildirsize). for u in `find $in/. \! -name . -prune -exec basename \{\} \;` do cyrus2dovecot --cyrus-inbox $in/$u \ --cyrus-seen $db/$u.seen \ --cyrus-sub $db/$u.sub \ --default-quota $quota \ --dovecot-inbox $out/$u/Maildir \ $u 2>&1 >>$log | tee -a $err >&2 done In order to create all folders (except for the INBOX) as subfolders of the INBOX in Dovecot, the following argument could be added to the B<cyrus2dovecot> command line: --edit-foldernames 's/^\./.INBOX./' Cyrus transparently replaces any "." character in folder names with a "^" character. Dovecot supports "." characters in Maildir++ folder names if the "listescape" plugin is used, which replaces any "." character in folder names with the string "\2e". The following argument could be added to the B<cyrus2dovecot> command line in order to replace any "^" character in Cyrus folder names with "\2e" for the Maildir++ folder name: --edit-foldernames 's/\^/\\2e/g' Dovecot 1.1 and newer support using folders such as F<Maildir/sub/folder> (as opposed to F<Maildir/.sub.folder>) if C<:LAYOUT=fs> was added to the C<mail_location> in the Dovecot configuration. The following B<cyrus2dovecot> arguments could be specified in order to create such folders by removing the leading dot from Maildir++ subfolder names and then substituting any following dots with slashes: --edit-foldernames 's/^\.//' \ --edit-foldernames 's/\./\//g' If the seen states, subscriptions, or quotas are stored in Berkeley databases, they must first be converted for B<cyrus2dovecot> using a command such as the following: cvt_cyrusdb /var/imap/user/b/bill.seen berkeley \ /tmp/imap/user/b/bill.seen skiplist =head1 CAVEATS B<cyrus2dovecot> assumes that the user has no e-mails in Dovecot yet and that neither his Cyrus folders nor his Dovecot folders will be accessed by another process during the conversion. If C<%I<n>u> is specified within any I<PATH> on the command line, all I<user> names must have a length of at least I<n> characters. Otherwise, B<cyrus2dovecot> will die with an exception. If folder name substitutions are specified via B<--edit-foldernames>, the resulting Maildir++ folder names must be unique. =head1 RESTRICTIONS Cyrus' seen and subscription databases must be present either in the "skiplist" format or in the "flat" text format, and Cyrus' quota database(s) (if any) must be present either in one of those formats or in the "quotalegacy" format, as B<cyrus2dovecot> doesn't support Berkeley databases. However, Berkeley databases can be converted to one of the supported formats using cvt_cyrusdb(8), see the L</EXAMPLES>. In F<maildirsize> files created by B<cyrus2dovecot>, no limit for the number of messages is specified (as such a limit does not seem useful). Cyrus' ACL settings are not converted. =head1 COMPATIBILITY B<cyrus2dovecot> is supposed to work with all Cyrus releases up to (at least) version 2.3.x. So far, it has been tested with Cyrus 1.4, 2.1.18, 2.2.12, and 2.3.12p2. =head1 SEE ALSO Other tools for converting e-mails from Cyrus to Dovecot can be found at L<http://wiki.dovecot.org/Migration/Cyrus>. =head1 AUTHOR Written by Holger WeiE<szlig> E<lt>holger@ZEDAT.FU-Berlin.DEE<gt> at Freie UniversitE<auml>t Berlin, Germany, Zentraleinrichtung fE<uuml>r Datenverarbeitung (ZEDAT). =head1 COPYRIGHT AND LICENSE Copyright (c) 2008 Freie UniversitE<auml>t Berlin. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L<perlartistic>. 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. =head1 HISTORY $Log: cyrus2dovecot,v $ Revision 1.2 2008/09/24 09:52:33 holger Message seen states are now parsed more efficiently with regard to performance and memory usage. Apart from that, minor code cleanups have been applied. Revision 1.1 2008/09/22 08:36:44 holger Initial release.
Installation
/tmp/cyrus2dovecot
Nach dem erfolgreichen herunterladen oder kopieren des Skriptes, sollte das Skript unter nachfolgendem Verzeichnis zu finden sein, oder angelegt werden:
/tmp/cyrus2dovecot
da das Skript nur zur Migration benötigt wird.
Um das Skript nun einfach ausführen zu können, ist es erforderlich die entsprechenden Dateirechte zu setzen, was mit nachfolgendem Befehl durchgeführt werden kann:
# chmod +x /tmp/cyrus2dovecot
Ein Überprüfung, ob dies erfolgreich war, kann mit nachfolgendem Befehl durchgeführt werden:
# ll /tmp/cyrus2dovecot -rwxr-xr-x 1 root root 52213 Apr 24 16:35 /tmp/cyrus2dovecot
Konfiguration
Nachfolgende Ausführungen beziehen sich auf eine Cyrus IMAPd Installation unter dem Betriebssystem CentOS ab Verison 6.x voraus, wie z.B. unter nachfolgendem internen Link beschrieben:
* Abweichende oder ältere Cyrus IMAPd oder CentOS Installation sind ähnlich, bitte entsprechende Verzeichnisangaben prüfen !
Nachfolgender Befehl listet die Parameter für das Skript Cyrus2Dovecot auf:
# /tmp/cyrus2dovecot -h Usage: cyrus2dovecot [-cdmq] [-C *cyrus-inbox*] [-D *dovecot-inbox*] [-E *edit-foldernames*] [-F *dovecot-uidlist-format*] [-H *dovecot-host*] [-N *default-quota*] [-O *cyrus-quota-format*] [-Q *cyrus-quota*] [-S *cyrus-seen*] [-U *cyrus-sub*] [*user* ...] cyrus2dovecot -h | -v Usage: cyrus2dovecot [-cdmq] [-C *cyrus-inbox*] [-D *dovecot-inbox*] [-E *edit-foldernames*] [-F *dovecot-uidlist-format*] [-H *dovecot-host*] [-N *default-quota*] [-O *cyrus-quota-format*] [-Q *cyrus-quota*] [-S *cyrus-seen*] [-U *cyrus-sub*] [*user* ...] cyrus2dovecot -h | -v
Hier die Parameter im Überblick:
Parameter | Defaultwert | Beschreibung |
---|---|---|
dovecot_inbox | /tmp/dovecot/%u/Maildir | Verzeichnisbaum der Maildir-Struktur |
cyrus_inbox | /var/spool/imap/user/%u | Cyrus: Postfach (Mailbox) des Benutzer |
cyrus_sub | /var/imap/user/%h/%u.sub | Cyrus: Ordnerstruktur-Datei des Benutzers |
cyrus_seen | /var/imap/user/%h/%u.seen | Cyrus: Gelesen-Flag-Datei des Benutzers |
cyrus_quota | <undef> | Cyrus: Default Quota-Wert |
cyrus_quota_format | 1 | Cyrus: Quota Format |
dovecot_uidlist_format | 3 | Dovecot: Erstellen des dovecot-uidlist Format |
dovecot_host | hostname | Dovecot: Hostname für MaildirDateinamen Dateinamen |
dovecot_inbox | /tmp/dovecot/%u/Maildir | Verzeichnisbaum der Maildir-Struktur |
user | klaus | Anmeldename des Benutzers |
%u = Benutzername | %h = Erster Buchstabe des Benutzernamens (wie %1u)
WICHTIG - Bevor es zur Ausführung kommen sollte, ist es dringend notwendig die Verzeichnisse und Dateinamen zu überprüfen:
Nachfolgende Tabelle zeigt noch einmal die Umsetzung der Verzeichnispfade und Dateinamen:
Parameter | Wert | Verzeichnisumsetzung |
---|---|---|
cyrus_inbox | /var/spool/imap/%h/user/%u | /var/spool/imap/k/user/klaus |
cyrus_seen | /var/lib/imap/user/%h/%u.seen | /var/lib/imap/user/k/klaus.seen |
cyrus_sub | /var/lib/imap/user/%h/%u.sub | /var/lib/imap/user/k/klaus.sub |
cyrus_quota | /var/lib/imap/quota/%h/user.%u | /var/lib/imap/quota/k/user.klaus |
Nachfolgende Befehle sollten der Reihe nach ausgeführt werden, um die Verzeichnispfade und Dateinamen zu überprüfen:
# ls -l /var/spool/imap/k/user/klaus total 71712 -rw------- 1 cyrus mail 17748 May 1 2012 1. ... -rw------- 1 cyrus mail 2749 May 1 2012 8976. -rw------- 1 cyrus mail 448512 Apr 24 10:30 cyrus.cache -rw------- 1 cyrus mail 208 Mar 23 21:40 cyrus.header -rw------- 1 cyrus mail 24912 Apr 24 10:30 cyrus.index -rw------- 1 cyrus mail 1518101 Apr 24 16:55 cyrus.squat drwx------ 2 cyrus mail 4096 Apr 24 16:55 Drafts drwx------ 2 cyrus mail 4096 Apr 24 16:56 Sent drwx------ 2 cyrus mail 4096 Apr 24 16:56 Junk drwx------ 2 cyrus mail 4096 Apr 24 17:01 Trash
# ls -l /var/lib/imap/user/k/klaus.seen -rw------- 1 cyrus mail 10708 Apr 24 17:52 /var/lib/imap/user/k/klaus.seen
# ls -l /var/lib/imap/user/k/klaus.sub -rw------- 1 cyrus mail 991 Mar 9 09:26 /var/lib/imap/user/k/klaus.sub
# ls -l /var/lib/imap/quota/k/user.klaus -rw------- 1 cyrus mail 17 Apr 24 17:52 /var/lib/imap/quota/k/user.klaus
Skript Aufruf
Nachfolgend der Befehlsaufruf:
# /tmp/cyrus2dovecot --cyrus-inbox /var/spool/imap/%h/user/%u --cyrus-seen /var/lib/imap/user/%h/%u.seen --cyrus-sub /var/lib/imap/user/%h/%u.sub --cyrus-quota /var/lib/imap/quota/%h/user.%u --cyrus-quota-format 2 --dovecot-host rechner80.dmz.tachtler.net --dovecot-inbox /tmp/dovecot/%u/Maildir klaus cyrus2dovecot [klaus]: (warning) Skipping Junk/cyrus.squat, dunno what it is. cyrus2dovecot [klaus]: (warning) Skipping INBOX/cyrus.squat, dunno what it is. cyrus2dovecot [klaus]: (warning) Skipping Sent/cyrus.squat, dunno what it is.. cyrus2dovecot [klaus]: (warning) Skipping Drafts/cyrus.squat, dunno what it is. cyrus2dovecot [klaus]: (warning) Skipping Trash/cyrus.squat, dunno what it is. cyrus2dovecot [klaus]: (warning) No quota information available. cyrus2dovecot [klaus]: 960 messages in 5 folders (72.1 MiB, 2 s)
HINWEIS - Die Meldungen (warning) können ignoriert werden, da diese von nachfolgender Konfiguration des Cyrus IMAPd stammen, siehe nachfolgenden internen Link
Squatter (warning)
Nachfolgend die relevante Erklärung zu den (warning) Ausgaben:
Im Bereich EVENTS{}
des Cyrus IMAPd, sind Dienste und Hilfsprogramme definiert, welche in regelmäßigen Abständen ausgeführt werden.
Hier wurde in diesem Beispiel ein Dienst bzw. ein Hilfsprogramm hinzugefügt, welches sich squatter
nennt.
Dieses Programm legt für jede Mailbox einen sogenannten „Squat-Volltext-Index“ an. Dadurch kann der Mail-Client bei einer Suche die betreffende Nachricht innerhalb der Mailbox schneller finden.
Nachfolgend der relevante Ausschnitt des Cyrus IMAPd aus der Konfigurationsdatei
/etc/cyrus.conf
(Nur relevanter Ausschnitt)
... EVENTS { # this is required checkpoint cmd="ctl_cyrusdb -c" period=30 # this is only necessary if using duplicate delivery suppression, # Sieve or NNTP delprune cmd="cyr_expire -E 3" at=0400 # this is only necessary if caching TLS sessions tlsprune cmd="tls_prune" at=0400 # Tachtler # this enables to build a squat-index, for faster search results # for better performance start with a high nice value squatter cmd="/bin/nice -n 19 /usr/lib/cyrus-imapd/squatter -r *" period=180 } ...
Maildir Verzeichnis
Nach dem Aufruf des Skriptes Cyrus2Dovecot sollte nun eine Verzeichnisstruktur unterhalb des Verzeichnisses
/tmp
mit nachfolgendem Inhalt einstanden sein, was mit nachfolgendem Befehl überprüft werden kann:
# ls -l /tmp/ total 4 -rwxr-xr-x 1 root root 104423 Apr 23 14:23 cyrus2dovecot drwxr-xr-x 3 root root 4096 Apr 24 18:04 dovecot
Die Hierarchie des Ordners /tmp/dovecot
sollte wie folgt skizziert aussehen:
/tmp/dovecot/ +-----------> klaus +-----> Maildir
Um in die das Verzeichnis /tmp/dovecot/klaus/Maildir
zu wechseln kann nachfolgender Befehl genutzt werden:
cd /tmp/dovecot/klaus/Maildir
Ob das Maildir-Verzeichnis korrekt aufgebaut wurde, kann mit einer Auflistung des Verzeichnisses /tmp/dovecot/klaus/Maildir
mit nachfolgendem Befehl überprüft werden:
# ll -la total 84 drwxr-xr-x 30 root root 4096 Apr 24 18:04 . drwxr-xr-x 3 root root 4096 Apr 24 18:04 .. drwxr-xr-x 2 root root 36864 Apr 24 18:04 cur -rw-r--r-- 1 root root 30 Apr 24 18:04 dovecot-keywords -rw-r--r-- 1 root root 21817 Apr 24 18:04 dovecot-uidlist drwxr-xr-x 5 root root 4096 Apr 24 18:04 .Drafts drwxr-xr-x 2 root root 4096 Apr 24 18:04 new drwxr-xr-x 5 root root 4096 Apr 24 18:04 .Sent drwxr-xr-x 5 root root 4096 Apr 24 18:04 .Junk -rw-r--r-- 1 root root 569 Apr 24 18:04 subscriptions drwxr-xr-x 2 root root 4096 Apr 24 18:04 tmp drwxr-xr-x 5 root root 4096 Apr 24 18:04 .Trash
Die gelesenen Nachrichten sollten nun im Verzeichnis
/tmp/dovecot/klaus/Maildir/cur
im Dovecot spezifischen Dateiformat, was mit nachfolgendem Befehl überprüft werden kann:
(Nur beispielhafter kurzer Ausschnitt)
# ls -l /tmp/dovecot/klaus/Maildir/cur total 68756 -rw-r--r-- 1 root root 17202 May 1 2012 1335866770.R4c6b2e68Q116.rechner80.dmz.tachtler.net,S=17202,W=17748:2,S -rw-r--r-- 1 root root 2684 May 1 2012 1335866775.Rd9f3e513Q117.rechner80.dmz.tachtler.net,S=2684,W=2749:2,SR ... -rw-r--r-- 1 root root 16059 Apr 22 09:20 1398151257.Rcf454617Q396.rechner80.dmz.tachtler.net,S=16059,W=16401:2,S -rw-r--r-- 1 root root 6745 Apr 22 10:13 1398154391.R7bbf55c8Q397.rechner80.dmz.tachtler.net,S=6745,W=6927:2,SR
Übertrag Dovecot-Server
Nachdem das Postfach des Benutzers vom Cyrus IMAPd spezifischen Dateiformat in das Dovecot spezifischen Dateiformat umgewandelt wurde, muss nun noch das so entstandene Maildir-Verzeichnis
- vom Cyrus IMAPd-Server, hier mit der IP: 192.168.0.180
- auf den Dovecot-Server, hier mit der IP: 192.168.0.80
übertragen werden.
Da dies alles hier offline
geschieht, ist eine Möglichkeit dies mittels
- Packen des Maildir-Verzeichnisses in eine Datei
- Übertragung der Datei auf den neuen Server und
- Entpacken des Maildir-Verzeichnisses aus der gepackten Datei
zu realisieren.
Mit nachfolgenden Befehlen kann der relevante Teil des unter dem Verzeichnis /tmp
entstandenen Verzeichnisses /tmp/dovecot
in eine Datei gepackt werden. Hierbei sollen die Besitzrechte, Dateirechte, das Datum und die Uhrzeit erhalten bleiben.
Vorbereitend hierzu kann mit nachfolgendem Befehl in den relevanten Teil des Verzeichnisses /tmp/dovecot
gewechselt werden:
# cd /tmp/dovecot
Mit nachfolgendem Befehl wird nun alles innerhalb von /tmp/dovecot/klaus
in eine *.tar.gz-Datei gepackt:
# tar -cvzf klaus-Maildir.tar.gz klaus/ --atime-preserve --preserve-permissions
Nun kann z.B. via scp (Secure Copy) die so erhaltene gepackte Datei mit nachfolgendem Befehl auf den neuen Dovecot-Server ebenfalls in das Verzeichnis /tmp
transferiert werden:
# scp -p /tmp/dovecot/klaus-Maildir.tar.gz 192.168.0.80:/tmp
Abschließend kann dann auf dem neuen Dovecot-Server die Datei mit nachfolgendem Befehl gleich in das Zielverzeichnis
/var/spool/vmail/tachtler.net/
entpackt werden:
tar -xvzf /tmp/klaus-Maildir.tar.gz -C /var/spool/vmail/tachtler.net/ --atime-preserve --preserve-permissions
WICHTIG - Nachfolgend müssen nach dem entpacken noch die Besitzrechte und die Dateirechte angepasst werden !!!
Nachfolgendes Beispiel setzt voraus, dass ein Authentifizierungsbenutzer wie unter nachfolgendem internen Link beschrieben
verwendet wird.
Mit nachfolgendem Befehl, werden die Besitzrechte, für das so entstandene Verzeichnis
/var/spool/vmail/tachtler.net/klaus
auf den Dummy-Benutzer gesetzt:
# chown -R vmail:vmail /var/spool/vmail/tachtler.net/klaus
Mit nachfolgendem Befehlen, werden die Dateirechte ebenfalls, für das so entstandene Verzeichnis
/var/spool/vmail/tachtler.net/klaus
richtig gesetzt:
# chmod -R o-rx /var/spool/vmail/tachtler.net/klaus
# chmod -R g-rx /var/spool/vmail/tachtler.net/klaus
Abschließend kann mit nachfolgenden Befehlen, die korrekte Erstellung und Rechtevergabe überprüft werden, welche eine Ausgabe wie die nachfolgende zurückgeben sollte:
# ls -l /var/spool/vmail/tachtler.net/klaus total 4 drwx------ 30 vmail vmail 4096 Apr 24 18:04 Maildir
# ls -la /var/spool/vmail/tachtler.net/klaus/* total 112 drwx------ 30 vmail vmail 4096 Apr 24 18:04 . drwx------ 3 vmail vmail 4096 Apr 24 18:04 .. drwx------ 2 vmail vmail 36864 Apr 24 18:04 cur -rw------- 1 vmail vmail 30 Apr 24 18:04 dovecot-keywords -rw------- 1 vmail vmail 21817 Apr 24 18:04 dovecot-uidlist drwx------ 5 vmail vmail 4096 Apr 24 18:04 .Drafts drwx------ 2 vmail vmail 4096 Apr 24 18:04 new drwx------ 5 vmail vmail 4096 Apr 24 18:04 .Sent drwx------ 5 vmail vmail 4096 Apr 24 18:04 .Junk -rw------- 1 vmail vmail 569 Apr 24 18:04 subscriptions drwx------ 2 vmail vmail 4096 Apr 24 18:04 tmp drwx------ 5 vmail vmail 4096 Apr 24 18:04 .Trash
Neustart
Bevor der der dovecot
-Daemon/Dienst neu gestartet werden soll, ist eine Überprüfung der korrekten Konfiguration durch nachfolgenden Befehl, zu empfehlen
# doveconf -n # 2.2.10: /etc/dovecot/dovecot.conf # OS: Linux 2.6.32-431.11.2.el6.x86_64 x86_64 CentOS release 6.5 (Final) auth_debug = yes auth_master_user_separator = * auth_mechanisms = plain digest-md5 cram-md5 login auth_verbose = yes listen = * mail_debug = yes mail_location = maildir:~/Maildir mail_plugins = " quota acl zlib mail_log notify" managesieve_notify_capability = mailto managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date ihave mbox_write_locks = fcntl namespace { list = children location = maildir:%%h/Maildir:INDEX=%h/shared/%%u:CONTROL=%h/shared/%%u prefix = shared/%%u/ separator = / subscriptions = yes type = shared } namespace inbox { inbox = yes location = mailbox Drafts { auto = subscribe special_use = \Drafts } mailbox Junk { auto = subscribe special_use = \Junk } mailbox Sent { auto = subscribe special_use = \Sent } mailbox "Sent Messages" { special_use = \Sent } mailbox Trash { auto = subscribe special_use = \Trash } prefix = INBOX/ separator = / } passdb { args = /etc/dovecot/master-users driver = passwd-file master = yes pass = yes } passdb { args = /etc/dovecot/dovecot-sql.conf.ext driver = sql } plugin { acl = vfile acl_shared_dict = file:/var/lib/dovecot/db/shared-mailboxes.db mail_log_fields = uid box msgid size from quota = maildir:User quota quota_grace = 10%% quota_rule = *:storage=1G quota_rule2 = INBOX/Trash:storage=+100M quota_status_nouser = DUNNO quota_status_overquota = 552 5.2.2 Mailbox is over quota quota_status_success = DUNNO quota_warning = storage=95%% quota-warning 95 %u quota_warning2 = storage=80%% quota-warning 80 %u sieve = ~/.dovecot.sieve sieve_dir = ~/sieve zlib_save = gz zlib_save_level = 6 } protocols = imap lmtp sieve service auth { unix_listener auth-userdb { group = vmail user = vmail } } service imap-login { process_min_avail = 1 service_count = 0 } service lmtp { inet_listener lmtp { address = 192.168.0.80 port = 24 } } service managesieve-login { inet_listener sieve { port = 4190 } inet_listener sieve_deprecated { port = 2000 } } service quota-status { client_limit = 1 executable = quota-status -p postfix inet_listener { address = 192.168.0.80 port = 12340 } } service quota-warning { executable = script /usr/local/bin/quota-warning.sh user = vmail } ssl_cert = </etc/pki/dovecot/certs/CAcert-class3-wildcard_all_in_one.crt ssl_cipher_list = ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA ssl_key = </etc/pki/dovecot/private/tachtler.net.key ssl_prefer_server_ciphers = yes ssl_protocols = !SSLv2 !SSLv3 userdb { driver = prefetch } userdb { args = /etc/dovecot/dovecot-sql.conf.ext driver = sql } verbose_proctitle = yes protocol lmtp { mail_plugins = " quota acl zlib mail_log notify sieve" } protocol imap { mail_max_userip_connections = 30 mail_plugins = " quota acl zlib mail_log notify imap_quota imap_acl imap_zlib" } protocol sieve { mail_max_userip_connections = 30 }
HINWEIS - die Konfiguration des dovecot
-Daemon/Dienst konnte korrekt gelesen werden, wenn die Konfiguration erscheint, was letztendlich zwar nicht bedeutet, das Sie auch korrekt ist, aber syntaktische Fehler ausschließt !!!
Danach kann der dovecot-Server mit nachfolgendem Befehle neu gestartet werden:
# service dovecot restart Stopping Dovecot Imap: [ OK ] Starting Dovecot Imap: [ OK ]
Login-Test mit telnet
Um zu Überprüfen, ob eine Anmeldung als Benutzer von einem entfernten Rechner möglich ist, kann nachfolgender Befehl genutzt werden:
# telnet 192.168.0.80 143 Trying 192.168.0.80... Connected to 192.168.0.80. Escape character is '^]'. * OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE STARTTLS AUTH=PLAIN AUTH=DIGEST-MD5 AUTH=CRAM-MD5 AUTH=LOGIN] Dovecot ready. a1 login klaus@tachtler.net geheim a1 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS SPECIAL-USE BINARY MOVE COMPRESS=DEFLATE QUOTA ACL RIGHTS=texk] Logged in a2 list "" "*" * LIST (\HasChildren) "/" INBOX * LIST (\HasNoChildren \Junk) "/" INBOX/Junk * LIST (\HasNoChildren \Sent) "/" INBOX/Sent * LIST (\HasNoChildren \Trash) "/" INBOX/Trash * LIST (\HasNoChildren \Drafts) "/" INBOX/Drafts a2 OK List completed. a3 logout * BYE Logging out a3 OK Logout completed. Connection closed by foreign host.
Erforderliche Benutzereingaben:
telnet 192.168.0.80 143
a1 login klaus@tachtler.net geheim
a2 list "" "*"
a3 logout