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

Revision 24300, 5.9 KB checked in by geofft, 14 years ago (diff)
In mitmailutils: * Switch from Hesiod to using *.mail.mit.edu so we can use GSSAPI authentication (Trac: #403). * Because of a Cyrus SASL bug (documented in that ticket) regarding parsing long encrypted responses, set maxssf to zero to send mail in the clear. This isn't as terrible as it sounds because mail travels in the clear on the public Internet anyway, and a bunch of client programs (Pine on Athena 9, for instance) don't use encryption, and the major use of mitmailutils is `from` and `mailquota` anyway. Patch based on one from Jonathan Reed <jdreed@mit.edu>.
  • 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 = (gethostbyname("$username.mail.mit.edu"))[0];
65    errorout "Cannot find Post Office server for $username" unless $opt_host;
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, -maxssf => 0) ||
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.