1 | #!/usr/bin/perl -w |
---|
2 | |
---|
3 | # $Id: mailquota.pl,v 1.4 2004-07-29 19:11:52 rbasch Exp $ |
---|
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; |
---|
47 | my $username = shift @ARGV || $ENV{'ATHENA_USER'} || $ENV{'USER'} || getlogin || (getpwuid($<))[0] || |
---|
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'}; |
---|
54 | my $host = $opts{'h'} || (split(" ", `hesinfo $username pobox`))[1] || |
---|
55 | errorout "Cannot find Post Office server for $username"; |
---|
56 | errorout "Exchange accounts are not supported yet. Try http://owa.mit.edu/." if $host =~ /EXCHANGE/; |
---|
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"; |
---|
81 | $client->authenticate(-authz => $username) || |
---|
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}); |
---|
91 | send_command "GETQUOTAROOT \"$mbox\""; |
---|
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) { |
---|
125 | printf($format, |
---|
126 | "Quota", |
---|
127 | "KB Used", "KB Max", "KB %", |
---|
128 | "# Msgs", "# Max", "# %"); |
---|
129 | $need_header = 0; |
---|
130 | } |
---|
131 | printf($format, |
---|
132 | $root, |
---|
133 | defined $storage_used ? $storage_used : '-', |
---|
134 | defined $storage_max ? $storage_max : '-', |
---|
135 | defined $storage_percent_out ? $storage_percent_out : '-', |
---|
136 | defined $message_used ? $message_used : '-', |
---|
137 | defined $message_max ? $message_max : '-', |
---|
138 | defined $message_percent_out ? $message_percent_out : '-'); |
---|
139 | } |
---|
140 | |
---|
141 | # Subroutine to send a command to the IMAP server, and wait for the |
---|
142 | # response; any defined callbacks for the response are invoked. |
---|
143 | # If the server response indicates failure, we error out. |
---|
144 | sub send_command($) { |
---|
145 | print "Sending: $_[0]\n" if $debug; |
---|
146 | my ($status, $text) = $client->send('', '', $_[0]); |
---|
147 | print "Response: status $status, text $text\n" if $debug; |
---|
148 | errorout "Premature end-of-file on IMAP connection to $host" |
---|
149 | if $status eq 'EOF'; |
---|
150 | close_and_errorout "IMAP error for $mbox on $host: $text" |
---|
151 | if $status ne 'OK'; |
---|
152 | } |
---|
153 | |
---|
154 | # Callback subroutine to parse the QUOTA response. |
---|
155 | # The "-text" hash element contains the quota root name, and a list |
---|
156 | # of quota resource names, usages, and limits. Recognized names are |
---|
157 | # STORAGE (sum of message sizes, in kilobytes) and MESSAGE (number of |
---|
158 | # messages). See RFC 2087. |
---|
159 | sub quota_callback(@) { |
---|
160 | my %cb = @_; |
---|
161 | my ($root, $quotalist); |
---|
162 | print "In QUOTA callback: text $cb{-text}\n" if $debug; |
---|
163 | if (($root, $quotalist) = ($cb{-text} =~ /(\S*)\s+\((.*)\)/io)) { |
---|
164 | my ($storage_used, $storage_max, $message_used, $message_max); |
---|
165 | while ($quotalist) { |
---|
166 | my ($resource, $used, $max); |
---|
167 | ($resource, $used, $max, $quotalist) = split /\s/, $quotalist, 4; |
---|
168 | last unless $max; |
---|
169 | $resource = uc $resource; |
---|
170 | if ($resource eq "STORAGE") { |
---|
171 | $storage_used = $used; |
---|
172 | $storage_max = $max; |
---|
173 | } |
---|
174 | elsif ($resource eq "MESSAGE") { |
---|
175 | $message_used = $used; |
---|
176 | $message_max = $max; |
---|
177 | } |
---|
178 | } |
---|
179 | print_quota($root, $storage_used, $storage_max, |
---|
180 | $message_used, $message_max) |
---|
181 | if (defined $storage_max || defined $message_max); |
---|
182 | } |
---|
183 | } |
---|
184 | |
---|
185 | # Callback subroutine to parse the QUOTAROOT response. The "-text" |
---|
186 | # hash element contains the mailbox name, and zero or more quota root |
---|
187 | # names. This is currently used for debugging only. |
---|
188 | sub quotaroot_callback(@) { |
---|
189 | my %cb = @_; |
---|
190 | print "In QUOTAROOT callback: text $cb{-text}\n" if $debug; |
---|
191 | } |
---|
192 | |
---|
193 | # Callback subroutine to parse the CAPABILITY response. The "-rock" hash |
---|
194 | # element is a reference to the string in which to store the space-separated |
---|
195 | # capability names. |
---|
196 | sub capability_callback(@) { |
---|
197 | my %cb = @_; |
---|
198 | print "In CAPABILITY callback: keyword $cb{-keyword}, text $cb{-text}\n" |
---|
199 | if $debug; |
---|
200 | ${$cb{-rock}} = $cb{-text}; |
---|
201 | } |
---|
202 | |
---|
203 | # Close the connection to the IMAP server, and error out. |
---|
204 | sub close_and_errorout($) { |
---|
205 | close_connection(); |
---|
206 | errorout $_[0]; |
---|
207 | } |
---|
208 | |
---|
209 | # Logout from the IMAP server, and close the connection. |
---|
210 | sub close_connection() { |
---|
211 | $client->send('', '', "LOGOUT"); |
---|
212 | # Set the client reference to undef, so that perl invokes the |
---|
213 | # destructor, which closes the connection. Note that if we invoke |
---|
214 | # the destructor explicitly here, then perl will still invoke it |
---|
215 | # again when the program exits, thus touching memory which has |
---|
216 | # already been freed. |
---|
217 | $client = undef; |
---|
218 | } |
---|
219 | |
---|
220 | sub errorout($) { |
---|
221 | print STDERR "mailquota: $_[0]\n"; |
---|
222 | exit 1; |
---|
223 | } |
---|