source: trunk/athena/bin/mitmailutils/mitmailmove.pl @ 23913

Revision 23913, 5.9 KB checked in by andersk, 15 years ago (diff)
In mitmailutils: * Replace test for EXCHANGE.MIT.EDU with test for *EXCHANGE*.
  • 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{'ATHENA_USER'} || $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}
67errorout "Exchange accounts are not supported yet. Try http://owa.mit.edu/." if $opt_host =~ /EXCHANGE/;
68
69# Connect to the IMAP server, and authenticate.
70my $client = Cyrus::IMAP->new($opt_host) ||
71    errorout "Cannot connect to IMAP server on $opt_host";
72$client->authenticate(-authz => $username) ||
73    close_and_errorout "Cannot authenticate to $opt_host";
74
75# Select (or examine, if copying only) the mailbox.
76my $select_cmd = ($copy ? 'EXAMINE' : 'SELECT');
77send_command "$select_cmd \"$opt_mailbox\"";
78
79# Use the correct forms of the COPY and STORE commands, depending on
80# whether we are operating on UIDs, or sequence numbers.
81my $store_cmd;
82my $copy_cmd;
83if ($opt_by_uid) {
84    $copy_cmd = 'UID COPY';
85    $store_cmd = 'UID STORE';
86} else {
87    $copy_cmd = 'COPY';
88    $store_cmd = 'STORE';
89}
90   
91# If we are doing a "move" operation, we will execute a STORE command
92# following the copy to set the message's Deleted flag.  Add a callback
93# for the FETCH response generated by the STORE.
94my $cb_numbered = Cyrus::IMAP::CALLBACK_NUMBERED;
95unless ($copy) {
96    $client->addcallback({-trigger => 'FETCH', -flags => $cb_numbered,
97                          -callback => \&fetch_callback});
98}
99
100# Copy each given message ID to the target mailbox, and, if doing a
101# "move", mark the source message(s) for deletion.
102my $exitcode = 0;
103foreach (@ARGV) {
104    my ($status, $text) = send_command "$copy_cmd $_ \"$target\"";
105    if ($status ne 'OK') {
106        if ($text =~ m/\bTRYCREATE\b/io) {
107            close_and_errorout "Mailbox $target does not exist"
108                if $opt_no_create;
109            print "Creating $target\n" if $opt_debug;
110            # send_command will error out if either the CREATE fails.
111            send_command "CREATE \"$target\"";
112            ($status, $text) = send_command "$copy_cmd $_ \"$target\"";
113        }
114        if ($status ne 'OK') {
115            print STDERR "IMAP error copying $_ to $target: $text\n";
116            $exitcode = 2;
117            next;
118        }
119    }
120    send_command "$store_cmd $_ +FLAGS (\\Deleted)" unless $copy;
121}
122
123# We are done talking to the IMAP server, close down the connection.
124close_connection();
125
126exit $exitcode;
127
128# Subroutine to send a command to the IMAP server, and wait for the
129# response; any defined callbacks for the response are invoked.
130# If called in list context, we return the server's response, as
131# status and text values.  Otherwise, if the response indicates
132# failure, we error out.
133sub send_command($) {
134    print "Sending: $_[0]\n" if $opt_debug;
135    my ($status, $text) = $client->send('', '', $_[0]);
136    print "Response: status $status, text $text\n" if $opt_debug;
137    errorout "Premature end-of-file on IMAP connection to $opt_host"
138        if $status eq 'EOF';
139    return ($status, $text) if wantarray;
140    close_and_errorout "IMAP error from $opt_host: $text"
141        if $status ne 'OK';
142}
143
144# Callback subroutine to parse the FETCH response from a STORE command.
145# This callback will be invoked for each message.  The "-text" hash
146# element contains the text returned by the server.
147sub fetch_callback(@) {
148    my %cb = @_;
149    my ($number, $flags);
150    print "In FETCH callback: msgno $cb{-msgno} text $cb{-text}\n"
151        if $opt_debug;
152    $number = $cb{-msgno};
153    foreach (split /\r\n/, $cb{-text}) {
154        $number = $1 if /UID\s+(\d+)/io;
155        $flags = $1 if /FLAGS\s+\(([^\)]*)\)/io;
156    }
157    # Warn if the message's state does not include the Deleted flag
158    # (should never happen).
159    if ($flags !~ m/\\Deleted\b/io) {
160        print STDERR "$prog: Warning: Message $number not deleted";
161        print STDERR " from $opt_mailbox\n";
162    }
163}
164
165# Close the connection to the IMAP server, and error out.
166sub close_and_errorout($) {
167    close_connection();
168    errorout $_[0];
169}
170
171# Logout from the IMAP server, and close the connection.
172sub close_connection() {
173    $client->send('', '', "LOGOUT");
174    # Set the client reference to undef, so that perl invokes the
175    # destructor, which closes the connection.  Note that if we invoke
176    # the destructor explicitly here, then perl will still invoke it
177    # again when the program exits, thus touching memory which has
178    # already been freed.
179    $client = undef;
180}
181
182sub errorout($) {
183    print STDERR "$prog: $_[0]\n";
184    exit 1;
185}
Note: See TracBrowser for help on using the repository browser.