source: trunk/athena/bin/mitmailmove/mitmailmove.pl @ 22830

Revision 22830, 5.7 KB checked in by tabbott, 16 years ago (diff)
In mitmailmove: * Merged quilt patches into mainline Athena tree
  • Property svn:executable set to *
Line 
1#!/usr/bin/perl -w
2
3# $Id: mitmailmove.pl,v 1.3 2004-08-31 16:00:16 rbasch Exp $
4
5# Move or copy messages between IMAP folders.
6
7use strict;
8use warnings FATAL => 'all';
9use Cyrus::IMAP;
10use Getopt::Long;
11
12sub usage(;$);
13sub send_command($);
14sub fetch_callback(@);
15sub close_and_errorout($);
16sub close_connection();
17sub errorout($);
18
19my $prog = $0;
20my $copy = ($prog =~ m/copy$/o);
21
22sub usage(;$) {
23    print STDERR "$prog: $_[0]\n" if ($_[0] && $_[0] ne "help");
24    print STDERR <<EOF;
25Usage: $prog [<options>] <message-id> [...] <target-mailbox>
26  Options:
27    --by-uid               specify message UIDs instead of sequence numbers
28    --debug                turn on debugging
29    --help                 print this usage information
30    --host=<name>          query host <name> instead of default POBOX server
31    --mailbox=<name>       examine mailbox <name> instead of INBOX
32    --no-create            do not create the target mailbox automatically
33EOF
34    exit 1;
35}
36
37# Parse the command line arguments.
38use vars qw($opt_by_uid $opt_debug $opt_host $opt_mailbox $opt_no_create);
39
40GetOptions("by-uid",
41           "debug",
42           "help" => \&usage,
43           "host=s",
44           "mailbox=s",
45           "no-create") || usage;
46
47usage "Please specify a message number and target mailbox" if @ARGV < 2;
48my $target = pop @ARGV;
49
50# Check the validity of message ID arguments.
51# The ID can be a number or '*', and we accept a range specification,
52# of the form 'n:m'.
53foreach (@ARGV) {
54    errorout "Invalid message specification $_"
55        unless (m/^(?:\d+|\*)(?::(?:\d+|\*))?$/o);
56}
57
58$opt_mailbox = 'INBOX' unless $opt_mailbox;
59
60my $username = $ENV{'USER'} || getlogin || (getpwuid($<))[0] ||
61    errorout "Cannot determine user name";
62
63unless ($opt_host) {
64    $opt_host = (split(" ", `hesinfo $username pobox`))[1] ||
65        errorout "Cannot find Post Office server for $username";
66}
67
68# Connect to the IMAP server, and authenticate.
69my $client = Cyrus::IMAP->new($opt_host) ||
70    errorout "Cannot connect to IMAP server on $opt_host";
71$client->authenticate(-authz => $username) ||
72    close_and_errorout "Cannot authenticate to $opt_host";
73
74# Select (or examine, if copying only) the mailbox.
75my $select_cmd = ($copy ? 'EXAMINE' : 'SELECT');
76send_command "$select_cmd \"$opt_mailbox\"";
77
78# Use the correct forms of the COPY and STORE commands, depending on
79# whether we are operating on UIDs, or sequence numbers.
80my $store_cmd;
81my $copy_cmd;
82if ($opt_by_uid) {
83    $copy_cmd = 'UID COPY';
84    $store_cmd = 'UID STORE';
85} else {
86    $copy_cmd = 'COPY';
87    $store_cmd = 'STORE';
88}
89   
90# If we are doing a "move" operation, we will execute a STORE command
91# following the copy to set the message's Deleted flag.  Add a callback
92# for the FETCH response generated by the STORE.
93my $cb_numbered = Cyrus::IMAP::CALLBACK_NUMBERED;
94unless ($copy) {
95    $client->addcallback({-trigger => 'FETCH', -flags => $cb_numbered,
96                          -callback => \&fetch_callback});
97}
98
99# Copy each given message ID to the target mailbox, and, if doing a
100# "move", mark the source message(s) for deletion.
101my $exitcode = 0;
102foreach (@ARGV) {
103    my ($status, $text) = send_command "$copy_cmd $_ \"$target\"";
104    if ($status ne 'OK') {
105        if ($text =~ m/\bTRYCREATE\b/io) {
106            close_and_errorout "Mailbox $target does not exist"
107                if $opt_no_create;
108            print "Creating $target\n" if $opt_debug;
109            # send_command will error out if either the CREATE fails.
110            send_command "CREATE \"$target\"";
111            ($status, $text) = send_command "$copy_cmd $_ \"$target\"";
112        }
113        if ($status ne 'OK') {
114            print STDERR "IMAP error copying $_ to $target: $text\n";
115            $exitcode = 2;
116            next;
117        }
118    }
119    send_command "$store_cmd $_ +FLAGS (\\Deleted)" unless $copy;
120}
121
122# We are done talking to the IMAP server, close down the connection.
123close_connection();
124
125exit $exitcode;
126
127# Subroutine to send a command to the IMAP server, and wait for the
128# response; any defined callbacks for the response are invoked.
129# If called in list context, we return the server's response, as
130# status and text values.  Otherwise, if the response indicates
131# failure, we error out.
132sub send_command($) {
133    print "Sending: $_[0]\n" if $opt_debug;
134    my ($status, $text) = $client->send('', '', $_[0]);
135    print "Response: status $status, text $text\n" if $opt_debug;
136    errorout "Premature end-of-file on IMAP connection to $opt_host"
137        if $status eq 'EOF';
138    return ($status, $text) if wantarray;
139    close_and_errorout "IMAP error from $opt_host: $text"
140        if $status ne 'OK';
141}
142
143# Callback subroutine to parse the FETCH response from a STORE command.
144# This callback will be invoked for each message.  The "-text" hash
145# element contains the text returned by the server.
146sub fetch_callback(@) {
147    my %cb = @_;
148    my ($number, $flags);
149    print "In FETCH callback: msgno $cb{-msgno} text $cb{-text}\n"
150        if $opt_debug;
151    $number = $cb{-msgno};
152    foreach (split /\r\n/, $cb{-text}) {
153        $number = $1 if /UID\s+(\d+)/io;
154        $flags = $1 if /FLAGS\s+\(([^\)]*)\)/io;
155    }
156    # Warn if the message's state does not include the Deleted flag
157    # (should never happen).
158    if ($flags !~ m/\\Deleted\b/io) {
159        print STDERR "$prog: Warning: Message $number not deleted";
160        print STDERR " from $opt_mailbox\n";
161    }
162}
163
164# Close the connection to the IMAP server, and error out.
165sub close_and_errorout($) {
166    close_connection();
167    errorout $_[0];
168}
169
170# Logout from the IMAP server, and close the connection.
171sub close_connection() {
172    $client->send('', '', "LOGOUT");
173    # Set the client reference to undef, so that perl invokes the
174    # destructor, which closes the connection.  Note that if we invoke
175    # the destructor explicitly here, then perl will still invoke it
176    # again when the program exits, thus touching memory which has
177    # already been freed.
178    $client = undef;
179}
180
181sub errorout($) {
182    print STDERR "$prog: $_[0]\n";
183    exit 1;
184}
Note: See TracBrowser for help on using the repository browser.