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

Revision 23913, 7.9 KB checked in by andersk, 15 years ago (diff)
In mitmailutils: * Replace test for EXCHANGE.MIT.EDU with test for *EXCHANGE*.
Line 
1#!/usr/bin/perl -w
2
3# $Id: mitmaildel.pl,v 1.3 2004-07-29 19:11:52 rbasch Exp $
4
5# Delete (or undelete) messages in an IMAP folder.
6
7use strict;
8use warnings FATAL => 'all';
9use Cyrus::IMAP;
10use Getopt::Long;
11
12sub usage(;$);
13sub send_command($);
14sub fetch_callback(@);
15sub number_callback(@);
16sub close_and_errorout($);
17sub close_connection();
18sub errorout($);
19
20my $prog = $0;
21my $undelete = ($prog =~ m/undel$/o);
22
23sub usage(;$) {
24    print STDERR "$prog: $_[0]\n" if ($_[0] && $_[0] ne "help");
25    print STDERR <<EOF;
26Usage: $prog [<options>] <message-id> ...
27  Options:
28    --by-uid               specify message UIDs instead of sequence numbers
29    --debug                turn on debugging
30    --expunge              expunge all deleted messages from mailbox
31    --help                 print this usage information
32    --host=<name>          query host <name> instead of default POBOX server
33    --mailbox=<name>       examine mailbox <name> instead of INBOX
34    --silent               suppress acknowledgement output
35EOF
36    exit 1;
37}
38
39# Parse the command line arguments.
40use vars qw($opt_by_uid $opt_debug $opt_expunge $opt_host
41            $opt_mailbox $opt_silent);
42
43GetOptions("by-uid",
44           "debug",
45           "expunge",
46           "help" => \&usage,
47           "host=s",
48           "mailbox=s",
49           "silent") || usage;
50
51usage "Please specify a message number" if @ARGV == 0;
52
53# Check the validity of message ID arguments.
54# The ID can be a number or '*', and we accept a range specification,
55# of the form 'n:m'.
56foreach (@ARGV) {
57    errorout "Invalid message specification $_"
58        unless (m/^(?:\d+|\*)(?::(?:\d+|\*))?$/o);
59}
60
61$opt_mailbox = 'INBOX' unless $opt_mailbox;
62
63my $username = $ENV{'ATHENA_USER'} || $ENV{'USER'} || getlogin || (getpwuid($<))[0] ||
64    errorout "Cannot determine user name";
65
66unless ($opt_host) {
67    $opt_host = (split(" ", `hesinfo $username pobox`))[1] ||
68        errorout "Cannot find Post Office server for $username";
69}
70errorout "Exchange accounts are not supported yet. Try http://owa.mit.edu/." if $opt_host =~ /EXCHANGE/;
71
72# Connect to the IMAP server, and authenticate.
73my $client = Cyrus::IMAP->new($opt_host) ||
74    errorout "Cannot connect to IMAP server on $opt_host";
75$client->authenticate(-authz => $username) ||
76    close_and_errorout "Cannot authenticate to $opt_host";
77
78# Select the mailbox (for read-write access).  Store the value of the
79# EXISTS (i.e. highest existing message sequence number) data item,
80# and, if returned, the UIDNEXT (next UID to be assigned) value.
81my $cb_numbered = Cyrus::IMAP::CALLBACK_NUMBERED;
82my $maxseq = 0;
83my $uidnext = 0;
84$client->addcallback({-trigger => 'EXISTS', -flags => $cb_numbered,
85                      -callback => \&number_callback,
86                      -rock => \$maxseq});
87$client->addcallback({-trigger => 'OK',
88                      -callback => sub {
89                          my %cb = @_;
90                          print "In OK callback: text $cb{-text}\n"
91                              if $opt_debug;
92                          return unless ($cb{-text} =~ m/UIDNEXT\s+(\d+)/io);
93                          $uidnext = $1;
94                      }});
95send_command "SELECT \"$opt_mailbox\"";
96
97# If we're operating on UIDs, and did not get the UIDNEXT value above,
98# use the STATUS command to get it explicitly.
99if ($opt_by_uid && !$uidnext) {
100    $client->addcallback({-trigger => 'STATUS',
101                          -callback => sub {
102                              my %cb = @_;
103                              print "In STATUS callback: text $cb{-text}\n"
104                                  if $opt_debug;
105                              return
106                                  unless ($cb{-text} =~ m/UIDNEXT\s+(\d+)/io);
107                              $uidnext = $1;
108                          }});
109    send_command "STATUS \"$opt_mailbox\" (UIDNEXT)";
110}
111
112# Note that the STORE command returns success even when the given
113# message ID does not exist.  So the most feasible way to determine
114# whether the (un)delete was successful for a message is to see if the
115# FETCH callback (invoked during the server response to the STORE
116# command) was invoked for the message, and whether the \Deleted flag
117# was returned.  We thus initialize a hash whose keys are the
118# individual message IDs given; the FETCH callback will remove the key
119# from the hash when it detects that the message flags have been set
120# as desired.  Any keys remaining in the hash upon completion will
121# indicate IDs whose flags could not be modified (presumably because
122# the message does not exist).
123my %unchanged = ();
124my $expect = ($undelete ? "undeleted" : "deleted");
125my $exitcode = 0;
126my $store_cmd = ($opt_by_uid ? 'UID STORE' : 'STORE');
127my $store_item = ($undelete ? '-FLAGS' : '+FLAGS');
128$client->addcallback({-trigger => 'FETCH', -flags => $cb_numbered,
129                      -callback => \&fetch_callback,
130                      -rock => \%unchanged});
131foreach (@ARGV) {
132    if ($opt_by_uid) {
133        # When operating on UIDs, the message numbers in a range are
134        # not necessarily sequential, so we don't detect unchanged
135        # messages in the range.  We can handle '*', though, as that
136        # is simply one less than the next UID to be assigned.
137        $_ = ($uidnext - 1) if ($_ eq '*' && $uidnext);
138        %unchanged = ($_ => 1) if (/^\d+$/);
139    } else {
140        s/\*/$maxseq/o;
141        m/^(\d+)(?::(\d+))?$/o;
142        if ($2) {
143            %unchanged = map { $_ => 1 } ($1 < $2 ? $1 .. $2 : $2 .. $1);
144        } else {
145            %unchanged = ($1 => 1);
146        }
147    }
148    send_command "$store_cmd $_ $store_item (\\Deleted)";
149    foreach my $msg (sort { $a <=> $b } keys %unchanged) {
150        print STDERR "$prog: Could not " .
151            ($undelete ? "un" : "") . "delete $msg\n";
152        $exitcode = 1;
153    }
154}
155
156# Expunge the mailbox if so desired, unless there was an error marking
157# any message.
158send_command "CLOSE" if ($opt_expunge && ($exitcode == 0));
159
160# We are done talking to the IMAP server, close down the connection.
161close_connection();
162
163exit $exitcode;
164
165# Subroutine to send a command to the IMAP server, and wait for the
166# response; any defined callbacks for the response are invoked.
167# If the server response indicates failure, we error out.
168sub send_command($) {
169    print "Sending: $_[0]\n" if $opt_debug;
170    my ($status, $text) = $client->send('', '', $_[0]);
171    print "Response: status $status, text $text\n" if $opt_debug;
172    errorout "Premature end-of-file on IMAP connection to $opt_host"
173        if $status eq 'EOF';
174    close_and_errorout "IMAP error from $opt_host: $text"
175        if $status ne 'OK';
176}
177
178# Callback subroutine to parse the FETCH response from a STORE command.
179# This callback will be invoked for each message.  The "-text" hash
180# element contains the text returned by the server.  The "-rock"
181# element is a reference to the hash containing the message IDs of
182# interest; we delete the appropriate key (sequence number or UID)
183# from this hash.
184sub fetch_callback(@) {
185    my %cb = @_;
186    my ($number, $flags);
187    print "In FETCH callback: msgno $cb{-msgno} text $cb{-text}\n"
188        if $opt_debug;
189    $number = $cb{-msgno};
190    foreach (split /\r\n/, $cb{-text}) {
191        $number = $1 if /UID\s+(\d+)/io;
192        $flags = $1 if /FLAGS\s+\(([^\)]*)\)/io;
193    }
194    delete ${$cb{-rock}}{$number};
195    my $state = ($flags =~ /\\Deleted\b/io ? "deleted" : "undeleted");
196    # Warn if the returned state is not what was expected.
197    if ($state ne $expect) {
198        print STDERR "$prog: Warning: Message $number $state\n";
199    } else {
200        # Display an acknowledgement of success if so desired.
201        print(($opt_by_uid ? "UID" : "Message") . " $number $state\n")
202            unless $opt_silent;
203    }
204}
205
206# Callback subroutine to parse a numeric value.  The "-rock" hash
207# element is a reference to the scalar in which to store the number.
208sub number_callback(@) {
209    my %cb = @_;
210    print "In number callback: keyword $cb{-keyword}, number $cb{-msgno}\n"
211        if $opt_debug;
212    ${$cb{-rock}} = $cb{-msgno};
213}
214
215# Close the connection to the IMAP server, and error out.
216sub close_and_errorout($) {
217    close_connection();
218    errorout $_[0];
219}
220
221# Logout from the IMAP server, and close the connection.
222sub close_connection() {
223    $client->send('', '', "LOGOUT");
224    # Set the client reference to undef, so that perl invokes the
225    # destructor, which closes the connection.  Note that if we invoke
226    # the destructor explicitly here, then perl will still invoke it
227    # again when the program exits, thus touching memory which has
228    # already been freed.
229    $client = undef;
230}
231
232sub errorout($) {
233    print STDERR "$prog: $_[0]\n";
234    exit 1;
235}
Note: See TracBrowser for help on using the repository browser.