[22834] | 1 | #!/usr/bin/perl -w |
---|
[19880] | 2 | |
---|
[20593] | 3 | # $Id: mailquota.pl,v 1.4 2004-07-29 19:11:52 rbasch Exp $ |
---|
[19880] | 4 | |
---|
| 5 | # Display the IMAP resource usage on the user's post office server. |
---|
| 6 | |
---|
| 7 | use strict; |
---|
| 8 | use warnings FATAL => 'all'; |
---|
| 9 | use Cyrus::IMAP; |
---|
| 10 | use Getopt::Std; |
---|
| 11 | |
---|
| 12 | sub usage(;$); |
---|
| 13 | sub print_quota($$$$$); |
---|
| 14 | sub send_command($); |
---|
| 15 | sub quota_callback(@); |
---|
| 16 | sub quotaroot_callback(@); |
---|
| 17 | sub capability_callback(@); |
---|
| 18 | sub close_and_errorout($); |
---|
| 19 | sub close_connection(); |
---|
| 20 | sub errorout($); |
---|
| 21 | |
---|
| 22 | sub usage(;$) { |
---|
| 23 | print STDERR "mailquota: $_[0]\n" if $_[0]; |
---|
| 24 | print STDERR <<EOF; |
---|
| 25 | Usage: mailquota [<options>] [<user>] |
---|
| 26 | Options: |
---|
| 27 | -h <host> query <host> instead of default post office server |
---|
| 28 | -m <mailbox> query for <mailbox> (default is INBOX) |
---|
| 29 | -n be silent unless usage % is above the threshold |
---|
| 30 | -u <percent> usage % threshold (default is 90); implies -n |
---|
| 31 | -d turn on debugging |
---|
| 32 | EOF |
---|
| 33 | exit 1; |
---|
| 34 | } |
---|
| 35 | |
---|
| 36 | my $need_header = 1; |
---|
| 37 | my $root_width = 16; |
---|
| 38 | my $num_width = 10; |
---|
| 39 | my $percent_width = 5; |
---|
| 40 | my $format = "%-${root_width}.${root_width}s" . |
---|
| 41 | " %${num_width}s %${num_width}s %${percent_width}s" . |
---|
| 42 | " %${num_width}s %${num_width}s %${percent_width}s\n"; |
---|
| 43 | |
---|
| 44 | # Parse the command line arguments. |
---|
| 45 | my %opts; |
---|
| 46 | getopts('dh:m:nu:', \%opts) || usage; |
---|
[23693] | 47 | my $username = shift @ARGV || $ENV{'ATHENA_USER'} || $ENV{'USER'} || getlogin || (getpwuid($<))[0] || |
---|
[19880] | 48 | errorout "Cannot determine user name"; |
---|
| 49 | |
---|
| 50 | usage "Too many arguments" if @ARGV > 0; |
---|
| 51 | my $debug = $opts{'d'}; |
---|
| 52 | my $warn_only = $opts{'n'}; |
---|
| 53 | my $usage_threshold = $opts{'u'}; |
---|
[24300] | 54 | my $host = $opts{'h'} || (gethostbyname("$username.mail.mit.edu"))[0]; |
---|
| 55 | errorout "Cannot find Post Office server for $username" unless $host; |
---|
[23913] | 56 | errorout "Exchange accounts are not supported yet. Try http://owa.mit.edu/." if $host =~ /EXCHANGE/; |
---|
[19880] | 57 | my $mbox = $opts{'m'} || "INBOX"; |
---|
| 58 | |
---|
| 59 | # Validate the usage percentage threshold, allowing a trailing %. |
---|
| 60 | # Setting the threshold implies we should only display quotas |
---|
| 61 | # for which any resource usage is above the threshold. |
---|
| 62 | if (defined $usage_threshold) { |
---|
| 63 | chop $usage_threshold if $usage_threshold =~ /^\d+%$/; |
---|
| 64 | usage "-u argument must be numeric" if $usage_threshold !~ /^\d+$/; |
---|
| 65 | $warn_only = 1; |
---|
| 66 | } else { |
---|
| 67 | $usage_threshold = 90; |
---|
| 68 | } |
---|
| 69 | |
---|
| 70 | # Connect to the IMAP server, check for the QUOTA extension, and |
---|
| 71 | # authenticate. |
---|
| 72 | my $client = Cyrus::IMAP->new($host) || |
---|
| 73 | errorout "Cannot connect to IMAP server on $host"; |
---|
| 74 | my $caps = ''; |
---|
| 75 | $client->addcallback({-trigger => 'CAPABILITY', |
---|
| 76 | -callback => \&capability_callback, |
---|
| 77 | -rock => \$caps}); |
---|
| 78 | send_command "CAPABILITY"; |
---|
| 79 | $caps =~ '\bQUOTA\b' || |
---|
| 80 | close_and_errorout "$host does not support the IMAP QUOTA extension"; |
---|
[24300] | 81 | $client->authenticate(-authz => $username, -maxssf => 0) || |
---|
[19880] | 82 | close_and_errorout "Cannot authenticate to $host"; |
---|
| 83 | |
---|
| 84 | # Send the GETQUOTAROOT command, which returns both the QUOTA and |
---|
| 85 | # QUOTAROOT responses. Quota information will be displayed via |
---|
| 86 | # the QUOTA callback. |
---|
| 87 | $client->addcallback({-trigger => 'QUOTA', |
---|
| 88 | -callback => \"a_callback}); |
---|
| 89 | $client->addcallback({-trigger => 'QUOTAROOT', |
---|
| 90 | -callback => \"aroot_callback}); |
---|
[20505] | 91 | send_command "GETQUOTAROOT \"$mbox\""; |
---|
[19880] | 92 | |
---|
| 93 | # We are done talking to the IMAP server; close down the connection. |
---|
| 94 | close_connection(); |
---|
| 95 | |
---|
| 96 | # Print the quota information for the given quota root and its |
---|
| 97 | # storage and message resource values. |
---|
| 98 | sub print_quota($$$$$) { |
---|
| 99 | my ($root, $storage_used, $storage_max, $message_used, $message_max) = @_; |
---|
| 100 | my $storage_percent; |
---|
| 101 | my $storage_percent_out; |
---|
| 102 | my $message_percent; |
---|
| 103 | my $message_percent_out; |
---|
| 104 | |
---|
| 105 | # Calculate the usage percentages, and format for output. |
---|
| 106 | if ($storage_max) { |
---|
| 107 | $storage_percent = ($storage_used / $storage_max) * 100; |
---|
| 108 | $storage_percent_out = sprintf("%.0f%%", $storage_percent); |
---|
| 109 | } |
---|
| 110 | if ($message_max) { |
---|
| 111 | $message_percent = ($message_used / $message_max) * 100; |
---|
| 112 | $message_percent_out = sprintf("%.0f%%", $message_percent); |
---|
| 113 | } |
---|
| 114 | |
---|
| 115 | # Skip this quota if we are only displaying usages above the |
---|
| 116 | # specified threshold. |
---|
| 117 | return unless (!$warn_only || |
---|
| 118 | (defined $storage_percent && |
---|
| 119 | $storage_percent >= $usage_threshold) || |
---|
| 120 | (defined $message_percent && |
---|
| 121 | $message_percent >= $usage_threshold)); |
---|
| 122 | |
---|
| 123 | # Print a header if this is the first line of output. |
---|
| 124 | if ($need_header) { |
---|
[24907] | 125 | print "** IMAP e-mail usage for $mbox on $host:\n"; |
---|
[19880] | 126 | printf($format, |
---|
| 127 | "Quota", |
---|
| 128 | "KB Used", "KB Max", "KB %", |
---|
| 129 | "# Msgs", "# Max", "# %"); |
---|
| 130 | $need_header = 0; |
---|
| 131 | } |
---|
| 132 | printf($format, |
---|
| 133 | $root, |
---|
| 134 | defined $storage_used ? $storage_used : '-', |
---|
| 135 | defined $storage_max ? $storage_max : '-', |
---|
| 136 | defined $storage_percent_out ? $storage_percent_out : '-', |
---|
| 137 | defined $message_used ? $message_used : '-', |
---|
| 138 | defined $message_max ? $message_max : '-', |
---|
| 139 | defined $message_percent_out ? $message_percent_out : '-'); |
---|
| 140 | } |
---|
| 141 | |
---|
| 142 | # Subroutine to send a command to the IMAP server, and wait for the |
---|
| 143 | # response; any defined callbacks for the response are invoked. |
---|
| 144 | # If the server response indicates failure, we error out. |
---|
| 145 | sub send_command($) { |
---|
| 146 | print "Sending: $_[0]\n" if $debug; |
---|
| 147 | my ($status, $text) = $client->send('', '', $_[0]); |
---|
| 148 | print "Response: status $status, text $text\n" if $debug; |
---|
| 149 | errorout "Premature end-of-file on IMAP connection to $host" |
---|
| 150 | if $status eq 'EOF'; |
---|
| 151 | close_and_errorout "IMAP error for $mbox on $host: $text" |
---|
| 152 | if $status ne 'OK'; |
---|
| 153 | } |
---|
| 154 | |
---|
| 155 | # Callback subroutine to parse the QUOTA response. |
---|
| 156 | # The "-text" hash element contains the quota root name, and a list |
---|
| 157 | # of quota resource names, usages, and limits. Recognized names are |
---|
| 158 | # STORAGE (sum of message sizes, in kilobytes) and MESSAGE (number of |
---|
| 159 | # messages). See RFC 2087. |
---|
| 160 | sub quota_callback(@) { |
---|
| 161 | my %cb = @_; |
---|
| 162 | my ($root, $quotalist); |
---|
| 163 | print "In QUOTA callback: text $cb{-text}\n" if $debug; |
---|
| 164 | if (($root, $quotalist) = ($cb{-text} =~ /(\S*)\s+\((.*)\)/io)) { |
---|
| 165 | my ($storage_used, $storage_max, $message_used, $message_max); |
---|
| 166 | while ($quotalist) { |
---|
| 167 | my ($resource, $used, $max); |
---|
| 168 | ($resource, $used, $max, $quotalist) = split /\s/, $quotalist, 4; |
---|
| 169 | last unless $max; |
---|
| 170 | $resource = uc $resource; |
---|
| 171 | if ($resource eq "STORAGE") { |
---|
| 172 | $storage_used = $used; |
---|
| 173 | $storage_max = $max; |
---|
| 174 | } |
---|
| 175 | elsif ($resource eq "MESSAGE") { |
---|
| 176 | $message_used = $used; |
---|
| 177 | $message_max = $max; |
---|
| 178 | } |
---|
| 179 | } |
---|
| 180 | print_quota($root, $storage_used, $storage_max, |
---|
| 181 | $message_used, $message_max) |
---|
| 182 | if (defined $storage_max || defined $message_max); |
---|
| 183 | } |
---|
| 184 | } |
---|
| 185 | |
---|
| 186 | # Callback subroutine to parse the QUOTAROOT response. The "-text" |
---|
| 187 | # hash element contains the mailbox name, and zero or more quota root |
---|
| 188 | # names. This is currently used for debugging only. |
---|
| 189 | sub quotaroot_callback(@) { |
---|
| 190 | my %cb = @_; |
---|
| 191 | print "In QUOTAROOT callback: text $cb{-text}\n" if $debug; |
---|
| 192 | } |
---|
| 193 | |
---|
| 194 | # Callback subroutine to parse the CAPABILITY response. The "-rock" hash |
---|
| 195 | # element is a reference to the string in which to store the space-separated |
---|
| 196 | # capability names. |
---|
| 197 | sub capability_callback(@) { |
---|
| 198 | my %cb = @_; |
---|
| 199 | print "In CAPABILITY callback: keyword $cb{-keyword}, text $cb{-text}\n" |
---|
| 200 | if $debug; |
---|
| 201 | ${$cb{-rock}} = $cb{-text}; |
---|
| 202 | } |
---|
| 203 | |
---|
| 204 | # Close the connection to the IMAP server, and error out. |
---|
| 205 | sub close_and_errorout($) { |
---|
| 206 | close_connection(); |
---|
| 207 | errorout $_[0]; |
---|
| 208 | } |
---|
| 209 | |
---|
| 210 | # Logout from the IMAP server, and close the connection. |
---|
| 211 | sub close_connection() { |
---|
| 212 | $client->send('', '', "LOGOUT"); |
---|
[20593] | 213 | # Set the client reference to undef, so that perl invokes the |
---|
| 214 | # destructor, which closes the connection. Note that if we invoke |
---|
| 215 | # the destructor explicitly here, then perl will still invoke it |
---|
| 216 | # again when the program exits, thus touching memory which has |
---|
| 217 | # already been freed. |
---|
| 218 | $client = undef; |
---|
[19880] | 219 | } |
---|
| 220 | |
---|
| 221 | sub errorout($) { |
---|
| 222 | print STDERR "mailquota: $_[0]\n"; |
---|
| 223 | exit 1; |
---|
| 224 | } |
---|