line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
=head1 NAME
|
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
Mail::MsgStore - Complete mail client back end.
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
use Mail::MsgStore;
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
# set mailroot
|
10
|
|
|
|
|
|
|
Mail::MsgStore::mailroot($ENV{MAILROOT});
|
11
|
|
|
|
|
|
|
# get new messages from server
|
12
|
|
|
|
|
|
|
$count= Mail::MsgStore::getmail(\&prompt);
|
13
|
|
|
|
|
|
|
# send a Mail::Internet message
|
14
|
|
|
|
|
|
|
Mail::MsgStore::send($msg);
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
# add an account
|
17
|
|
|
|
|
|
|
Mail::MsgStore::acct_set('Joe User (work)',$password);
|
18
|
|
|
|
|
|
|
# delete an account
|
19
|
|
|
|
|
|
|
Mail::MsgStore::acct_del('Joe User (work)');
|
20
|
|
|
|
|
|
|
# change mailroot
|
21
|
|
|
|
|
|
|
Mail::MsgStore::mailroot('c:/mail');
|
22
|
|
|
|
|
|
|
# change from address
|
23
|
|
|
|
|
|
|
Mail::MsgStore::from('Brian Lalonde ');
|
24
|
|
|
|
|
|
|
# get SMTP server address
|
25
|
|
|
|
|
|
|
$smtp= Mail::MsgStore::smtp;
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
# add message
|
28
|
|
|
|
|
|
|
$MsgStore{'/'}= $msg; # auto-filter
|
29
|
|
|
|
|
|
|
$MsgStore{'path/to/folder/'}= $msg; # add to specific folder
|
30
|
|
|
|
|
|
|
# delete message
|
31
|
|
|
|
|
|
|
delete $MsgStore{'path/to/folder/msgid'};
|
32
|
|
|
|
|
|
|
# delete folder
|
33
|
|
|
|
|
|
|
delete $MsgStore{'path/to/folder/'};
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
# get message
|
36
|
|
|
|
|
|
|
$msg= $MsgStore{'path/to/folder/msgid'};
|
37
|
|
|
|
|
|
|
# mark message as read, unmark 'general' flag
|
38
|
|
|
|
|
|
|
$MsgStore{'path/to/folder/msgid'}= 'read, -general';
|
39
|
|
|
|
|
|
|
# get folder's message id list
|
40
|
|
|
|
|
|
|
@msgids= $MsgStore{'path/to/folder/'};
|
41
|
|
|
|
|
|
|
# get list of folders
|
42
|
|
|
|
|
|
|
@folders= keys %MsgStore;
|
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# move message
|
45
|
|
|
|
|
|
|
$MsgStore{'newfolder/'}= delete $MsgStore{'path/to/folder/msgid'};
|
46
|
|
|
|
|
|
|
# copy message
|
47
|
|
|
|
|
|
|
$MsgStore{'path/to/newfolder/'}= $MsgStore{'path/to/folder/msgid'};
|
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
The primary goal of this module is ease of use.
|
53
|
|
|
|
|
|
|
The Mail::Folder module, on top of not quite being complete yet, is a
|
54
|
|
|
|
|
|
|
pretty low-level API. I was very impressed with how Win32::TieRegistry
|
55
|
|
|
|
|
|
|
simplified an otherwise complex task, and decided to adopt a similar
|
56
|
|
|
|
|
|
|
interface for handling a mail store.
|
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
Another, equally important, reason for creating this module was
|
59
|
|
|
|
|
|
|
user-configurability.
|
60
|
|
|
|
|
|
|
I was unhappy with existing mail clients' filtering capabilities--
|
61
|
|
|
|
|
|
|
I wanted to pass every new message through some arbitrary Perl
|
62
|
|
|
|
|
|
|
code that was smart enough to forward, reply, send pages, activate
|
63
|
|
|
|
|
|
|
emergency-type alerts, etc. based on properties of the message.
|
64
|
|
|
|
|
|
|
What I didn't want was more bloatware--Exchange, Outlook and
|
65
|
|
|
|
|
|
|
Groupwise have already been written, and despite being huge,
|
66
|
|
|
|
|
|
|
still don't do enough.
|
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=head2 Storage Format
|
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
MsgStore uses a modified form of qmail's maildir format.
|
71
|
|
|
|
|
|
|
Here's how it works: new messages are downloaded into a
|
72
|
|
|
|
|
|
|
file guaranteed to have a unique, but incomplete, name.
|
73
|
|
|
|
|
|
|
The filename is completed once the entire message has
|
74
|
|
|
|
|
|
|
been successfully downloaded (the finishing of the filename
|
75
|
|
|
|
|
|
|
replaces maildir's state subdirectories).
|
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
The unique filename is generated as a dot-separated list of (uppercase)
|
78
|
|
|
|
|
|
|
hexadecimal numbers: seconds past epoch (12 digits), IP address
|
79
|
|
|
|
|
|
|
(8 digits), process id (4 digits), and download number (2 digits).
|
80
|
|
|
|
|
|
|
The IP should guarantee uniqueness to a machine, the time and pid narrows
|
81
|
|
|
|
|
|
|
it down to a specific process, and a simple incremental number ensures
|
82
|
|
|
|
|
|
|
that 256 messages can be downloaded per second and still retain
|
83
|
|
|
|
|
|
|
uniqueness. The filename also begins and ends with 'mail',
|
84
|
|
|
|
|
|
|
also separated by dots.
|
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
Message flags are part of the message id (although requesting a
|
87
|
|
|
|
|
|
|
message by an id with the wrong flags still works).
|
88
|
|
|
|
|
|
|
The flags are five characters delimited by parens.
|
89
|
|
|
|
|
|
|
Each position is either a dash (off) or a letter (on).
|
90
|
|
|
|
|
|
|
Order is significant, but since the letters spell the word
|
91
|
|
|
|
|
|
|
FLAGS, that shouldn't be a problem.
|
92
|
|
|
|
|
|
|
Here are what the letters stand for:
|
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
F flame
|
95
|
|
|
|
|
|
|
L list/group
|
96
|
|
|
|
|
|
|
A answered/replied
|
97
|
|
|
|
|
|
|
G general/flag
|
98
|
|
|
|
|
|
|
S seen/opened/read
|
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=head2 Warning
|
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
The storage format used for this module quickly becomes unusable for
|
103
|
|
|
|
|
|
|
large message stores; hundreds or thousands of tiny files are rarely
|
104
|
|
|
|
|
|
|
stored efficiently on the disk.
|
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
Although the module is completely usable, I hope it will inspire better
|
107
|
|
|
|
|
|
|
storage formats to use the same simple tied-hash interface.
|
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=head1 EVENTS
|
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
The message store allows definition of the following subroutines
|
112
|
|
|
|
|
|
|
in the F file located in the B directory:
|
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=over 4
|
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=item C
|
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Accepts the Mail::Internet message object.
|
119
|
|
|
|
|
|
|
The message's recipient account is available as
|
120
|
|
|
|
|
|
|
C in the message header.
|
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
Returns the name of the folder that the Mail::Internet $msg belongs in.
|
123
|
|
|
|
|
|
|
Returning undef implies the C.
|
124
|
|
|
|
|
|
|
Also, all message flags should be stored in the C header,
|
125
|
|
|
|
|
|
|
either as the native C<(FLOR!)> format of the message ID, or the english
|
126
|
|
|
|
|
|
|
equivalents: C.
|
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=item C
|
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
Accepts the Mail::Internet message object.
|
131
|
|
|
|
|
|
|
The message's recipient account is available as
|
132
|
|
|
|
|
|
|
C in the message header.
|
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
Returns a boolean value that determines whether the message should
|
135
|
|
|
|
|
|
|
be kept on the server.
|
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=item C
|
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Signs a message before it is sent.
|
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=back
|
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=head1 FUNCTIONS
|
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=head2 Sending and Receiving
|
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=over 4
|
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=item C
|
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
Logs on to each mail account, checking for new messages, which are
|
153
|
|
|
|
|
|
|
downloaded, passed to C and added.
|
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
Returns number of messages downloaded.
|
156
|
|
|
|
|
|
|
Requires a callback that will be used if there is a problem logging in:
|
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=over 4
|
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
=item C
|
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
Parameters: C<$acct> ISA Mail::Address: user is the POP3 username,
|
163
|
|
|
|
|
|
|
host is the POP3 server.
|
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
The function must return a password, or undef to cancel.
|
166
|
|
|
|
|
|
|
The password will be updated if it was initially set, or
|
167
|
|
|
|
|
|
|
left blank otherwise.
|
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=item C
|
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
Parameters: C<$status_message> is a string describing what is going on
|
172
|
|
|
|
|
|
|
suitable for GUI statusbars, etc.
|
173
|
|
|
|
|
|
|
C<$percent_done> is an integer between 0 and 100 (when included, else C)
|
174
|
|
|
|
|
|
|
suitable for feeding to progress bars, etc.
|
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=back
|
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=item C
|
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
Signs a Mail::Internet message, using the C function from the
|
181
|
|
|
|
|
|
|
user-defined F.
|
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=item C
|
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
Sends a Mail::Internet message, and stores a copy in C.
|
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
=back
|
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=head2 Settings
|
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=over 4
|
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=item C
|
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Gets/sets the root directory of the mailstore.
|
197
|
|
|
|
|
|
|
The user's login is appended to this directory.
|
198
|
|
|
|
|
|
|
If the directory doesn't exist, it is created.
|
199
|
|
|
|
|
|
|
If the directory doesn't contain an F
|
200
|
|
|
|
|
|
|
file, one (fully commented) is created.
|
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
Defaults to C<$ENV{MAILROOT}> or current dir unless set.
|
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=item C
|
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
Reloads the F file.
|
207
|
|
|
|
|
|
|
Useful if you provide an editing facility for that file,
|
208
|
|
|
|
|
|
|
or otherwise know that it has changed.
|
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=item C
|
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
Gets/sets the address of the outgoing mail server.
|
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=item C
|
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
Gets/sets the email C address.
|
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=item C
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
Returns a list of account strings.
|
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=item C
|
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
Adds/sets an POP3 account to the list handled by C.
|
225
|
|
|
|
|
|
|
Parameters: account and optional password.
|
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
Accounts strings are parsed by Mail::Address; the server portion is
|
228
|
|
|
|
|
|
|
used to connect, and the user portion is used to log in.
|
229
|
|
|
|
|
|
|
Everything else is mnemonic.
|
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
=item C
|
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
Deletes an account.
|
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=back
|
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=head2 The Address Book
|
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=over 4
|
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
=item C
|
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
Returns a list of (references to) hashes for the entire address book.
|
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=item C $value, ... )>
|
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
Add an entry to the address book.
|
249
|
|
|
|
|
|
|
The key for the new entry is returned.
|
250
|
|
|
|
|
|
|
The full list of fields is available in C<@addr_field>, pretty names
|
251
|
|
|
|
|
|
|
for the fields are in C<%addr_field> (neither exported by default).
|
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
Some fields of note:
|
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=over 4
|
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=item key
|
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
A guaranteed unique identifier for the address entry.
|
260
|
|
|
|
|
|
|
Auto-generated on insert.
|
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=item notes
|
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
The I field allowed to contain tabs and newlines.
|
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=item firstname, lastname, nickname, email
|
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
Standard mail-client stuff.
|
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=item tons more...
|
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
(and in no guaranteed order)
|
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
=back
|
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
=item C
|
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
Retrive the hash for an address.
|
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=item C $value, ...)>
|
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
Update fields on an existing address.
|
283
|
|
|
|
|
|
|
Boolean success is returned.
|
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
=item C 1 )>
|
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
Delete an entry from the address book.
|
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=item C
|
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
Gets/sets a comma or space-delimited list of LDAP servers.
|
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
=item C
|
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
Searches the address book fields specified by fields, looking for
|
296
|
|
|
|
|
|
|
records that match the regex, the C and C fields
|
297
|
|
|
|
|
|
|
by default.
|
298
|
|
|
|
|
|
|
(Actually, matches with C<"@addr{@fields}"=~ /regex/>.)
|
299
|
|
|
|
|
|
|
The special field C is also checked to match.
|
300
|
|
|
|
|
|
|
A list of (references to) hashes of matching records are returned,
|
301
|
|
|
|
|
|
|
plus a C field in each hash that contains the value of
|
302
|
|
|
|
|
|
|
either C<$field[0]> or C, depending on which field matched.
|
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
The result set is sorted by matching field.
|
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
This function is probably unneccessarily complex for most mail clients.
|
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=item C $namestart,
|
309
|
|
|
|
|
|
|
[ -number =E $hitnum, ] [ -fields =E \@fields, ] )>
|
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
This is a simpler version of L<"whosearch"> that just returns address strings
|
312
|
|
|
|
|
|
|
(rather than entire hashrefs for each record).
|
313
|
|
|
|
|
|
|
(Actually, matches with C<"@addr{@fields}"=~ /regex/>.)
|
314
|
|
|
|
|
|
|
By default, the C and C fields are used, just
|
315
|
|
|
|
|
|
|
as in L<"whosearch">.
|
316
|
|
|
|
|
|
|
The special field C is also checked to match.
|
317
|
|
|
|
|
|
|
In list context, the list of matching address strings is returned,
|
318
|
|
|
|
|
|
|
but in a scalar context, the C<$hitnum>-th element is returned
|
319
|
|
|
|
|
|
|
(this allows passing of a kind of "Nope, next one." request).
|
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
Each address is formatted this way:
|
322
|
|
|
|
|
|
|
C C ECE
|
323
|
|
|
|
|
|
|
unless the match was via C, in which case the nickname and
|
324
|
|
|
|
|
|
|
a tab character are prepended to the address string.
|
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
=item C
|
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
Searches the server(s) specified by C for an entry
|
329
|
|
|
|
|
|
|
that starts with C<$startswith>, and returns a list similar to
|
330
|
|
|
|
|
|
|
L<"addrsearch">. Ignores queries shorter than 3 letters.
|
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
This function is called by L<"addrsearch">, and probably needn't be
|
333
|
|
|
|
|
|
|
called directly.
|
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
=back
|
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
=head2 Utility
|
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=over 4
|
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
=item C
|
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
Searches messages in C<$folder> (and all subfolders) for messages
|
345
|
|
|
|
|
|
|
that produce a true value when passed to C<&match>.
|
346
|
|
|
|
|
|
|
Returns a list of fully-qualified message IDs.
|
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=item C
|
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
Returns a text-only body of C<$msg>.
|
351
|
|
|
|
|
|
|
If the actual C<$msg> is a C or C,
|
352
|
|
|
|
|
|
|
for example, this just gives you the text portion of the message for
|
353
|
|
|
|
|
|
|
display purposes.
|
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=item C
|
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
Given a fully-qualified messsage ID (one that begins with the folder path),
|
358
|
|
|
|
|
|
|
breaks the string into folder path and message ID.
|
359
|
|
|
|
|
|
|
(Similar in spirit to the L module.)
|
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
=item C
|
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
Given a message ID whose flags may have changed (the message ID contains
|
364
|
|
|
|
|
|
|
the message flags), returns the new message ID.
|
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=item C
|
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
Returns a valid flagstring for the Mail::MsgStore message ID,
|
369
|
|
|
|
|
|
|
given either a msgid or english string (C<'+read -list !flame'>)
|
370
|
|
|
|
|
|
|
to parse.
|
371
|
|
|
|
|
|
|
Mostly for internal use.
|
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=back
|
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=head1 AUTHOR
|
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
v, Ev@rant.scriptmania.comE
|
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
=head1 SEE ALSO
|
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
perl(1),
|
382
|
|
|
|
|
|
|
Sys::UniqueId,
|
383
|
|
|
|
|
|
|
Mail::Internet,
|
384
|
|
|
|
|
|
|
Mail::Folder,
|
385
|
|
|
|
|
|
|
Win32::TieRegistry,
|
386
|
|
|
|
|
|
|
Net::LDAP,
|
387
|
|
|
|
|
|
|
Net::POP3,
|
388
|
|
|
|
|
|
|
Time::ParseDate
|
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
=cut
|
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
package Mail::MsgStore;
|
393
|
|
|
|
|
|
|
require Exporter;
|
394
|
1
|
|
|
1
|
|
876
|
use strict;
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
30
|
|
395
|
1
|
|
|
1
|
|
4
|
use Carp;
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
91
|
|
396
|
1
|
|
|
1
|
|
5
|
use File::Find;
|
|
1
|
|
|
|
|
12
|
|
|
1
|
|
|
|
|
59
|
|
397
|
1
|
|
|
1
|
|
5
|
use File::Path;
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
208
|
|
398
|
1
|
|
|
1
|
|
1271
|
use Mail::Address;
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
use Mail::Internet;
|
400
|
|
|
|
|
|
|
use MIME::Entity;
|
401
|
|
|
|
|
|
|
use Net::LDAP;
|
402
|
|
|
|
|
|
|
use Net::POP3 2.20;
|
403
|
|
|
|
|
|
|
use Time::ParseDate;
|
404
|
|
|
|
|
|
|
use Sys::UniqueID;
|
405
|
|
|
|
|
|
|
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS &isa);
|
406
|
|
|
|
|
|
|
use vars qw($MsgStore %MsgStore $mailroot @folder);
|
407
|
|
|
|
|
|
|
use vars qw(@addr_field %addr_field %mime_ext);
|
408
|
|
|
|
|
|
|
use vars qw($_default_script %_folder_sort $_noflock);
|
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
$VERSION= '1.51';
|
411
|
|
|
|
|
|
|
@ISA= qw(Exporter);
|
412
|
|
|
|
|
|
|
@EXPORT= qw(%MsgStore);
|
413
|
|
|
|
|
|
|
@EXPORT_OK= qw(accounts acct_set acct_del
|
414
|
|
|
|
|
|
|
getmail mailroot msgsearch simplifymsg
|
415
|
|
|
|
|
|
|
smtp from ldaps signmsg sendmsg %mime_ext
|
416
|
|
|
|
|
|
|
address addresses whosearch addrsearch ldapsearch
|
417
|
|
|
|
|
|
|
flags msgid msgpath load_events);
|
418
|
|
|
|
|
|
|
%EXPORT_TAGS=
|
419
|
|
|
|
|
|
|
(
|
420
|
|
|
|
|
|
|
ALL => [ @EXPORT, @EXPORT_OK ],
|
421
|
|
|
|
|
|
|
ACCT => [ @EXPORT, qw(accounts acct_set acct_del) ],
|
422
|
|
|
|
|
|
|
READ => [ @EXPORT, qw(getmail mailroot msgsearch simplifymsg) ],
|
423
|
|
|
|
|
|
|
SEND => [ @EXPORT, qw(smtp from ldaps signmsg sendmsg %mime_ext) ],
|
424
|
|
|
|
|
|
|
ADDR => [ @EXPORT, qw(address addresses whosearch addrsearch ldapsearch) ],
|
425
|
|
|
|
|
|
|
UTIL => [ @EXPORT, qw(flags msgid msgpath load_events) ],
|
426
|
|
|
|
|
|
|
);
|
427
|
|
|
|
|
|
|
*isa = \&UNIVERSAL::isa;
|
428
|
|
|
|
|
|
|
$_noflock= 1 if $^O eq 'MSWin32' and Win32::IsWin95;
|
429
|
|
|
|
|
|
|
END { unlink $_noflock if -f $_noflock }
|
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
###################################
|
432
|
|
|
|
|
|
|
#
|
433
|
|
|
|
|
|
|
# Methods
|
434
|
|
|
|
|
|
|
#
|
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
sub mailroot
|
437
|
|
|
|
|
|
|
{
|
438
|
|
|
|
|
|
|
if(@_)
|
439
|
|
|
|
|
|
|
{ # change mailroot
|
440
|
|
|
|
|
|
|
unlink $_noflock if $_noflock and -f $_noflock;
|
441
|
|
|
|
|
|
|
local $_= shift;
|
442
|
|
|
|
|
|
|
y,\\,/,; s,/$,,;
|
443
|
|
|
|
|
|
|
$_.= '/'.getlogin;
|
444
|
|
|
|
|
|
|
unless(-d $_)
|
445
|
|
|
|
|
|
|
{
|
446
|
|
|
|
|
|
|
mkpath $_;
|
447
|
|
|
|
|
|
|
# TODO: if(uname) chmod/Win32::FilePerms
|
448
|
|
|
|
|
|
|
# to secure mail dir
|
449
|
|
|
|
|
|
|
}
|
450
|
|
|
|
|
|
|
$mailroot= $_;
|
451
|
|
|
|
|
|
|
load_events();
|
452
|
|
|
|
|
|
|
}
|
453
|
|
|
|
|
|
|
if($_noflock)
|
454
|
|
|
|
|
|
|
{
|
455
|
|
|
|
|
|
|
croak "Only one MsgStore application at a time, please!\n".
|
456
|
|
|
|
|
|
|
"(Your system can't lock files.)\n"
|
457
|
|
|
|
|
|
|
if -f "$mailroot/MsgStore.lck";
|
458
|
|
|
|
|
|
|
$_noflock= "$mailroot/MsgStore.lck";
|
459
|
|
|
|
|
|
|
open LOCK, ">$_noflock"
|
460
|
|
|
|
|
|
|
or croak "Unable to create lock file: $!\n";
|
461
|
|
|
|
|
|
|
close LOCK;
|
462
|
|
|
|
|
|
|
}
|
463
|
|
|
|
|
|
|
return $mailroot;
|
464
|
|
|
|
|
|
|
}
|
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
sub _getkept(\%)
|
467
|
|
|
|
|
|
|
{
|
468
|
|
|
|
|
|
|
my $kept= shift;
|
469
|
|
|
|
|
|
|
if(open KEPT, "<$mailroot/kept")
|
470
|
|
|
|
|
|
|
{
|
471
|
|
|
|
|
|
|
local $_;
|
472
|
|
|
|
|
|
|
while()
|
473
|
|
|
|
|
|
|
{
|
474
|
|
|
|
|
|
|
chomp;
|
475
|
|
|
|
|
|
|
my($key,$val)= split /\t/;
|
476
|
|
|
|
|
|
|
$$kept{$key}= $val;
|
477
|
|
|
|
|
|
|
}
|
478
|
|
|
|
|
|
|
close KEPT;
|
479
|
|
|
|
|
|
|
}
|
480
|
|
|
|
|
|
|
}
|
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
sub _savekept(\%)
|
483
|
|
|
|
|
|
|
{
|
484
|
|
|
|
|
|
|
my $kept= shift;
|
485
|
|
|
|
|
|
|
if(open KEPT, ">$mailroot/kept")
|
486
|
|
|
|
|
|
|
{
|
487
|
|
|
|
|
|
|
for(keys %$kept)
|
488
|
|
|
|
|
|
|
{ print KEPT $_, "\t", $$kept{$_}, "\n"; }
|
489
|
|
|
|
|
|
|
close KEPT;
|
490
|
|
|
|
|
|
|
}
|
491
|
|
|
|
|
|
|
}
|
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
sub getmail(&;&)
|
494
|
|
|
|
|
|
|
{
|
495
|
|
|
|
|
|
|
my($prompt,$status)= @_;
|
496
|
|
|
|
|
|
|
$status= sub{} unless $status;
|
497
|
|
|
|
|
|
|
my $started= time;
|
498
|
|
|
|
|
|
|
my %kept; _getkept(%kept);
|
499
|
|
|
|
|
|
|
dbmopen my %acct, $mailroot.'/accounts', 0600
|
500
|
|
|
|
|
|
|
or croak "Unable to open accounts database: $!\n";
|
501
|
|
|
|
|
|
|
my($NewMsg,$index,@acct)= (0,0,keys %acct);
|
502
|
|
|
|
|
|
|
my $grain= 10_000/@acct;
|
503
|
|
|
|
|
|
|
ACCT: for(@acct)
|
504
|
|
|
|
|
|
|
{
|
505
|
|
|
|
|
|
|
my $progress= $index*$grain/100;
|
506
|
|
|
|
|
|
|
&$status("Checking $_...",$progress);
|
507
|
|
|
|
|
|
|
my($acct)= Mail::Address->parse($_);
|
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
# Connect and log in to POP3 server
|
510
|
|
|
|
|
|
|
carp("Unable to connect to server ".$acct->host().": $!\n"), next ACCT
|
511
|
|
|
|
|
|
|
unless my $conn= new Net::POP3($acct->host());
|
512
|
|
|
|
|
|
|
my $count= $conn->apop($acct->user(),($acct{$_} ^ getlogin)) if $conn;
|
513
|
|
|
|
|
|
|
unless(defined $count)
|
514
|
|
|
|
|
|
|
{ # APOP didn't work, try basic auth
|
515
|
|
|
|
|
|
|
&$status("Connecting to $_...",$progress);
|
516
|
|
|
|
|
|
|
$conn->quit() if $conn; # reset connection (some servers get stuck)
|
517
|
|
|
|
|
|
|
$conn= new Net::POP3($acct->host());
|
518
|
|
|
|
|
|
|
$count= $conn->login($acct->user(),($acct{$_} ^ getlogin)) if $conn;
|
519
|
|
|
|
|
|
|
}
|
520
|
|
|
|
|
|
|
until(defined $count)
|
521
|
|
|
|
|
|
|
{
|
522
|
|
|
|
|
|
|
&$status("Login failed for $_...",$progress);
|
523
|
|
|
|
|
|
|
my $pass= &$prompt($acct);
|
524
|
|
|
|
|
|
|
next ACCT unless defined $pass;
|
525
|
|
|
|
|
|
|
unless(defined($count= $conn->apop($acct->user(),$pass)))
|
526
|
|
|
|
|
|
|
{ # APOP didn't work, try basic auth
|
527
|
|
|
|
|
|
|
$conn->quit() if $conn; # reset connection (some servers get stuck)
|
528
|
|
|
|
|
|
|
$conn= new Net::POP3($acct->host());
|
529
|
|
|
|
|
|
|
$count= $conn->login($acct->user(),$pass) if $conn;
|
530
|
|
|
|
|
|
|
}
|
531
|
|
|
|
|
|
|
$acct{$_}= $pass ^ getlogin if $acct{$_};
|
532
|
|
|
|
|
|
|
}
|
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
# Get messages
|
535
|
|
|
|
|
|
|
&$status("Connected to $_...",$progress);
|
536
|
|
|
|
|
|
|
&$status("No new messages for $_...",$progress), next unless int $count;
|
537
|
|
|
|
|
|
|
load_events();
|
538
|
|
|
|
|
|
|
my($newmsg,$msggrain)= (0, $grain/$count );
|
539
|
|
|
|
|
|
|
for my $msgnum (1..$count)
|
540
|
|
|
|
|
|
|
{
|
541
|
|
|
|
|
|
|
&$status("$_: $msgnum of $count",
|
542
|
|
|
|
|
|
|
($msgnum-1)*$msggrain/100 + $progress);
|
543
|
|
|
|
|
|
|
my $uidl= $conn->uidl($msgnum);
|
544
|
|
|
|
|
|
|
unless($uidl)
|
545
|
|
|
|
|
|
|
{ # not all servers support UIDL, here's a substitute
|
546
|
|
|
|
|
|
|
my $head= new Mail::Header($conn->top($msgnum));
|
547
|
|
|
|
|
|
|
$uidl= join($;,$head->get('Message-Id'),$conn->list($msgnum));
|
548
|
|
|
|
|
|
|
$uidl=~ y/\n//d;
|
549
|
|
|
|
|
|
|
}
|
550
|
|
|
|
|
|
|
if($kept{$_,$uidl})
|
551
|
|
|
|
|
|
|
{ $kept{$_,$uidl}= time; next }
|
552
|
|
|
|
|
|
|
# NULL-value headers really confuse Mail::Internet
|
553
|
|
|
|
|
|
|
my @msgdata= grep { !/./..1 or /^(\s|\S+:\s*\S)/ } @{$conn->get($msgnum)};
|
554
|
|
|
|
|
|
|
my $msg= new Mail::Internet(\@msgdata);
|
555
|
|
|
|
|
|
|
$msg->head->add('X-Recipient-Account',$_);
|
556
|
|
|
|
|
|
|
$MsgStore{'/'}= $msg; # filter into message store
|
557
|
|
|
|
|
|
|
next unless $msg->get('Received'); # messages disappearing >:(
|
558
|
|
|
|
|
|
|
if(Mail::MsgStore::Event::keep($msg))
|
559
|
|
|
|
|
|
|
{ # keep message (remember uidl)
|
560
|
|
|
|
|
|
|
$kept{$_,$uidl}= time;
|
561
|
|
|
|
|
|
|
}
|
562
|
|
|
|
|
|
|
else
|
563
|
|
|
|
|
|
|
{ # delete from server
|
564
|
|
|
|
|
|
|
$conn->delete($msgnum);
|
565
|
|
|
|
|
|
|
}
|
566
|
|
|
|
|
|
|
$newmsg++;$NewMsg++;
|
567
|
|
|
|
|
|
|
}
|
568
|
|
|
|
|
|
|
$conn->quit();
|
569
|
|
|
|
|
|
|
$newmsg= 'no' unless $newmsg;
|
570
|
|
|
|
|
|
|
&$status("$_: $newmsg new messages.",++$index*$grain/100);
|
571
|
|
|
|
|
|
|
}
|
572
|
|
|
|
|
|
|
dbmclose %acct;
|
573
|
|
|
|
|
|
|
for(keys %kept) { delete $kept{$_} unless $kept{$_} > $started; }
|
574
|
|
|
|
|
|
|
_savekept(%kept);
|
575
|
|
|
|
|
|
|
$NewMsg= 'No' unless $NewMsg;
|
576
|
|
|
|
|
|
|
&$status("$NewMsg New Messages.",100);
|
577
|
|
|
|
|
|
|
return $NewMsg;
|
578
|
|
|
|
|
|
|
}
|
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
sub from
|
581
|
|
|
|
|
|
|
{
|
582
|
|
|
|
|
|
|
my($value)= @_;
|
583
|
|
|
|
|
|
|
dbmopen my %settings, $mailroot.'/settings', 0600
|
584
|
|
|
|
|
|
|
or croak "Unable to open settings database: $!\n";
|
585
|
|
|
|
|
|
|
$settings{from}= $value if $value;
|
586
|
|
|
|
|
|
|
$value= $settings{from};
|
587
|
|
|
|
|
|
|
dbmclose %settings;
|
588
|
|
|
|
|
|
|
return $value;
|
589
|
|
|
|
|
|
|
}
|
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
sub smtp
|
592
|
|
|
|
|
|
|
{
|
593
|
|
|
|
|
|
|
my($value)= @_;
|
594
|
|
|
|
|
|
|
dbmopen my %settings, $mailroot.'/settings', 0600
|
595
|
|
|
|
|
|
|
or croak "Unable to open settings database: $!\n";
|
596
|
|
|
|
|
|
|
$settings{smtp}= $value if $value;
|
597
|
|
|
|
|
|
|
$value= $settings{smtp};
|
598
|
|
|
|
|
|
|
dbmclose %settings;
|
599
|
|
|
|
|
|
|
return $value;
|
600
|
|
|
|
|
|
|
}
|
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
sub ldaps
|
603
|
|
|
|
|
|
|
{
|
604
|
|
|
|
|
|
|
my($value)= @_;
|
605
|
|
|
|
|
|
|
dbmopen my %settings, $mailroot.'/settings', 0600
|
606
|
|
|
|
|
|
|
or croak "Unable to open settings database: $!\n";
|
607
|
|
|
|
|
|
|
$settings{ldap}= $value if $value;
|
608
|
|
|
|
|
|
|
$value= $settings{ldap};
|
609
|
|
|
|
|
|
|
dbmclose %settings;
|
610
|
|
|
|
|
|
|
return $value;
|
611
|
|
|
|
|
|
|
}
|
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
sub load_events
|
614
|
|
|
|
|
|
|
{ # event script default/init
|
615
|
|
|
|
|
|
|
my $script= $mailroot.'/events.pl';
|
616
|
|
|
|
|
|
|
unless(-f $script)
|
617
|
|
|
|
|
|
|
{
|
618
|
|
|
|
|
|
|
open SCRIPT, ">$script"
|
619
|
|
|
|
|
|
|
or croak "Unable to create default event script file: $!\n";
|
620
|
|
|
|
|
|
|
print SCRIPT $_default_script;
|
621
|
|
|
|
|
|
|
close SCRIPT;
|
622
|
|
|
|
|
|
|
}
|
623
|
|
|
|
|
|
|
{ package Mail::MsgStore::Event;
|
624
|
|
|
|
|
|
|
do $script;
|
625
|
|
|
|
|
|
|
}
|
626
|
|
|
|
|
|
|
croak "Error(s) in user script: $script.\n$@\n" if $@;
|
627
|
|
|
|
|
|
|
}
|
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
sub flags
|
630
|
|
|
|
|
|
|
{
|
631
|
|
|
|
|
|
|
return '(-----)' unless local $_= shift;
|
632
|
|
|
|
|
|
|
return $_ if s/^([F\-][L\-][A\-][G\-][S\-])$/\(\U($1)\)/i;
|
633
|
|
|
|
|
|
|
return uc$1 if m/(\([F\-][L\-][A\-][G\-][S\-]\))/i;
|
634
|
|
|
|
|
|
|
shift=~ /\(?([F\-][L\-][A\-][G\-][S\-])\)?/i;
|
635
|
|
|
|
|
|
|
my @flag= split //, ($1 or '-----');
|
636
|
|
|
|
|
|
|
for(split /[^!\+\-\w]+/)
|
637
|
|
|
|
|
|
|
{
|
638
|
|
|
|
|
|
|
$flag[0]= ( /\-/ ? '-' : ( /!/ ? ( $flag[0] eq '-' ? 'F' : '-' ) : 'F' ) )
|
639
|
|
|
|
|
|
|
and next if /\b(flame|troll)\b/i;
|
640
|
|
|
|
|
|
|
$flag[1]= ( /\-/ ? '-' : ( /!/ ? ( $flag[1] eq '-' ? 'L' : '-' ) : 'L' ) )
|
641
|
|
|
|
|
|
|
and next if /\b(list|group|sig)\b/i;
|
642
|
|
|
|
|
|
|
$flag[2]= ( /\-/ ? '-' : ( /!/ ? ( $flag[2] eq '-' ? 'A' : '-' ) : 'A' ) )
|
643
|
|
|
|
|
|
|
and next if /\b(answer(ed)?|repl(y|ied))\b/i;
|
644
|
|
|
|
|
|
|
$flag[4]= ( /\-/ ? '-' : ( /!/ ? ( $flag[4] eq '-' ? 'S' : '-' ) : 'S' ) )
|
645
|
|
|
|
|
|
|
and next if /\b(seen|open(ed)?|read)\b/i;
|
646
|
|
|
|
|
|
|
$flag[3]= ( /\-/ ? '-' : ( /!/ ? ( $flag[3] eq '-' ? 'G' : '-' ) : 'G' ) );
|
647
|
|
|
|
|
|
|
}
|
648
|
|
|
|
|
|
|
local $";
|
649
|
|
|
|
|
|
|
return "(@flag[0..4])";
|
650
|
|
|
|
|
|
|
}
|
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
sub sendmsg($)
|
653
|
|
|
|
|
|
|
{
|
654
|
|
|
|
|
|
|
my $msg= shift;
|
655
|
|
|
|
|
|
|
return unless isa($msg,'Mail::Internet');
|
656
|
|
|
|
|
|
|
$msg->head->add('X-Mailer','Mail::MsgStore');
|
657
|
|
|
|
|
|
|
$msg->head->combine('X-Mailer',' and ');
|
658
|
|
|
|
|
|
|
return unless $msg->smtpsend( Host => smtp() );
|
659
|
|
|
|
|
|
|
return 1;
|
660
|
|
|
|
|
|
|
}
|
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
sub signmsg($)
|
663
|
|
|
|
|
|
|
{
|
664
|
|
|
|
|
|
|
my $msg= shift;
|
665
|
|
|
|
|
|
|
die "[signmsg] No message to sign!" unless $msg;
|
666
|
|
|
|
|
|
|
$msg->remove_sig; # may want to re-sign (random quotes, ...)
|
667
|
|
|
|
|
|
|
return Mail::MsgStore::Event::sign($msg);
|
668
|
|
|
|
|
|
|
#return $msg;
|
669
|
|
|
|
|
|
|
}
|
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
sub msgpath
|
672
|
|
|
|
|
|
|
{
|
673
|
|
|
|
|
|
|
local $_= shift;
|
674
|
|
|
|
|
|
|
return '/' if m<^[@*/!?\\]$>; # convenience root
|
675
|
|
|
|
|
|
|
return if /^[<|>].*[<|>]$/; # not a path
|
676
|
|
|
|
|
|
|
s{2,}|\\>>g; # clean path
|
677
|
|
|
|
|
|
|
return $_ if -d "$mailroot/$_"
|
678
|
|
|
|
|
|
|
or s$><> or not m<^\W?(.*)/(mail[^/]+mail)$>i;
|
679
|
|
|
|
|
|
|
return($1,$2);
|
680
|
|
|
|
|
|
|
}
|
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
sub msgid
|
683
|
|
|
|
|
|
|
{
|
684
|
|
|
|
|
|
|
my($folder,$msgid)= @_;
|
685
|
|
|
|
|
|
|
return unless $msgid;
|
686
|
|
|
|
|
|
|
return $msgid if -f "$mailroot/$folder/$msgid";
|
687
|
|
|
|
|
|
|
$msgid=~ s/\./\\./g;
|
688
|
|
|
|
|
|
|
$msgid=~ s/\(.....\)/\\(.....\\)/; # flag-independant msgid search
|
689
|
|
|
|
|
|
|
opendir FOLDER, "$mailroot/$folder/"
|
690
|
|
|
|
|
|
|
or croak "Unable to open mail folder at '$mailroot/$folder/'.\n";
|
691
|
|
|
|
|
|
|
$msgid= ( grep { /^$msgid$/i } readdir FOLDER )[0];
|
692
|
|
|
|
|
|
|
closedir FOLDER;
|
693
|
|
|
|
|
|
|
return unless $msgid;
|
694
|
|
|
|
|
|
|
return $msgid;
|
695
|
|
|
|
|
|
|
}
|
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
sub accounts()
|
698
|
|
|
|
|
|
|
{ # list accounts
|
699
|
|
|
|
|
|
|
dbmopen my %acct, $mailroot.'/accounts', 0600
|
700
|
|
|
|
|
|
|
or croak "Unable to open accounts database: $!\n";
|
701
|
|
|
|
|
|
|
my @acct= keys %acct;
|
702
|
|
|
|
|
|
|
dbmclose %acct;
|
703
|
|
|
|
|
|
|
return @acct;
|
704
|
|
|
|
|
|
|
}
|
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
sub acct_set($;$)
|
707
|
|
|
|
|
|
|
{ # add account: name@server, password
|
708
|
|
|
|
|
|
|
my($acct,$pass)= @_;
|
709
|
|
|
|
|
|
|
dbmopen my %acct, $mailroot.'/accounts', 0600
|
710
|
|
|
|
|
|
|
or croak "Unable to open accounts database: $!\n";
|
711
|
|
|
|
|
|
|
$acct{$acct}= ($pass ^ getlogin);
|
712
|
|
|
|
|
|
|
dbmclose %acct;
|
713
|
|
|
|
|
|
|
return 1;
|
714
|
|
|
|
|
|
|
}
|
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
sub acct_del($)
|
717
|
|
|
|
|
|
|
{ # remove account
|
718
|
|
|
|
|
|
|
my($acct)= @_;
|
719
|
|
|
|
|
|
|
dbmopen my %acct, $mailroot.'/accounts', 0600
|
720
|
|
|
|
|
|
|
or croak "Unable to open accounts database: $!\n";
|
721
|
|
|
|
|
|
|
delete $acct{$acct};
|
722
|
|
|
|
|
|
|
dbmclose %acct;
|
723
|
|
|
|
|
|
|
return 1;
|
724
|
|
|
|
|
|
|
}
|
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
sub msgsearch
|
727
|
|
|
|
|
|
|
{
|
728
|
|
|
|
|
|
|
my $folder= msgid(shift);
|
729
|
|
|
|
|
|
|
my $match= shift;
|
730
|
|
|
|
|
|
|
my @match;
|
731
|
|
|
|
|
|
|
my $wanted= sub
|
732
|
|
|
|
|
|
|
{
|
733
|
|
|
|
|
|
|
return unless /^mail.*mail$/i;
|
734
|
|
|
|
|
|
|
(my $folder= $File::Find::dir.'/')=~ s<^$mailroot/><>;
|
735
|
|
|
|
|
|
|
push @match, $folder.$_ if &$match($MsgStore{"$folder$_"});
|
736
|
|
|
|
|
|
|
};
|
737
|
|
|
|
|
|
|
finddepth( $wanted, "$mailroot/$folder" );
|
738
|
|
|
|
|
|
|
return @match;
|
739
|
|
|
|
|
|
|
}
|
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
@addr_field=
|
742
|
|
|
|
|
|
|
qw(
|
743
|
|
|
|
|
|
|
key
|
744
|
|
|
|
|
|
|
firstname
|
745
|
|
|
|
|
|
|
lastname
|
746
|
|
|
|
|
|
|
nickname
|
747
|
|
|
|
|
|
|
email
|
748
|
|
|
|
|
|
|
url
|
749
|
|
|
|
|
|
|
chat
|
750
|
|
|
|
|
|
|
title
|
751
|
|
|
|
|
|
|
organization
|
752
|
|
|
|
|
|
|
department
|
753
|
|
|
|
|
|
|
birthdate
|
754
|
|
|
|
|
|
|
workphone
|
755
|
|
|
|
|
|
|
homephone
|
756
|
|
|
|
|
|
|
cellphone
|
757
|
|
|
|
|
|
|
pager
|
758
|
|
|
|
|
|
|
fax
|
759
|
|
|
|
|
|
|
modem
|
760
|
|
|
|
|
|
|
street
|
761
|
|
|
|
|
|
|
city
|
762
|
|
|
|
|
|
|
state
|
763
|
|
|
|
|
|
|
zip
|
764
|
|
|
|
|
|
|
country
|
765
|
|
|
|
|
|
|
notes
|
766
|
|
|
|
|
|
|
);
|
767
|
|
|
|
|
|
|
@addr_field{@addr_field}=
|
768
|
|
|
|
|
|
|
(
|
769
|
|
|
|
|
|
|
'',
|
770
|
|
|
|
|
|
|
'First Name',
|
771
|
|
|
|
|
|
|
'Last Name',
|
772
|
|
|
|
|
|
|
'Nickname',
|
773
|
|
|
|
|
|
|
'email',
|
774
|
|
|
|
|
|
|
'URL',
|
775
|
|
|
|
|
|
|
'ICQ/AIM/IRC',
|
776
|
|
|
|
|
|
|
'Title',
|
777
|
|
|
|
|
|
|
'Organization',
|
778
|
|
|
|
|
|
|
'Department',
|
779
|
|
|
|
|
|
|
'Birthdate',
|
780
|
|
|
|
|
|
|
'Work Phone',
|
781
|
|
|
|
|
|
|
'Home Phone',
|
782
|
|
|
|
|
|
|
'Cell Phone',
|
783
|
|
|
|
|
|
|
'Pager',
|
784
|
|
|
|
|
|
|
'Fax',
|
785
|
|
|
|
|
|
|
'Modem',
|
786
|
|
|
|
|
|
|
'Street Address',
|
787
|
|
|
|
|
|
|
'City',
|
788
|
|
|
|
|
|
|
'State',
|
789
|
|
|
|
|
|
|
'ZIP',
|
790
|
|
|
|
|
|
|
'Country',
|
791
|
|
|
|
|
|
|
'Notes',
|
792
|
|
|
|
|
|
|
);
|
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
sub address
|
795
|
|
|
|
|
|
|
{
|
796
|
|
|
|
|
|
|
local $_;
|
797
|
|
|
|
|
|
|
my $key;
|
798
|
|
|
|
|
|
|
$key= shift if @_&1;
|
799
|
|
|
|
|
|
|
my %addr= @_;
|
800
|
|
|
|
|
|
|
$key= $addr{key} unless $key;
|
801
|
|
|
|
|
|
|
if($key and !@_)
|
802
|
|
|
|
|
|
|
{ # retrieve address
|
803
|
|
|
|
|
|
|
open ADDR, "<$mailroot/address.tsv" or return;
|
804
|
|
|
|
|
|
|
while() { last if /^$key\t/; }
|
805
|
|
|
|
|
|
|
close ADDR;
|
806
|
|
|
|
|
|
|
return unless /^$key\t/;
|
807
|
|
|
|
|
|
|
chomp;
|
808
|
|
|
|
|
|
|
@addr{@addr_field}= split /\t/;
|
809
|
|
|
|
|
|
|
if($addr{notes} and $addr{notes}=~ /\\/)
|
810
|
|
|
|
|
|
|
{ # unescape
|
811
|
|
|
|
|
|
|
$addr{notes}=~ s/\\\\/\\/g;
|
812
|
|
|
|
|
|
|
$addr{notes}=~ s/\\n/\n/g;
|
813
|
|
|
|
|
|
|
$addr{notes}=~ s/\\t/\t/g;
|
814
|
|
|
|
|
|
|
}
|
815
|
|
|
|
|
|
|
return %addr;
|
816
|
|
|
|
|
|
|
}
|
817
|
|
|
|
|
|
|
else
|
818
|
|
|
|
|
|
|
{
|
819
|
|
|
|
|
|
|
if($addr{notes})
|
820
|
|
|
|
|
|
|
{ # escape
|
821
|
|
|
|
|
|
|
$addr{notes}=~ s/\\/\\\\/g;
|
822
|
|
|
|
|
|
|
$addr{notes}=~ s/\n/\\n/g;
|
823
|
|
|
|
|
|
|
$addr{notes}=~ s/\t/\\t/g;
|
824
|
|
|
|
|
|
|
}
|
825
|
|
|
|
|
|
|
if($key)
|
826
|
|
|
|
|
|
|
{ # update/delete key
|
827
|
|
|
|
|
|
|
my $tempaddr= 'addr.'.&uniqueid.'.addr';
|
828
|
|
|
|
|
|
|
open NADDR, ">$mailroot/$tempaddr" or return;
|
829
|
|
|
|
|
|
|
open ADDR, "<$mailroot/address.tsv" or return;
|
830
|
|
|
|
|
|
|
flock(ADDR,1) unless $_noflock;
|
831
|
|
|
|
|
|
|
if($addr{Delete})
|
832
|
|
|
|
|
|
|
{ # delete entry
|
833
|
|
|
|
|
|
|
while() { print NADDR unless /^$key\t/; }
|
834
|
|
|
|
|
|
|
}
|
835
|
|
|
|
|
|
|
else
|
836
|
|
|
|
|
|
|
{ # update entry
|
837
|
|
|
|
|
|
|
my %prev;
|
838
|
|
|
|
|
|
|
while() { last if /^$key\t/; print NADDR; }
|
839
|
|
|
|
|
|
|
chomp;
|
840
|
|
|
|
|
|
|
@prev{@addr_field}= split /\t/;
|
841
|
|
|
|
|
|
|
for(keys %addr) { $prev{$_}= $addr{$_}; }
|
842
|
|
|
|
|
|
|
print NADDR join("\t",@prev{@addr_field}),"\n";
|
843
|
|
|
|
|
|
|
print NADDR while();
|
844
|
|
|
|
|
|
|
}
|
845
|
|
|
|
|
|
|
close NADDR;
|
846
|
|
|
|
|
|
|
close ADDR;
|
847
|
|
|
|
|
|
|
unlink "$mailroot/address.tsv";
|
848
|
|
|
|
|
|
|
rename "$mailroot/$tempaddr", "$mailroot/address.tsv";
|
849
|
|
|
|
|
|
|
return 1;
|
850
|
|
|
|
|
|
|
}
|
851
|
|
|
|
|
|
|
else
|
852
|
|
|
|
|
|
|
{ # new: insert (append)
|
853
|
|
|
|
|
|
|
$addr{key}= &uniqueid;
|
854
|
|
|
|
|
|
|
open ADDR, ">>$mailroot/address.tsv" or return;
|
855
|
|
|
|
|
|
|
flock(ADDR,2) unless $_noflock;
|
856
|
|
|
|
|
|
|
print ADDR join("\t",@addr{@addr_field}),"\n";
|
857
|
|
|
|
|
|
|
close ADDR;
|
858
|
|
|
|
|
|
|
return $addr{key};
|
859
|
|
|
|
|
|
|
}
|
860
|
|
|
|
|
|
|
}
|
861
|
|
|
|
|
|
|
return;
|
862
|
|
|
|
|
|
|
}
|
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
sub addresses
|
865
|
|
|
|
|
|
|
{
|
866
|
|
|
|
|
|
|
local $_;
|
867
|
|
|
|
|
|
|
my $query= shift;
|
868
|
|
|
|
|
|
|
my $field= (shift or 'firstname');
|
869
|
|
|
|
|
|
|
my(%addr,@match);
|
870
|
|
|
|
|
|
|
open ADDR, "<$mailroot/address.tsv" or return;
|
871
|
|
|
|
|
|
|
while()
|
872
|
|
|
|
|
|
|
{
|
873
|
|
|
|
|
|
|
chomp;
|
874
|
|
|
|
|
|
|
@addr{@addr_field}= split /\t/;
|
875
|
|
|
|
|
|
|
if($addr{notes})
|
876
|
|
|
|
|
|
|
{
|
877
|
|
|
|
|
|
|
$addr{notes}=~ s/\\t/\t/g;
|
878
|
|
|
|
|
|
|
$addr{notes}=~ s/\\n/\n/g;
|
879
|
|
|
|
|
|
|
$addr{notes}=~ s/\\\\/\\/g;
|
880
|
|
|
|
|
|
|
}
|
881
|
|
|
|
|
|
|
push @match, { %addr };
|
882
|
|
|
|
|
|
|
}
|
883
|
|
|
|
|
|
|
close ADDR;
|
884
|
|
|
|
|
|
|
return unless @match;
|
885
|
|
|
|
|
|
|
return sort { $$a{$$a{MATCHED}} cmp $$b{$$b{MATCHED}} } @match;
|
886
|
|
|
|
|
|
|
}
|
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
sub whosearch
|
889
|
|
|
|
|
|
|
{ # more comprehensive: find entire records
|
890
|
|
|
|
|
|
|
local $_;
|
891
|
|
|
|
|
|
|
my $query= shift;
|
892
|
|
|
|
|
|
|
my @field= (@_ or qw);
|
893
|
|
|
|
|
|
|
my(%addr,@match);
|
894
|
|
|
|
|
|
|
open ADDR, "<$mailroot/address.tsv" or return;
|
895
|
|
|
|
|
|
|
while()
|
896
|
|
|
|
|
|
|
{
|
897
|
|
|
|
|
|
|
chomp;
|
898
|
|
|
|
|
|
|
@addr{@addr_field}= split /\t/;
|
899
|
|
|
|
|
|
|
if($addr{notes})
|
900
|
|
|
|
|
|
|
{
|
901
|
|
|
|
|
|
|
$addr{notes}=~ s/\\t/\t/g;
|
902
|
|
|
|
|
|
|
$addr{notes}=~ s/\\n/\n/g;
|
903
|
|
|
|
|
|
|
$addr{notes}=~ s/\\\\/\\/g;
|
904
|
|
|
|
|
|
|
}
|
905
|
|
|
|
|
|
|
if("@addr{@field}"=~ /$query/)
|
906
|
|
|
|
|
|
|
{ push @match, { %addr, MATCHED => $field[0] }; }
|
907
|
|
|
|
|
|
|
elsif($addr{nickname}=~ /$query/)
|
908
|
|
|
|
|
|
|
{ push @match, { %addr, MATCHED => 'nickname' }; }
|
909
|
|
|
|
|
|
|
}
|
910
|
|
|
|
|
|
|
close ADDR;
|
911
|
|
|
|
|
|
|
return unless @match;
|
912
|
|
|
|
|
|
|
@match= sort { $$a{$$a{MATCHED}} cmp $$b{$$b{MATCHED}} } @match;
|
913
|
|
|
|
|
|
|
return( wantarray ? @match : ${$match[0]}{key} );
|
914
|
|
|
|
|
|
|
}
|
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
sub addrsearch
|
917
|
|
|
|
|
|
|
{ # less ambitious: just find addresses
|
918
|
|
|
|
|
|
|
local $_;
|
919
|
|
|
|
|
|
|
my %param= @_;
|
920
|
|
|
|
|
|
|
my $query= $param{-starts};
|
921
|
|
|
|
|
|
|
my $number= $param{-number};
|
922
|
|
|
|
|
|
|
my @field= ( $param{-fields} ? @{$param{-fields}} : qw );
|
923
|
|
|
|
|
|
|
my(%addr,@match);
|
924
|
|
|
|
|
|
|
open ADDR, "<$mailroot/address.tsv" or return;
|
925
|
|
|
|
|
|
|
while()
|
926
|
|
|
|
|
|
|
{
|
927
|
|
|
|
|
|
|
chomp;
|
928
|
|
|
|
|
|
|
@addr{@addr_field}= split /\t/;
|
929
|
|
|
|
|
|
|
if($addr{notes})
|
930
|
|
|
|
|
|
|
{
|
931
|
|
|
|
|
|
|
$addr{notes}=~ s/\\t/\t/g;
|
932
|
|
|
|
|
|
|
$addr{notes}=~ s/\\n/\n/g;
|
933
|
|
|
|
|
|
|
$addr{notes}=~ s/\\\\/\\/g;
|
934
|
|
|
|
|
|
|
}
|
935
|
|
|
|
|
|
|
if("@addr{@field}"=~ /^$query/i)
|
936
|
|
|
|
|
|
|
{ push @match, "$addr{firstname} $addr{lastname} <$addr{email}>"; }
|
937
|
|
|
|
|
|
|
elsif($addr{nickname}=~ /^$query/i)
|
938
|
|
|
|
|
|
|
{ push @match,
|
939
|
|
|
|
|
|
|
"$addr{nickname}\t$addr{firstname} $addr{lastname} <$addr{email}>"; }
|
940
|
|
|
|
|
|
|
}
|
941
|
|
|
|
|
|
|
close ADDR;
|
942
|
|
|
|
|
|
|
@match= ( @match ? ( sort { lc$a cmp lc$b } @match ) : &ldapsearch($query) );
|
943
|
|
|
|
|
|
|
return unless @match;
|
944
|
|
|
|
|
|
|
return( wantarray ? @match : $match[$number] );
|
945
|
|
|
|
|
|
|
}
|
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
sub ldapsearch
|
948
|
|
|
|
|
|
|
{ # EXTREMELY simple LDAP search
|
949
|
|
|
|
|
|
|
my @found;
|
950
|
|
|
|
|
|
|
my $query= shift;
|
951
|
|
|
|
|
|
|
return unless length($query) > 2;
|
952
|
|
|
|
|
|
|
my $filter;
|
953
|
|
|
|
|
|
|
if($query=~ /\s/)
|
954
|
|
|
|
|
|
|
{
|
955
|
|
|
|
|
|
|
my($first,$last)= split /\s+/, $query, 2;
|
956
|
|
|
|
|
|
|
$filter= "(&(cn=$first*)(sn=$last*))";
|
957
|
|
|
|
|
|
|
}
|
958
|
|
|
|
|
|
|
else
|
959
|
|
|
|
|
|
|
{ $filter= "(cn=$query*)"; }
|
960
|
|
|
|
|
|
|
for my $server (split /,?\s+|,/, &ldaps())
|
961
|
|
|
|
|
|
|
{
|
962
|
|
|
|
|
|
|
my $ldap= new Net::LDAP($server, timeout => 3 )
|
963
|
|
|
|
|
|
|
or die "Unable to use LDAP: $! $@\n";
|
964
|
|
|
|
|
|
|
$ldap->bind; # anonymous logon
|
965
|
|
|
|
|
|
|
my $result= $ldap->search ( filter => $filter, timelimit => 3 );
|
966
|
|
|
|
|
|
|
carp("LDAP error. ".$result->error()), next if $result->code();
|
967
|
|
|
|
|
|
|
push @found, map {$_->get('cn')->[0].' <'.$_->get('mail')->[0].'>'}
|
968
|
|
|
|
|
|
|
$result->all_entries;
|
969
|
|
|
|
|
|
|
$ldap->unbind; # take down session
|
970
|
|
|
|
|
|
|
}
|
971
|
|
|
|
|
|
|
return sort { lc$a cmp lc$b } @found;
|
972
|
|
|
|
|
|
|
}
|
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
sub simplifymsg
|
975
|
|
|
|
|
|
|
{
|
976
|
|
|
|
|
|
|
return unless my $msg= shift;
|
977
|
|
|
|
|
|
|
chomp(my $mtype= lc $msg->get('Content-Type'));
|
978
|
|
|
|
|
|
|
if($mtype=~ m<^(text/plain|message/rfc822)\b> or not $mtype)
|
979
|
|
|
|
|
|
|
{ # message body
|
980
|
|
|
|
|
|
|
return join('',@{$msg->body})."\n";
|
981
|
|
|
|
|
|
|
}
|
982
|
|
|
|
|
|
|
elsif($mtype=~ m<^multipart/alternative\b>)
|
983
|
|
|
|
|
|
|
{ # attachments
|
984
|
|
|
|
|
|
|
my $body;
|
985
|
|
|
|
|
|
|
my $Brown= new MIME::Parser( output_dir => ( $ENV{TEMP} or $ENV{TMP} ) );
|
986
|
|
|
|
|
|
|
my $mime= $Brown->parse_data([@{$msg->header}, "\n", @{$msg->body}]);
|
987
|
|
|
|
|
|
|
for my $mimeitem ($mime->parts)
|
988
|
|
|
|
|
|
|
{ # look for the simplest alternative
|
989
|
|
|
|
|
|
|
return "\n\n".$mimeitem->stringify_body()."\n\n"
|
990
|
|
|
|
|
|
|
if($mimeitem->head->get('Content-Type')=~ mi);
|
991
|
|
|
|
|
|
|
}
|
992
|
|
|
|
|
|
|
return "\n\n".$mime->parts(0)->stringify_body()."\n\n";
|
993
|
|
|
|
|
|
|
}
|
994
|
|
|
|
|
|
|
else
|
995
|
|
|
|
|
|
|
{ # alternative types
|
996
|
|
|
|
|
|
|
my $Brown= new MIME::Parser( output_dir => ( $ENV{TEMP} or $ENV{TMP} ) );
|
997
|
|
|
|
|
|
|
my $mime= $Brown->parse_data([ split /^/m, $msg->as_string ]);
|
998
|
|
|
|
|
|
|
my $body;
|
999
|
|
|
|
|
|
|
for my $mimeitem ($mime->parts)
|
1000
|
|
|
|
|
|
|
{
|
1001
|
|
|
|
|
|
|
if(my $filename= $mimeitem->head->recommended_filename)
|
1002
|
|
|
|
|
|
|
{
|
1003
|
|
|
|
|
|
|
$body.= '['.$mimeitem->head->recommended_filename.'] ';
|
1004
|
|
|
|
|
|
|
}
|
1005
|
|
|
|
|
|
|
else #if($msg->get('Content-Type')=~ m<^(text/plain|message/rfc822)\b>)
|
1006
|
|
|
|
|
|
|
{
|
1007
|
|
|
|
|
|
|
$body.= $mimeitem->stringify_body;
|
1008
|
|
|
|
|
|
|
}
|
1009
|
|
|
|
|
|
|
}
|
1010
|
|
|
|
|
|
|
return $body;
|
1011
|
|
|
|
|
|
|
}
|
1012
|
|
|
|
|
|
|
}
|
1013
|
|
|
|
|
|
|
|
1014
|
|
|
|
|
|
|
sub _folder_sort
|
1015
|
|
|
|
|
|
|
{
|
1016
|
|
|
|
|
|
|
$_folder_sort{$a} ?
|
1017
|
|
|
|
|
|
|
( $_folder_sort{$b} ?
|
1018
|
|
|
|
|
|
|
( $_folder_sort{$a} <=> $_folder_sort{$b} ) : -1 ) :
|
1019
|
|
|
|
|
|
|
( $_folder_sort{$b} ?
|
1020
|
|
|
|
|
|
|
1 : ( $a cmp $b ) );
|
1021
|
|
|
|
|
|
|
}
|
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
###################################
|
1025
|
|
|
|
|
|
|
#
|
1026
|
|
|
|
|
|
|
# Hash Tie Handlers
|
1027
|
|
|
|
|
|
|
#
|
1028
|
|
|
|
|
|
|
|
1029
|
|
|
|
|
|
|
sub TIEHASH { bless {}, $_[0] }
|
1030
|
|
|
|
|
|
|
sub CLEAR { %{$_[0]} = () }
|
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
sub STORE
|
1033
|
|
|
|
|
|
|
{
|
1034
|
|
|
|
|
|
|
my($this,$key,$val)= @_;
|
1035
|
|
|
|
|
|
|
my($folder,$msgid)= msgpath $key;
|
1036
|
|
|
|
|
|
|
if($msgid)
|
1037
|
|
|
|
|
|
|
{ # modify message flag(s)
|
1038
|
|
|
|
|
|
|
$msgid= msgid($folder,$msgid);
|
1039
|
|
|
|
|
|
|
local $_= $msgid;
|
1040
|
|
|
|
|
|
|
s/(\(.....\))/flags($val,$1)/e;
|
1041
|
|
|
|
|
|
|
rename "$mailroot/$folder/$msgid", "$mailroot/$folder/$_";
|
1042
|
|
|
|
|
|
|
return "$folder/$_";
|
1043
|
|
|
|
|
|
|
}
|
1044
|
|
|
|
|
|
|
elsif($folder eq '/')
|
1045
|
|
|
|
|
|
|
{ # use filter() to sort message
|
1046
|
|
|
|
|
|
|
my @msg= ( isa($val,'ARRAY') ? @$val : ($val) );
|
1047
|
|
|
|
|
|
|
for my $msg (@msg)
|
1048
|
|
|
|
|
|
|
{
|
1049
|
|
|
|
|
|
|
#print "[STORE:/] Got:\n"; $msg->print; # DEBUG
|
1050
|
|
|
|
|
|
|
STORE($this,(Mail::MsgStore::Event::filter($msg) or 'Inbox'),$msg);
|
1051
|
|
|
|
|
|
|
}
|
1052
|
|
|
|
|
|
|
return scalar @msg;
|
1053
|
|
|
|
|
|
|
}
|
1054
|
|
|
|
|
|
|
elsif($folder)
|
1055
|
|
|
|
|
|
|
{ # add message(s) to folder
|
1056
|
|
|
|
|
|
|
$folder= "$mailroot/$folder";
|
1057
|
|
|
|
|
|
|
# create folder unless exists
|
1058
|
|
|
|
|
|
|
mkpath $folder unless -d $folder;
|
1059
|
|
|
|
|
|
|
croak "Unable to create folder $folder: $!\n" unless -d $folder;
|
1060
|
|
|
|
|
|
|
my @msg= ( isa($val,'ARRAY') ? @$val : ($val) );
|
1061
|
|
|
|
|
|
|
for my $msg (@msg)
|
1062
|
|
|
|
|
|
|
{ # add message to folder
|
1063
|
|
|
|
|
|
|
next unless isa($msg,'Mail::Internet');
|
1064
|
|
|
|
|
|
|
# build msgid: mail.000238C42D34.69FD09C3.00003082.001A.(FLAGS).mail
|
1065
|
|
|
|
|
|
|
$msgid= 'mail.'.&uniqueid;
|
1066
|
|
|
|
|
|
|
local $_= "$folder/$msgid";
|
1067
|
|
|
|
|
|
|
open MESSAGE, ">$_" or croak "Unable to create $_: $!";
|
1068
|
|
|
|
|
|
|
{ local $_; $msg->print(\*MESSAGE); } # MIME::Entity isn't friendly to $_
|
1069
|
|
|
|
|
|
|
close MESSAGE;
|
1070
|
|
|
|
|
|
|
my $time= parsedate($msg->get('Date'));
|
1071
|
|
|
|
|
|
|
utime $time, $time, $_;
|
1072
|
|
|
|
|
|
|
# message fully saved, complete the msgid (filename)
|
1073
|
|
|
|
|
|
|
$msg->head->combine('X-Msg-Flags');
|
1074
|
|
|
|
|
|
|
chomp(my $inflags= $msg->get('X-Msg-Flags'));
|
1075
|
|
|
|
|
|
|
rename $_, $_.flags($inflags).'.mail';
|
1076
|
|
|
|
|
|
|
}
|
1077
|
|
|
|
|
|
|
return scalar @msg;
|
1078
|
|
|
|
|
|
|
}
|
1079
|
|
|
|
|
|
|
else
|
1080
|
|
|
|
|
|
|
{ # save an instance value
|
1081
|
|
|
|
|
|
|
return $$this{$key}= $val;
|
1082
|
|
|
|
|
|
|
}
|
1083
|
|
|
|
|
|
|
return;
|
1084
|
|
|
|
|
|
|
}
|
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
sub EXISTS
|
1087
|
|
|
|
|
|
|
{
|
1088
|
|
|
|
|
|
|
my($this,$key)= @_;
|
1089
|
|
|
|
|
|
|
my($folder,$msgid)= msgpath $key;
|
1090
|
|
|
|
|
|
|
if($msgid)
|
1091
|
|
|
|
|
|
|
{ # message
|
1092
|
|
|
|
|
|
|
return "$folder/$msgid" if -f "$mailroot/$folder/$msgid";
|
1093
|
|
|
|
|
|
|
return $folder.'/'.msgid($folder,$msgid); # maybe different flags
|
1094
|
|
|
|
|
|
|
}
|
1095
|
|
|
|
|
|
|
elsif($folder)
|
1096
|
|
|
|
|
|
|
{ # folder
|
1097
|
|
|
|
|
|
|
if(opendir FOLDER, "$mailroot/$folder")
|
1098
|
|
|
|
|
|
|
{ # check to see if the folder is empty
|
1099
|
|
|
|
|
|
|
while($_= readdir FOLDER)
|
1100
|
|
|
|
|
|
|
{
|
1101
|
|
|
|
|
|
|
next unless /^mail\..*\.mail$/;
|
1102
|
|
|
|
|
|
|
close FOLDER;
|
1103
|
|
|
|
|
|
|
return 1;
|
1104
|
|
|
|
|
|
|
}
|
1105
|
|
|
|
|
|
|
close FOLDER;
|
1106
|
|
|
|
|
|
|
}
|
1107
|
|
|
|
|
|
|
return 0;
|
1108
|
|
|
|
|
|
|
}
|
1109
|
|
|
|
|
|
|
else
|
1110
|
|
|
|
|
|
|
{
|
1111
|
|
|
|
|
|
|
return exists $$this{$key};
|
1112
|
|
|
|
|
|
|
}
|
1113
|
|
|
|
|
|
|
return 0;
|
1114
|
|
|
|
|
|
|
}
|
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
sub FETCH
|
1117
|
|
|
|
|
|
|
{
|
1118
|
|
|
|
|
|
|
my($this,$key)= @_;
|
1119
|
|
|
|
|
|
|
my($folder,$msgid)= msgpath $key;
|
1120
|
|
|
|
|
|
|
if($msgid)
|
1121
|
|
|
|
|
|
|
{ # message
|
1122
|
|
|
|
|
|
|
$msgid= msgid($folder,$msgid);
|
1123
|
|
|
|
|
|
|
return unless open MESSAGE, "<$mailroot/$folder/$msgid";
|
1124
|
|
|
|
|
|
|
my $msg= new Mail::Internet(\*MESSAGE);
|
1125
|
|
|
|
|
|
|
close MESSAGE;
|
1126
|
|
|
|
|
|
|
{ local $_; # head->replace unfriendly to $_
|
1127
|
|
|
|
|
|
|
# save current flags internally (will be used if re-saved)
|
1128
|
|
|
|
|
|
|
$msgid=~ m<(\(.....\))>;
|
1129
|
|
|
|
|
|
|
my $curflags= $1;
|
1130
|
|
|
|
|
|
|
$msg->head->replace('X-Msg-Flags',$curflags);
|
1131
|
|
|
|
|
|
|
}
|
1132
|
|
|
|
|
|
|
return $msg;
|
1133
|
|
|
|
|
|
|
}
|
1134
|
|
|
|
|
|
|
elsif($folder eq '/')
|
1135
|
|
|
|
|
|
|
{ # convenience root: get new, flagged messages
|
1136
|
|
|
|
|
|
|
my @new;
|
1137
|
|
|
|
|
|
|
my $wanted= sub
|
1138
|
|
|
|
|
|
|
{
|
1139
|
|
|
|
|
|
|
return unless /\(--..-\)/i;
|
1140
|
|
|
|
|
|
|
(my $folder= $File::Find::dir.'/')=~ s<^$mailroot/><>;
|
1141
|
|
|
|
|
|
|
push @new, $folder.$_;
|
1142
|
|
|
|
|
|
|
};
|
1143
|
|
|
|
|
|
|
finddepth( $wanted, $mailroot );
|
1144
|
|
|
|
|
|
|
return \@new;
|
1145
|
|
|
|
|
|
|
}
|
1146
|
|
|
|
|
|
|
elsif($folder)
|
1147
|
|
|
|
|
|
|
{ # folder
|
1148
|
|
|
|
|
|
|
my @msgid;
|
1149
|
|
|
|
|
|
|
if(opendir FOLDER, "$mailroot/$folder")
|
1150
|
|
|
|
|
|
|
{
|
1151
|
|
|
|
|
|
|
@msgid= sort { (stat "$mailroot/$folder/$b")[9] <=>
|
1152
|
|
|
|
|
|
|
(stat "$mailroot/$folder/$a")[9] }
|
1153
|
|
|
|
|
|
|
grep /^mail\..*\.mail$/, readdir FOLDER;
|
1154
|
|
|
|
|
|
|
close FOLDER;
|
1155
|
|
|
|
|
|
|
}
|
1156
|
|
|
|
|
|
|
return \@msgid;
|
1157
|
|
|
|
|
|
|
}
|
1158
|
|
|
|
|
|
|
else
|
1159
|
|
|
|
|
|
|
{
|
1160
|
|
|
|
|
|
|
return $$this{$key};
|
1161
|
|
|
|
|
|
|
}
|
1162
|
|
|
|
|
|
|
return;
|
1163
|
|
|
|
|
|
|
}
|
1164
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
sub DELETE
|
1166
|
|
|
|
|
|
|
{
|
1167
|
|
|
|
|
|
|
my($this,$key)= @_;
|
1168
|
|
|
|
|
|
|
my($folder,$msgid)= msgpath $key;
|
1169
|
|
|
|
|
|
|
return if $folder eq '/';
|
1170
|
|
|
|
|
|
|
if($msgid)
|
1171
|
|
|
|
|
|
|
{ # Trash, delete & return message
|
1172
|
|
|
|
|
|
|
$msgid= msgid($folder,$msgid);
|
1173
|
|
|
|
|
|
|
my $msg;
|
1174
|
|
|
|
|
|
|
return
|
1175
|
|
|
|
|
|
|
unless open MSG, "<$mailroot/$folder/$msgid"
|
1176
|
|
|
|
|
|
|
and $msg= new Mail::Internet(\*MSG);
|
1177
|
|
|
|
|
|
|
close MSG;
|
1178
|
|
|
|
|
|
|
{ local $_; # head->replace unfriendly to $_
|
1179
|
|
|
|
|
|
|
# save current flags internally (will be used if re-saved)
|
1180
|
|
|
|
|
|
|
$msgid=~ m<(\(.....\))>;
|
1181
|
|
|
|
|
|
|
my $curflags= $1;
|
1182
|
|
|
|
|
|
|
$msg->head->replace('X-Msg-Flags',$curflags);
|
1183
|
|
|
|
|
|
|
}
|
1184
|
|
|
|
|
|
|
return $msg if unlink "$mailroot/$folder/$msgid";
|
1185
|
|
|
|
|
|
|
}
|
1186
|
|
|
|
|
|
|
elsif($folder)
|
1187
|
|
|
|
|
|
|
{ # folder
|
1188
|
|
|
|
|
|
|
my @msg;
|
1189
|
|
|
|
|
|
|
my $wanted= sub
|
1190
|
|
|
|
|
|
|
{
|
1191
|
|
|
|
|
|
|
return unless /^mail\..*\.mail$/;
|
1192
|
|
|
|
|
|
|
(my $folder= $File::Find::dir.'/')=~ s<^$mailroot/><>;
|
1193
|
|
|
|
|
|
|
my $msg= $MsgStore{$folder.$_};
|
1194
|
|
|
|
|
|
|
push @msg, $msg;
|
1195
|
|
|
|
|
|
|
};
|
1196
|
|
|
|
|
|
|
finddepth( $wanted, "$mailroot/$folder" );
|
1197
|
|
|
|
|
|
|
rmtree "$mailroot/$folder";
|
1198
|
|
|
|
|
|
|
return \@msg;
|
1199
|
|
|
|
|
|
|
}
|
1200
|
|
|
|
|
|
|
else
|
1201
|
|
|
|
|
|
|
{
|
1202
|
|
|
|
|
|
|
return delete $$this{$key};
|
1203
|
|
|
|
|
|
|
}
|
1204
|
|
|
|
|
|
|
return;
|
1205
|
|
|
|
|
|
|
}
|
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
sub FIRSTKEY
|
1208
|
|
|
|
|
|
|
{
|
1209
|
|
|
|
|
|
|
undef @folder;
|
1210
|
|
|
|
|
|
|
my $wanted= sub
|
1211
|
|
|
|
|
|
|
{
|
1212
|
|
|
|
|
|
|
(my $folder= $File::Find::dir.'/')=~ s,^$mailroot/,,;
|
1213
|
|
|
|
|
|
|
push @folder, $folder.$_ if -d and $_ ne '.';
|
1214
|
|
|
|
|
|
|
};
|
1215
|
|
|
|
|
|
|
finddepth( $wanted, $mailroot );
|
1216
|
|
|
|
|
|
|
@folder= sort {&_folder_sort} @folder;
|
1217
|
|
|
|
|
|
|
return shift @folder;
|
1218
|
|
|
|
|
|
|
}
|
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
sub NEXTKEY
|
1221
|
|
|
|
|
|
|
{
|
1222
|
|
|
|
|
|
|
return shift @folder;
|
1223
|
|
|
|
|
|
|
}
|
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
|
1226
|
|
|
|
|
|
|
%_folder_sort=
|
1227
|
|
|
|
|
|
|
(
|
1228
|
|
|
|
|
|
|
Inbox => 1,
|
1229
|
|
|
|
|
|
|
Outbox => 2,
|
1230
|
|
|
|
|
|
|
Draft => 4,
|
1231
|
|
|
|
|
|
|
Sent => 3,
|
1232
|
|
|
|
|
|
|
Trash => 5,
|
1233
|
|
|
|
|
|
|
);
|
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
%mime_ext=
|
1236
|
|
|
|
|
|
|
(
|
1237
|
|
|
|
|
|
|
aif => 'audio/x-aiff',
|
1238
|
|
|
|
|
|
|
aifc => 'audio/x-aiff',
|
1239
|
|
|
|
|
|
|
aiff => 'audio/x-aiff',
|
1240
|
|
|
|
|
|
|
asc => 'text/plain',
|
1241
|
|
|
|
|
|
|
asp => 'application/x-asp',
|
1242
|
|
|
|
|
|
|
au => 'audio/ulaw',
|
1243
|
|
|
|
|
|
|
avi => 'video/x-msvideo',
|
1244
|
|
|
|
|
|
|
bat => 'application/x-batchfile',
|
1245
|
|
|
|
|
|
|
bin => 'application/octet-stream',
|
1246
|
|
|
|
|
|
|
bmp => 'image/bitmap',
|
1247
|
|
|
|
|
|
|
cgi => 'application/x-perl',
|
1248
|
|
|
|
|
|
|
cmd => 'application/x-nt-command-script',
|
1249
|
|
|
|
|
|
|
eps => 'application/postscript',
|
1250
|
|
|
|
|
|
|
exe => 'application/octet-stream',
|
1251
|
|
|
|
|
|
|
gif => 'image/gif',
|
1252
|
|
|
|
|
|
|
gtar => 'application/x-gtar',
|
1253
|
|
|
|
|
|
|
gz => 'application/x-gunzip',
|
1254
|
|
|
|
|
|
|
htm => 'text/html',
|
1255
|
|
|
|
|
|
|
html => 'text/html',
|
1256
|
|
|
|
|
|
|
ief => 'image/ief',
|
1257
|
|
|
|
|
|
|
jpe => 'image/jpeg',
|
1258
|
|
|
|
|
|
|
jpeg => 'image/jpeg',
|
1259
|
|
|
|
|
|
|
jpg => 'image/jpeg',
|
1260
|
|
|
|
|
|
|
latex => 'application/x-latex',
|
1261
|
|
|
|
|
|
|
mid => 'audio/midi',
|
1262
|
|
|
|
|
|
|
midi => 'audio/midi',
|
1263
|
|
|
|
|
|
|
mov => 'video/quicktime',
|
1264
|
|
|
|
|
|
|
movie => 'video/x-sgi-movie',
|
1265
|
|
|
|
|
|
|
mp2 => 'video/mpeg',
|
1266
|
|
|
|
|
|
|
mp3 => 'audio/mpeg-layer3',
|
1267
|
|
|
|
|
|
|
mpe => 'video/mpeg',
|
1268
|
|
|
|
|
|
|
mpeg => 'video/mpeg',
|
1269
|
|
|
|
|
|
|
mpg => 'video/mpeg',
|
1270
|
|
|
|
|
|
|
pbm => 'image/x-portable-bitmap',
|
1271
|
|
|
|
|
|
|
pdf => 'application/pdf',
|
1272
|
|
|
|
|
|
|
pgm => 'image/x-portable-graymap',
|
1273
|
|
|
|
|
|
|
pgp => 'application/pgp',
|
1274
|
|
|
|
|
|
|
pl => 'application/x-perl',
|
1275
|
|
|
|
|
|
|
pm => 'application/x-perl',
|
1276
|
|
|
|
|
|
|
png => 'image/png',
|
1277
|
|
|
|
|
|
|
pnm => 'image/x-portable-anymap',
|
1278
|
|
|
|
|
|
|
ps => 'application/postscript',
|
1279
|
|
|
|
|
|
|
qt => 'video/quicktime',
|
1280
|
|
|
|
|
|
|
ra => 'audio/x-pn-realaudio',
|
1281
|
|
|
|
|
|
|
ram => 'audio/x-pn-realaudio',
|
1282
|
|
|
|
|
|
|
ras => 'image/x-cmu-raster',
|
1283
|
|
|
|
|
|
|
rgb => 'image/x-rgb',
|
1284
|
|
|
|
|
|
|
rm => 'audio/x-pn-realaudio',
|
1285
|
|
|
|
|
|
|
rmi => 'audio/midi',
|
1286
|
|
|
|
|
|
|
rtf => 'text/richtext',
|
1287
|
|
|
|
|
|
|
rtx => 'text/richtext',
|
1288
|
|
|
|
|
|
|
shtml => 'text/html',
|
1289
|
|
|
|
|
|
|
snd => 'audio/basic',
|
1290
|
|
|
|
|
|
|
stm => 'text/html',
|
1291
|
|
|
|
|
|
|
tar => 'application/x-tar',
|
1292
|
|
|
|
|
|
|
tif => 'image/tiff',
|
1293
|
|
|
|
|
|
|
tiff => 'image/tiff',
|
1294
|
|
|
|
|
|
|
tsv => 'text/tab-separated-values',
|
1295
|
|
|
|
|
|
|
txt => 'text/plain',
|
1296
|
|
|
|
|
|
|
wav => 'audio/x-wav',
|
1297
|
|
|
|
|
|
|
xbm => 'image/x-bitmap',
|
1298
|
|
|
|
|
|
|
xpm => 'image/x-pixmap',
|
1299
|
|
|
|
|
|
|
zip => 'application/zip',
|
1300
|
|
|
|
|
|
|
);
|
1301
|
|
|
|
|
|
|
|
1302
|
|
|
|
|
|
|
$_default_script= <<'SCRIPT_END';
|
1303
|
|
|
|
|
|
|
##############################################################
|
1304
|
|
|
|
|
|
|
#
|
1305
|
|
|
|
|
|
|
# events.pl - customized mail filtering and more
|
1306
|
|
|
|
|
|
|
#
|
1307
|
|
|
|
|
|
|
|
1308
|
|
|
|
|
|
|
##############################################################
|
1309
|
|
|
|
|
|
|
#
|
1310
|
|
|
|
|
|
|
# filter()
|
1311
|
|
|
|
|
|
|
#
|
1312
|
|
|
|
|
|
|
# parameter: Mail::Internet object
|
1313
|
|
|
|
|
|
|
# returns: name of folder to store message in
|
1314
|
|
|
|
|
|
|
# (undef implies 'Inbox')
|
1315
|
|
|
|
|
|
|
#
|
1316
|
|
|
|
|
|
|
# Message flags can be stored in the 'X-Msg-Flags' message
|
1317
|
|
|
|
|
|
|
# header, and can be either native '(FLAGS)' format, or
|
1318
|
|
|
|
|
|
|
# the more readable english 'list, flag, answered' format.
|
1319
|
|
|
|
|
|
|
#
|
1320
|
|
|
|
|
|
|
# Flag English
|
1321
|
|
|
|
|
|
|
# F flame
|
1322
|
|
|
|
|
|
|
# L list/group
|
1323
|
|
|
|
|
|
|
# A answered/replied
|
1324
|
|
|
|
|
|
|
# G green/general/flag (general purpose flag)
|
1325
|
|
|
|
|
|
|
# S seen/read/opened
|
1326
|
|
|
|
|
|
|
#
|
1327
|
|
|
|
|
|
|
sub filter($)
|
1328
|
|
|
|
|
|
|
{
|
1329
|
|
|
|
|
|
|
}
|
1330
|
|
|
|
|
|
|
|
1331
|
|
|
|
|
|
|
##############################################################
|
1332
|
|
|
|
|
|
|
#
|
1333
|
|
|
|
|
|
|
# keep()
|
1334
|
|
|
|
|
|
|
#
|
1335
|
|
|
|
|
|
|
# parameter: Mail::Internet object
|
1336
|
|
|
|
|
|
|
# returns: boolean - keep message on server?
|
1337
|
|
|
|
|
|
|
#
|
1338
|
|
|
|
|
|
|
# The source account is stored in the 'X-Recipient-Account'
|
1339
|
|
|
|
|
|
|
# message header.
|
1340
|
|
|
|
|
|
|
#
|
1341
|
|
|
|
|
|
|
sub keep($)
|
1342
|
|
|
|
|
|
|
{
|
1343
|
|
|
|
|
|
|
return; # delete by default (no return value = false)
|
1344
|
|
|
|
|
|
|
}
|
1345
|
|
|
|
|
|
|
|
1346
|
|
|
|
|
|
|
##############################################################
|
1347
|
|
|
|
|
|
|
#
|
1348
|
|
|
|
|
|
|
# sign()
|
1349
|
|
|
|
|
|
|
#
|
1350
|
|
|
|
|
|
|
# parameter: Mail::Internet object
|
1351
|
|
|
|
|
|
|
# returns: the modified Mail::Internet object
|
1352
|
|
|
|
|
|
|
#
|
1353
|
|
|
|
|
|
|
# Add a signature to a message.
|
1354
|
|
|
|
|
|
|
# $msg->sign( Signature => 'Your Signature Message' );
|
1355
|
|
|
|
|
|
|
#
|
1356
|
|
|
|
|
|
|
sub sign($)
|
1357
|
|
|
|
|
|
|
{
|
1358
|
|
|
|
|
|
|
my $msg= shift;
|
1359
|
|
|
|
|
|
|
$msg->sign( Signature => 'Your Signature Message' );
|
1360
|
|
|
|
|
|
|
return $msg;
|
1361
|
|
|
|
|
|
|
}
|
1362
|
|
|
|
|
|
|
|
1363
|
|
|
|
|
|
|
local $_;1
|
1364
|
|
|
|
|
|
|
SCRIPT_END
|
1365
|
|
|
|
|
|
|
|
1366
|
|
|
|
|
|
|
|
1367
|
|
|
|
|
|
|
###################################
|
1368
|
|
|
|
|
|
|
#
|
1369
|
|
|
|
|
|
|
# Initialization
|
1370
|
|
|
|
|
|
|
#
|
1371
|
|
|
|
|
|
|
|
1372
|
|
|
|
|
|
|
tie %MsgStore, __PACKAGE__;
|
1373
|
|
|
|
|
|
|
if($ENV{MAILROOT})
|
1374
|
|
|
|
|
|
|
{ mailroot($ENV{MAILROOT}); }
|
1375
|
|
|
|
|
|
|
else
|
1376
|
|
|
|
|
|
|
{
|
1377
|
|
|
|
|
|
|
$mailroot= '.';
|
1378
|
|
|
|
|
|
|
{ package Mail::MsgStore::Event;
|
1379
|
|
|
|
|
|
|
sub filter($) { }
|
1380
|
|
|
|
|
|
|
sub keep($) { 1 }
|
1381
|
|
|
|
|
|
|
sub sign($) { }
|
1382
|
|
|
|
|
|
|
}
|
1383
|
|
|
|
|
|
|
}
|
1384
|
|
|
|
|
|
|
|
1385
|
|
|
|
|
|
|
1 |