/projects/james-2.2.0/src/java/org/apache/james/fetchmail/MessageProcessor.java
Java | 1494 lines | 807 code | 148 blank | 539 comment | 105 complexity | dc53f165ce25e96fd530c7fe881a4409 MD5 | raw file
1/***********************************************************************
2 * Copyright (c) 2003-2004 The Apache Software Foundation. *
3 * All rights reserved. *
4 * ------------------------------------------------------------------- *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you *
6 * may not use this file except in compliance with the License. You *
7 * may obtain a copy of the License at: *
8 * *
9 * http://www.apache.org/licenses/LICENSE-2.0 *
10 * *
11 * Unless required by applicable law or agreed to in writing, software *
12 * distributed under the License is distributed on an "AS IS" BASIS, *
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
14 * implied. See the License for the specific language governing *
15 * permissions and limitations under the License. *
16 ***********************************************************************/
17
18package org.apache.james.fetchmail;
19
20import java.io.InputStream;
21import java.net.InetAddress;
22import java.net.UnknownHostException;
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.Enumeration;
26import java.util.Iterator;
27import java.util.StringTokenizer;
28
29import javax.mail.Address;
30import javax.mail.Flags;
31import javax.mail.Folder;
32import javax.mail.MessagingException;
33import javax.mail.Session;
34import javax.mail.internet.InternetAddress;
35import javax.mail.internet.InternetHeaders;
36import javax.mail.internet.MimeMessage;
37import javax.mail.internet.ParseException;
38
39import org.apache.james.core.MailImpl;
40import org.apache.james.util.RFC2822Headers;
41import org.apache.mailet.Mail;
42import org.apache.mailet.MailAddress;
43
44/**
45 * <p>Class <code>MessageProcessor</code> handles the delivery of
46 * <code>MimeMessages</code> to the James input spool.</p>
47 *
48 * <p>Messages written to the input spool always have the following Mail
49 * Attributes set:</p>
50 * <dl>
51 * <dt>org.apache.james.fetchmail.taskName (java.lang.String)</dt>
52 * <dd>The name of the fetch task that processed the message</dd>
53 * <dt>org.apache.james.fetchmail.folderName (java.lang.String)</dt>
54 * <dd>The name of the folder from which the message was fetched</dd>
55 * </dl>
56 *
57 * <p>Messages written to the input spool have the following Mail Attributes
58 * set if the corresponding condition is satisfied:
59 * <dl>
60 * <dt>org.apache.james.fetchmail.isBlacklistedRecipient</dt>
61 * <dd>The recipient is in the configured blacklist</dd>
62 * <dt>org.apache.james.fetchmail.isMaxMessageSizeExceeded (java.lang.String)</dt>
63 * <dd>The message size exceeds the configured limit. An empty message is
64 * written to the input spool. The Mail Attribute value is a String
65 * representing the size of the original message in bytes.</dd>
66 * <dt>org.apache.james.fetchmail.isRecipientNotFound</dt>
67 * <dd>The recipient could not be found. Delivery is to the configured recipient.
68 * See the discussion of delivery to a sole intended recipient below.</dd>
69 * <dt>org.apache.james.fetchmail.isRemoteRecievedHeaderInvalid</dt>
70 * <dd>The Receieved header at the index specified by parameter
71 * <code>remoteReceivedHeaderIndex</code> is invalid.</dd>
72 * <dt>org.apache.james.fetchmail.isRemoteRecipient</dt>
73 * <dd>The recipient is on a remote host</dd>
74 * <dt>org.apache.james.fetchmail.isUserUndefined</dt>
75 * <dd>The recipient is on a localhost but not defined to James</dd>
76 * </dl>
77 *
78 * <p>Configuration settings -
79 * see <code>org.apache.james.fetchmail.ParsedConfiguration</code>
80 * - control the messages that are written to the James input spool, those that
81 * are rejected and what happens to messages that are rejected.</p>
82 *
83 * <p>Rejection processing is based on the following filters:</p>
84 * <dl>
85 * <dt>RejectRemoteRecipient</dt>
86 * <dd>Rejects recipients on remote hosts</dd>
87 * <dt>RejectBlacklistedRecipient</dt>
88 * <dd>Rejects recipients configured in a blacklist</dd>
89 * <dt>RejectUserUndefined</dt>
90 * <dd>Rejects recipients on local hosts who are not defined as James users</dd>
91 * <dt>RejectRecipientNotFound</dt>
92 * <dd>See the discussion of delivery to a sole intended recipient below</dd>
93 * <dt>RejectMaxMessageSizeExceeded</dt>
94 * <dd>Rejects messages whose size exceeds the configured limit</dd>
95 * <dt>RejectRemoteReceievedHeaderInvalid</dt>
96 * <dd>Rejects messages whose Received header is invalid.</dd>
97 * </dl>
98 *
99 * <p>Rejection processing is intentionally limited to managing the status of the
100 * messages that are rejected on the server from which they were fetched. View
101 * it as a simple automation of the manual processing an end-user would perform
102 * through a mail client. Messages may be marked as seen or be deleted.</p>
103 *
104 * <p>Further processing can be achieved by configuring to disable rejection for
105 * one or more filters. This enables Messages that would have been rejected to
106 * be written to the James input spool. The conditional Mail Attributes
107 * described above identify the filter states. The Matcher/Mailet chain can
108 * then be used to perform any further processing required, such as notifying
109 * the Postmaster and/or sender, marking the message for error processing, etc.</p>
110 *
111 * <p>Note that in the case of a message exceeding the message size limit, the
112 * message that is written to the input spool has no content. This enables
113 * configuration of a mailet notifying the sender that their mail has not been
114 * delivered due to its size while maintaining the purpose of the filter which is
115 * to avoid injecting excessively large messages into the input spool.</p>
116 *
117 * <p>Delivery is to a sole intended recipient. The recipient is determined in the
118 * following manner:</p>
119 *
120 * <ol>
121 * <li>If isIgnoreIntendedRecipient(), use the configured recipient</li>
122 * <li>If the Envelope contains a for: stanza, use the recipient in the stanza</li>
123 * <li>If the Message has a sole intended recipient, use this recipient</li>
124 * <li>If not rejectRecipientNotFound(), use the configured recipient</li>
125 * </ol>
126 *
127 * <p>If a recipient cannot be determined after these steps, the message is
128 * rejected.</p>
129 *
130 * <p>Every delivered message CURRENTLY has an "X-fetched-from" header added
131 * containing the name of the fetch task. Its primary uses are to detect bouncing
132 * mail and provide backwards compatibility with the fetchPop task that inserted
133 * this header to enable injected messages to be detected in the Matcher/Mailet
134 * chain. This header is DEPRECATED and WILL BE REMOVED in a future version of
135 * fetchmail. Use the Mail Attribute <code>org.apache.james.fetchmail.taskName</code>
136 * instead.
137 *
138 * <p><code>MessageProcessor</code> is as agnostic as it can be about the format
139 * and contents of the messages it delivers. There are no RFCs that govern its
140 * behavior. The most releveant RFCs relate to the exchange of messages between
141 * MTA servers, but not POP3 or IMAP servers which are normally end-point
142 * servers and not expected to re-inject mail into MTAs. None the less, the
143 * intent is to conform to the 'spirit' of the RFCs.
144 * <code>MessageProcessor</code> relies on the MTA (James in this
145 * implementation) to manage and validate the injected mail just as it would
146 * when receiving mail from an upstream MTA.</p>
147 *
148 * <p>The only correction applied by <code>MessageProcessor</code> is to correct a
149 * partial originator address. If the originator address has a valid user part
150 * but no domain part, a domain part is added. The added domain is either the
151 * default domain specified in the configuration, or if not specified, the
152 * fully qualified name of the machine on which the fetch task is running.</p>
153 *
154 * <p>The status of messages on the server from which they were fetched that
155 * cannot be injected into the input spool due to non-correctable errors is
156 * determined by the undeliverable configuration options.</p>
157 *
158 * <p>Creation Date: 27-May-03</p>
159 *
160 */
161public class MessageProcessor extends ProcessorAbstract
162{
163 private MimeMessage fieldMessageIn;
164
165 /**
166 * Recipient cannot be found
167 */
168 private boolean fieldRecipientNotFound = false;
169
170 /**
171 * Recipient is a local user on a local host
172 */
173 private boolean fieldRemoteRecipient = true;
174
175 /**
176 * The mail's Received header at index remoteReceivedHeaderIndex is invalid.
177 */
178 private Boolean fieldRemoteReceivedHeaderInvalid;
179
180 /**
181 * Recipient is not a local user
182 */
183 private boolean fieldUserUndefined = false;
184
185 /**
186 * The Maximum Message has been exceeded
187 */
188 private Boolean fieldMaxMessageSizeExceeded;
189
190
191 /**
192 * Field names for an RFC2822 compliant RECEIVED Header
193 */
194 static final private String fieldRFC2822RECEIVEDHeaderFields =
195 "from by via with id for ;";
196
197 /**
198 * Recipient is blacklisted
199 */
200 private boolean fieldBlacklistedRecipient = false;
201
202 /**
203 * The RFC2822 compliant "Received : from" domain
204 */
205 private String fieldRemoteDomain;
206
207 /**
208 * The remote address derived from the remote domain
209 */
210 private String fieldRemoteAddress;
211
212 /**
213 * The remote host name derived from the remote domain
214 */
215 private String fieldRemoteHostName;
216
217 /**
218 * Constructor for MessageProcessor.
219 *
220 * @param account
221 */
222 private MessageProcessor(Account account)
223 {
224 super(account);
225 }
226
227 /**
228 * Constructor for MessageProcessor.
229 *
230 * @param messageIn
231 * @param account
232 */
233
234 MessageProcessor(
235 MimeMessage messageIn,
236 Account account)
237 {
238 this(account);
239 setMessageIn(messageIn);
240 }
241
242
243 /**
244 * Method process attempts to deliver a fetched message.
245 *
246 * @see org.apache.james.fetchmail.ProcessorAbstract#process()
247 */
248 public void process() throws MessagingException
249 {
250 // Log delivery attempt
251 if (getLogger().isDebugEnabled())
252 {
253 StringBuffer logMessageBuffer =
254 new StringBuffer("Attempting delivery of message with id. ");
255 logMessageBuffer.append(getMessageIn().getMessageID());
256 getLogger().debug(logMessageBuffer.toString());
257 }
258
259 // Determine the intended recipient
260 MailAddress intendedRecipient = getIntendedRecipient();
261 setRecipientNotFound(null == intendedRecipient);
262
263 if (isRecipientNotFound())
264 {
265 if (isDeferRecipientNotFound())
266 {
267
268 String messageID = getMessageIn().getMessageID();
269 if (!getDeferredRecipientNotFoundMessageIDs()
270 .contains(messageID))
271 {
272 getDeferredRecipientNotFoundMessageIDs().add(messageID);
273 if (getLogger().isDebugEnabled())
274 {
275 StringBuffer messageBuffer =
276 new StringBuffer("Deferred processing of message for which the intended recipient could not be found. Message ID: ");
277 messageBuffer.append(messageID);
278 getLogger().debug(messageBuffer.toString());
279 }
280 return;
281 }
282 else
283 {
284 getDeferredRecipientNotFoundMessageIDs().remove(messageID);
285 if (getLogger().isDebugEnabled())
286 {
287 StringBuffer messageBuffer =
288 new StringBuffer("Processing deferred message for which the intended recipient could not be found. Message ID: ");
289 messageBuffer.append(messageID);
290 getLogger().debug(messageBuffer.toString());
291 }
292 }
293 }
294
295 if (isRejectRecipientNotFound())
296 {
297 rejectRecipientNotFound();
298 return;
299 }
300 intendedRecipient = getRecipient();
301 StringBuffer messageBuffer =
302 new StringBuffer("Intended recipient not found. Using configured recipient as new envelope recipient - ");
303 messageBuffer.append(intendedRecipient);
304 messageBuffer.append('.');
305 logStatusInfo(messageBuffer.toString());
306 }
307
308 // Set the filter states
309 setBlacklistedRecipient(isBlacklistedRecipient(intendedRecipient));
310 setRemoteRecipient(!isLocalServer(intendedRecipient));
311 setUserUndefined(!isLocalRecipient(intendedRecipient));
312
313 // Apply the filters. Return if rejected
314 if (isRejectBlacklisted() && isBlacklistedRecipient())
315 {
316 rejectBlacklistedRecipient(intendedRecipient);
317 return;
318 }
319
320 if (isRejectRemoteRecipient() && isRemoteRecipient())
321 {
322 rejectRemoteRecipient(intendedRecipient);
323 return;
324 }
325
326 if (isRejectUserUndefined() && isUserUndefined())
327 {
328 rejectUserUndefined(intendedRecipient);
329 return;
330 }
331
332 if (isRejectMaxMessageSizeExceeded()
333 && isMaxMessageSizeExceeded().booleanValue())
334 {
335 rejectMaxMessageSizeExceeded(getMessageIn().getSize());
336 return;
337 }
338
339 if (isRejectRemoteReceivedHeaderInvalid()
340 && isRemoteReceivedHeaderInvalid().booleanValue())
341 {
342 rejectRemoteReceivedHeaderInvalid();
343 return;
344 }
345
346 // Create the mail
347 // If any of the mail addresses are malformed, we will get a
348 // ParseException.
349 // If the IP address and host name for the remote domain cannot
350 // be found, we will get an UnknownHostException.
351 // In both cases, we log the problem and
352 // return. The message disposition is defined by the
353 // <undeliverable> attributes.
354 Mail mail = null;
355 try
356 {
357 mail = createMail(createMessage(), intendedRecipient);
358 }
359 catch (ParseException ex)
360 {
361 handleParseException(ex);
362 return;
363 }
364 catch (UnknownHostException ex)
365 {
366 handleUnknownHostException(ex);
367 return;
368 }
369
370 addMailAttributes(mail);
371 addErrorMessages(mail);
372
373 // If this mail is bouncing move it to the ERROR repository
374 if (isBouncing())
375 {
376 handleBouncing(mail);
377 return;
378 }
379
380 // OK, lets send that mail!
381 sendMail(mail);
382 }
383
384 /**
385 * Method rejectRemoteRecipient.
386 * @param recipient
387 * @throws MessagingException
388 */
389 protected void rejectRemoteRecipient(MailAddress recipient)
390 throws MessagingException
391 {
392 // Update the flags of the received message
393 if (!isLeaveRemoteRecipient())
394 setMessageDeleted();
395
396 if (isMarkRemoteRecipientSeen())
397 setMessageSeen();
398
399 StringBuffer messageBuffer =
400 new StringBuffer("Rejected mail intended for remote recipient: ");
401 messageBuffer.append(recipient);
402 messageBuffer.append('.');
403 logStatusInfo(messageBuffer.toString());
404
405 return;
406 }
407
408 /**
409 * Method rejectBlacklistedRecipient.
410 * @param recipient
411 * @throws MessagingException
412 */
413 protected void rejectBlacklistedRecipient(MailAddress recipient)
414 throws MessagingException
415 {
416 // Update the flags of the received message
417 if (!isLeaveBlacklisted())
418 setMessageDeleted();
419 if (isMarkBlacklistedSeen())
420 setMessageSeen();
421
422 StringBuffer messageBuffer =
423 new StringBuffer("Rejected mail intended for blacklisted recipient: ");
424 messageBuffer.append(recipient);
425 messageBuffer.append('.');
426 logStatusInfo(messageBuffer.toString());
427
428 return;
429 }
430
431 /**
432 * Method rejectRecipientNotFound.
433 * @throws MessagingException
434 */
435 protected void rejectRecipientNotFound() throws MessagingException
436 {
437 // Update the flags of the received message
438 if (!isLeaveRecipientNotFound())
439 setMessageDeleted();
440
441 if (isMarkRecipientNotFoundSeen())
442 setMessageSeen();
443
444 StringBuffer messageBuffer =
445 new StringBuffer("Rejected mail for which a sole intended recipient could not be found.");
446 messageBuffer.append(" Recipients: ");
447 Address[] allRecipients = getMessageIn().getAllRecipients();
448 for (int i = 0; i < allRecipients.length; i++)
449 {
450 messageBuffer.append(allRecipients[i]);
451 messageBuffer.append(' ');
452 }
453 messageBuffer.append('.');
454 logStatusInfo(messageBuffer.toString());
455 return;
456 }
457
458 /**
459 * Method rejectUserUndefined.
460 * @param recipient
461 * @throws MessagingException
462 */
463 protected void rejectUserUndefined(MailAddress recipient)
464 throws MessagingException
465 {
466 // Update the flags of the received message
467 if (!isLeaveUserUndefined())
468 setMessageDeleted();
469
470 if (isMarkUserUndefinedSeen())
471 setMessageSeen();
472
473 StringBuffer messageBuffer =
474 new StringBuffer("Rejected mail intended for undefined user: ");
475 messageBuffer.append(recipient);
476 messageBuffer.append('.');
477 logStatusInfo(messageBuffer.toString());
478
479 return;
480 }
481
482 /**
483 * Method rejectMaxMessageSizeExceeded.
484 * @param message size
485 * @throws MessagingException
486 */
487 protected void rejectMaxMessageSizeExceeded(int messageSize)
488 throws MessagingException
489 {
490 // Update the flags of the received message
491 if (!isLeaveMaxMessageSizeExceeded())
492 setMessageDeleted();
493
494 if (isMarkMaxMessageSizeExceededSeen())
495 setMessageSeen();
496
497 StringBuffer messageBuffer =
498 new StringBuffer("Rejected mail exceeding message size limit. Message size: ");
499 messageBuffer.append(messageSize/1024);
500 messageBuffer.append("KB.");
501 logStatusInfo(messageBuffer.toString());
502
503 return;
504 }
505
506 /**
507 * Method rejectRemoteReceivedHeaderInvalid.
508 * @throws MessagingException
509 */
510 protected void rejectRemoteReceivedHeaderInvalid()
511 throws MessagingException
512 {
513 // Update the flags of the received message
514 if (!isLeaveRemoteReceivedHeaderInvalid())
515 setMessageDeleted();
516
517 if (isMarkRemoteReceivedHeaderInvalidSeen())
518 setMessageSeen();
519
520 StringBuffer messageBuffer =
521 new StringBuffer("Rejected mail with an invalid Received: header at index ");
522 messageBuffer.append(getRemoteReceivedHeaderIndex());
523 messageBuffer.append(".");
524 logStatusInfo(messageBuffer.toString());
525 return;
526 }
527
528 /**
529 * <p>Method createMessage answers a new <code>MimeMessage</code> from the
530 * fetched message.</p>
531 *
532 * <p>If the maximum message size is exceeded, an empty message is created,
533 * else the new message is a copy of the received message.</p>
534 *
535 * @return MimeMessage
536 * @throws MessagingException
537 */
538 protected MimeMessage createMessage() throws MessagingException
539 {
540 // Create a new messsage from the received message
541 MimeMessage messageOut = null;
542 if (isMaxMessageSizeExceeded().booleanValue())
543 messageOut = createEmptyMessage();
544 else
545 messageOut = new MimeMessage(getMessageIn());
546
547 // set the X-fetched headers
548 // Note this is still required to detect bouncing mail and
549 // for backwards compatibility with fetchPop
550 messageOut.addHeader("X-fetched-from", getFetchTaskName());
551
552 return messageOut;
553 }
554
555 /**
556 * Method createEmptyMessage answers a new
557 * <code>MimeMessage</code> from the fetched message with the message
558 * contents removed.
559 *
560 * @return MimeMessage
561 * @throws MessagingException
562 */
563 protected MimeMessage createEmptyMessage()
564 throws MessagingException
565 {
566 // Create an empty messsage
567 MimeMessage messageOut = new MimeMessage(getSession());
568
569 // Propogate the headers and subject
570 Enumeration headersInEnum = getMessageIn().getAllHeaderLines();
571 while (headersInEnum.hasMoreElements())
572 messageOut.addHeaderLine((String) headersInEnum.nextElement());
573 messageOut.setSubject(getMessageIn().getSubject());
574
575 // Add empty text
576 messageOut.setText("");
577
578 // Save
579 messageOut.saveChanges();
580
581 return messageOut;
582 }
583
584 /**
585 * Method createMail creates a new <code>Mail</code>.
586 *
587 * @param message
588 * @param recipient
589 * @return Mail
590 * @throws MessagingException
591 */
592 protected Mail createMail(MimeMessage message, MailAddress recipient)
593 throws MessagingException, UnknownHostException
594 {
595 Collection recipients = new ArrayList(1);
596 recipients.add(recipient);
597 MailImpl mail =
598 new MailImpl(getServer().getId(), getSender(), recipients, message);
599 // Ensure the mail is created with non-null remote host name and address,
600 // otherwise the Mailet chain may go splat!
601 if (getRemoteAddress() == null || getRemoteHostName() == null)
602 {
603 mail.setRemoteAddr("127.0.0.1");
604 mail.setRemoteHost("localhost");
605 }
606 else
607 {
608 mail.setRemoteAddr(getRemoteAddress());
609 mail.setRemoteHost(getRemoteHostName());
610 }
611
612 if (getLogger().isDebugEnabled())
613 {
614 StringBuffer messageBuffer =
615 new StringBuffer("Created mail with name: ");
616 messageBuffer.append(mail.getName());
617 messageBuffer.append(", sender: ");
618 messageBuffer.append(mail.getSender());
619 messageBuffer.append(", recipients: ");
620 Iterator recipientIterator = mail.getRecipients().iterator();
621 while (recipientIterator.hasNext())
622 {
623 messageBuffer.append(recipientIterator.next());
624 messageBuffer.append(' ');
625 }
626 messageBuffer.append(", remote address: ");
627 messageBuffer.append(mail.getRemoteAddr());
628 messageBuffer.append(", remote host name: ");
629 messageBuffer.append(mail.getRemoteHost());
630 messageBuffer.append('.');
631 getLogger().debug(messageBuffer.toString());
632 }
633 return mail;
634 }
635
636
637 /**
638 * Method getSender answers a <code>MailAddress</code> for the sender.
639 *
640 * @return MailAddress
641 * @throws MessagingException
642 */
643 protected MailAddress getSender() throws MessagingException
644 {
645 String from = "FETCHMAIL-SERVICE";
646 try {
647 from = ((InternetAddress) getMessageIn().getFrom()[0]).getAddress().trim();
648 }
649 catch (Exception _) {
650 getLogger().info("Could not identify sender -- using default value");
651 }
652
653 InternetAddress internetAddress = null;
654
655 // Check for domain part, add default if missing
656 if (from.indexOf('@') < 0)
657 {
658 StringBuffer fromBuffer = new StringBuffer(from);
659 fromBuffer.append('@');
660 fromBuffer.append(getDefaultDomainName());
661 internetAddress = new InternetAddress(fromBuffer.toString());
662 }
663 else
664 internetAddress = new InternetAddress(from);
665
666 return new MailAddress(internetAddress);
667 }
668
669 /**
670 * <p>Method computeRemoteDomain answers a <code>String</code> that is the
671 * RFC2822 compliant "Received : from" domain extracted from the message
672 * being processed.</p>
673 *
674 * <p>Normally this is the domain that sent the message to the host for the
675 * message store as reported by the second "received" header. The index of
676 * the header to use is specified by the configuration parameter
677 * <code>RemoteReceivedHeaderIndex</code>. If a header at this index does
678 * not exist, the domain of the successively closer "received" headers
679 * is tried until they are exhausted, then "localhost" is used.</p>
680 *
681 * @return String
682 */
683 protected String computeRemoteDomain() throws MessagingException
684 {
685 StringBuffer domainBuffer = new StringBuffer();
686 String[] headers = null;
687 if (getRemoteReceivedHeaderIndex() > -1)
688 getMessageIn().getHeader(RFC2822Headers.RECEIVED);
689
690 if (null != headers)
691 {
692 // If there are RECEIVED headers and the index to begin at is greater
693 // than -1, try and extract the domain
694 if (headers.length > 0)
695 {
696 final String headerTokens = " \n\r";
697
698 // Search the headers for a domain
699 for (int headerIndex =
700 headers.length > getRemoteReceivedHeaderIndex()
701 ? getRemoteReceivedHeaderIndex()
702 : headers.length - 1;
703 headerIndex >= 0 && domainBuffer.length() == 0;
704 headerIndex--)
705 {
706 // Find the "from" token
707 StringTokenizer tokenizer =
708 new StringTokenizer(headers[headerIndex], headerTokens);
709 boolean inFrom = false;
710 while (!inFrom && tokenizer.hasMoreTokens())
711 inFrom = tokenizer.nextToken().equals("from");
712
713 // Add subsequent tokens to the domain buffer until another
714 // field is encountered or there are no more tokens
715 while (inFrom && tokenizer.hasMoreTokens())
716 {
717 String token = tokenizer.nextToken();
718 if (inFrom =
719 getRFC2822RECEIVEDHeaderFields().indexOf(token)
720 == -1)
721 {
722 domainBuffer.append(token);
723 domainBuffer.append(' ');
724 }
725 }
726 }
727 }
728 }
729
730 // Default is "localhost"
731 if (domainBuffer.length() == 0)
732 domainBuffer.append("localhost");
733
734 return domainBuffer.toString().trim();
735 }
736
737 /**
738 * Method handleBouncing sets the Mail state to ERROR and delete from
739 * the message store.
740 *
741 * @param mail
742 */
743 protected void handleBouncing(Mail mail) throws MessagingException
744 {
745 mail.setState(Mail.ERROR);
746 setMessageDeleted();
747
748 mail.setErrorMessage(
749 "This mail from FetchMail task "
750 + getFetchTaskName()
751 + " seems to be bouncing!");
752 logStatusError("Message is bouncing! Deleted from message store and moved to the Error repository.");
753 }
754
755 /**
756 * Method handleParseException.
757 * @param ex
758 * @throws MessagingException
759 */
760 protected void handleParseException(ParseException ex)
761 throws MessagingException
762 {
763 // Update the flags of the received message
764 if (!isLeaveUndeliverable())
765 setMessageDeleted();
766 if (isMarkUndeliverableSeen())
767 setMessageSeen();
768 logStatusWarn("Message could not be delivered due to an error parsing a mail address.");
769 if (getLogger().isDebugEnabled())
770 {
771 StringBuffer messageBuffer =
772 new StringBuffer("UNDELIVERABLE Message ID: ");
773 messageBuffer.append(getMessageIn().getMessageID());
774 getLogger().debug(messageBuffer.toString(), ex);
775 }
776 }
777
778 /**
779 * Method handleUnknownHostException.
780 * @param ex
781 * @throws MessagingException
782 */
783 protected void handleUnknownHostException(UnknownHostException ex)
784 throws MessagingException
785 {
786 // Update the flags of the received message
787 if (!isLeaveUndeliverable())
788 setMessageDeleted();
789
790 if (isMarkUndeliverableSeen())
791 setMessageSeen();
792
793 logStatusWarn("Message could not be delivered due to an error determining the remote domain.");
794 if (getLogger().isDebugEnabled())
795 {
796 StringBuffer messageBuffer =
797 new StringBuffer("UNDELIVERABLE Message ID: ");
798 messageBuffer.append(getMessageIn().getMessageID());
799 getLogger().debug(messageBuffer.toString(), ex);
800 }
801 }
802
803 /**
804 * Method isLocalRecipient.
805 * @param recipient
806 * @return boolean
807 */
808 protected boolean isLocalRecipient(MailAddress recipient)
809 {
810 return isLocalUser(recipient) && isLocalServer(recipient);
811 }
812
813 /**
814 * Method isLocalServer.
815 * @param recipient
816 * @return boolean
817 */
818 protected boolean isLocalServer(MailAddress recipient)
819 {
820 return getServer().isLocalServer(recipient.getHost());
821 }
822
823 /**
824 * Method isLocalUser.
825 * @param recipient
826 * @return boolean
827 */
828 protected boolean isLocalUser(MailAddress recipient)
829 {
830 return getLocalUsers().containsCaseInsensitive(recipient.getUser());
831 }
832
833 /**
834 * Method isBlacklistedRecipient.
835 * @param recipient
836 * @return boolean
837 */
838 protected boolean isBlacklistedRecipient(MailAddress recipient)
839 {
840 return getBlacklist().contains(recipient);
841 }
842
843 /**
844 * Check if this mail has been bouncing by counting the X-fetched-from
845 * headers for this task
846 *
847 * @return boolean
848 */
849 protected boolean isBouncing() throws MessagingException
850 {
851 Enumeration enum =
852 getMessageIn().getMatchingHeaderLines(
853 new String[] { "X-fetched-from" });
854 int count = 0;
855 while (enum.hasMoreElements())
856 {
857 String header = (String) enum.nextElement();
858 if (header.equals(getFetchTaskName()))
859 count++;
860 }
861 return count >= 3;
862 }
863
864 /**
865 * Method sendMail.
866 * @param mail
867 * @throws MessagingException
868 */
869 protected void sendMail(Mail mail) throws MessagingException
870 {
871 // send the mail
872 getServer().sendMail(mail);
873
874 // Update the flags of the received message
875 if (!isLeave())
876 setMessageDeleted();
877
878 if (isMarkSeen())
879 setMessageSeen();
880
881 // Log the status
882 StringBuffer messageBuffer =
883 new StringBuffer("Spooled message to recipients: ");
884 Iterator recipientIterator = mail.getRecipients().iterator();
885 while (recipientIterator.hasNext())
886 {
887 messageBuffer.append(recipientIterator.next());
888 messageBuffer.append(' ');
889 }
890 messageBuffer.append('.');
891 logStatusInfo(messageBuffer.toString());
892 }
893
894
895 /**
896 * Method getEnvelopeRecipient answers the recipient if found else null.
897 *
898 * Try and parse the "for" parameter from a Received header
899 * Maybe not the most accurate parsing in the world but it should do
900 * I opted not to use ORO (maybe I should have)
901 *
902 * @param msg
903 * @return String
904 */
905
906 protected String getEnvelopeRecipient(MimeMessage msg) throws MessagingException
907 {
908 try
909 {
910 Enumeration enum =
911 msg.getMatchingHeaderLines(new String[] { "Received" });
912 while (enum.hasMoreElements())
913 {
914 String received = (String) enum.nextElement();
915
916 int nextSearchAt = 0;
917 int i = 0;
918 int start = 0;
919 int end = 0;
920 boolean hasBracket = false;
921 boolean usableAddress = false;
922 while (!usableAddress && (i != -1))
923 {
924 hasBracket = false;
925 i = received.indexOf("for ", nextSearchAt);
926 if (i > 0)
927 {
928 start = i + 4;
929 end = 0;
930 nextSearchAt = start;
931 for (int c = start; c < received.length(); c++)
932 {
933 char ch = received.charAt(c);
934 switch (ch)
935 {
936 case '<' :
937 hasBracket = true;
938 continue;
939 case '@' :
940 usableAddress = true;
941 continue;
942 case ' ' :
943 end = c;
944 break;
945 case ';' :
946 end = c;
947 break;
948 }
949 if (end > 0)
950 break;
951 }
952 }
953 }
954 if (usableAddress)
955 {
956 // lets try and grab the email address
957 String mailFor = received.substring(start, end);
958
959 // strip the <> around the address if there are any
960 if (mailFor.startsWith("<") && mailFor.endsWith(">"))
961 mailFor = mailFor.substring(1, (mailFor.length() - 1));
962
963 return mailFor;
964 }
965 }
966 }
967 catch (MessagingException me)
968 {
969 logStatusWarn("No Received headers found.");
970 }
971 return null;
972 }
973
974 /**
975 * Method getIntendedRecipient answers the sole intended recipient else null.
976 *
977 * @return MailAddress
978 * @throws MessagingException
979 */
980 protected MailAddress getIntendedRecipient() throws MessagingException
981 {
982 // If the original recipient should be ignored, answer the
983 // hard-coded recipient
984 if (isIgnoreRecipientHeader())
985 {
986 StringBuffer messageBuffer =
987 new StringBuffer("Ignoring recipient header. Using configured recipient as new envelope recipient: ");
988 messageBuffer.append(getRecipient());
989 messageBuffer.append('.');
990 logStatusInfo(messageBuffer.toString());
991 return getRecipient();
992 }
993
994 // If we can determine who the message was received for, answer
995 // the target recipient
996 String targetRecipient = getEnvelopeRecipient(getMessageIn());
997 if (targetRecipient != null)
998 {
999 MailAddress recipient = new MailAddress(targetRecipient);
1000 StringBuffer messageBuffer =
1001 new StringBuffer("Using original envelope recipient as new envelope recipient: ");
1002 messageBuffer.append(recipient);
1003 messageBuffer.append('.');
1004 logStatusInfo(messageBuffer.toString());
1005 return recipient;
1006 }
1007
1008 // If we can determine the intended recipient from all of the recipients,
1009 // answer the intended recipient. This requires that there is exactly one
1010 // recipient answered by getAllRecipients(), which examines the TO: CC: and
1011 // BCC: headers
1012 Address[] allRecipients = getMessageIn().getAllRecipients();
1013 if (allRecipients.length == 1)
1014 {
1015 MailAddress recipient =
1016 new MailAddress((InternetAddress) allRecipients[0]);
1017 StringBuffer messageBuffer =
1018 new StringBuffer("Using sole recipient header address as new envelope recipient: ");
1019 messageBuffer.append(recipient);
1020 messageBuffer.append('.');
1021 logStatusInfo(messageBuffer.toString());
1022 return recipient;
1023 }
1024
1025 return null;
1026 }
1027
1028 /**
1029 * Returns the messageIn.
1030 * @return MimeMessage
1031 */
1032 protected MimeMessage getMessageIn()
1033 {
1034 return fieldMessageIn;
1035 }
1036
1037 /**
1038 * Sets the messageIn.
1039 * @param messageIn The messageIn to set
1040 */
1041 protected void setMessageIn(MimeMessage messageIn)
1042 {
1043 fieldMessageIn = messageIn;
1044 }
1045
1046 /**
1047 * Returns the localRecipient.
1048 * @return boolean
1049 */
1050 protected boolean isRemoteRecipient()
1051 {
1052 return fieldRemoteRecipient;
1053 }
1054
1055 /**
1056 * Returns <code>boolean</code> indicating if the message to be delivered
1057 * was unprocessed in a previous delivery attempt.
1058 * @return boolean
1059 */
1060 protected boolean isPreviouslyUnprocessed()
1061 {
1062 return true;
1063 }
1064
1065 /**
1066 * Log the status of the current message as INFO.
1067 * @param detailMsg
1068 */
1069 protected void logStatusInfo(String detailMsg) throws MessagingException
1070 {
1071 getLogger().info(getStatusReport(detailMsg).toString());
1072 }
1073
1074 /**
1075 * Log the status the current message as WARN.
1076 * @param detailMsg
1077 */
1078 protected void logStatusWarn(String detailMsg) throws MessagingException
1079 {
1080 getLogger().warn(getStatusReport(detailMsg).toString());
1081 }
1082
1083 /**
1084 * Log the status the current message as ERROR.
1085 * @param detailMsg
1086 */
1087 protected void logStatusError(String detailMsg) throws MessagingException
1088 {
1089 getLogger().error(getStatusReport(detailMsg).toString());
1090 }
1091
1092 /**
1093 * Answer a <code>StringBuffer</code> containing a message reflecting
1094 * the current status of the message being processed.
1095 *
1096 * @param detailMsg
1097 * @return StringBuffer
1098 */
1099 protected StringBuffer getStatusReport(String detailMsg) throws MessagingException
1100 {
1101 StringBuffer messageBuffer = new StringBuffer(detailMsg);
1102 if (detailMsg.length() > 0)
1103 messageBuffer.append(' ');
1104 messageBuffer.append("Message ID: ");
1105 messageBuffer.append(getMessageIn().getMessageID());
1106 messageBuffer.append(". Flags: Seen = ");
1107 messageBuffer.append(new Boolean(isMessageSeen()));
1108 messageBuffer.append(", Delete = ");
1109 messageBuffer.append(new Boolean(isMessageDeleted()));
1110 messageBuffer.append('.');
1111 return messageBuffer;
1112 }
1113
1114 /**
1115 * Returns the userUndefined.
1116 * @return boolean
1117 */
1118 protected boolean isUserUndefined()
1119 {
1120 return fieldUserUndefined;
1121 }
1122
1123 /**
1124 * Is the DELETED flag set?
1125 * @throws MessagingException
1126 */
1127 protected boolean isMessageDeleted() throws MessagingException
1128 {
1129 return getMessageIn().isSet(Flags.Flag.DELETED);
1130 }
1131
1132 /**
1133 * Is the SEEN flag set?
1134 * @throws MessagingException
1135 */
1136 protected boolean isMessageSeen() throws MessagingException
1137 {
1138 return getMessageIn().isSet(Flags.Flag.SEEN);
1139 }
1140
1141 /**
1142 * Set the DELETED flag.
1143 * @throws MessagingException
1144 */
1145 protected void setMessageDeleted() throws MessagingException
1146 {
1147 getMessageIn().setFlag(Flags.Flag.DELETED, true);
1148 }
1149
1150 /* /**
1151 * Set the SEEN flag.
1152 * @throws MessagingException
1153 */
1154 protected void setMessageSeen() throws MessagingException
1155 {
1156 // If the Seen flag is not handled by the folder
1157 // allow a handler to do whatever it deems necessary
1158 if (!getMessageIn()
1159 .getFolder()
1160 .getPermanentFlags()
1161 .contains(Flags.Flag.SEEN))
1162 handleMarkSeenNotPermanent();
1163 else
1164 getMessageIn().setFlag(Flags.Flag.SEEN, true);
1165 }
1166
1167 /**
1168 * <p>Handler for when the folder does not support the SEEN flag.
1169 * The default behaviour implemented here is to log a warning and set the
1170 * flag anyway.</p>
1171 *
1172 * <p> Subclasses may choose to override this and implement their own
1173 * solutions.</p>
1174 *
1175 * @throws MessagingException
1176 */
1177 protected void handleMarkSeenNotPermanent() throws MessagingException
1178 {
1179 getMessageIn().setFlag(Flags.Flag.SEEN, true);
1180 logStatusWarn("Message marked as SEEN, but the folder does not support a permanent SEEN flag.");
1181 }
1182
1183 /**
1184 * Returns the Blacklisted.
1185 * @return boolean
1186 */
1187 protected boolean isBlacklistedRecipient()
1188 {
1189 return fieldBlacklistedRecipient;
1190 }
1191
1192 /**
1193 * Sets the localRecipient.
1194 * @param localRecipient The localRecipient to set
1195 */
1196 protected void setRemoteRecipient(boolean localRecipient)
1197 {
1198 fieldRemoteRecipient = localRecipient;
1199 }
1200
1201 /**
1202 * Sets the userUndefined.
1203 * @param userUndefined The userUndefined to set
1204 */
1205 protected void setUserUndefined(boolean userUndefined)
1206 {
1207 fieldUserUndefined = userUndefined;
1208 }
1209
1210 /**
1211 * Adds the mail attributes to a <code>Mail</code>.
1212 * @param aMail a Mail instance
1213 */
1214 protected void addMailAttributes(Mail aMail) throws MessagingException
1215 {
1216 aMail.setAttribute(
1217 getAttributePrefix() + "taskName",
1218 getFetchTaskName());
1219
1220 aMail.setAttribute(
1221 getAttributePrefix() + "folderName",
1222 getMessageIn().getFolder().getFullName());
1223
1224 if (isRemoteRecipient())
1225 aMail.setAttribute(
1226 getAttributePrefix() + "isRemoteRecipient",
1227 null);
1228
1229 if (isUserUndefined())
1230 aMail.setAttribute(getAttributePrefix() + "isUserUndefined", null);
1231
1232 if (isBlacklistedRecipient())
1233 aMail.setAttribute(
1234 getAttributePrefix() + "isBlacklistedRecipient",
1235 null);
1236
1237 if (isRecipientNotFound())
1238 aMail.setAttribute(
1239 getAttributePrefix() + "isRecipientNotFound",
1240 null);
1241
1242 if (isMaxMessageSizeExceeded().booleanValue())
1243 aMail.setAttribute(
1244 getAttributePrefix() + "isMaxMessageSizeExceeded",
1245 new Integer(getMessageIn().getSize()).toString());
1246
1247 if (isRemoteReceivedHeaderInvalid().booleanValue())
1248 aMail.setAttribute(
1249 getAttributePrefix() + "isRemoteReceivedHeaderInvalid",
1250 null);
1251 }
1252
1253 /**
1254 * Adds any required error messages to a <code>Mail</code>.
1255 * @param aMail a Mail instance
1256 */
1257 protected void addErrorMessages(Mail mail) throws MessagingException
1258 {
1259 if (isMaxMessageSizeExceeded().booleanValue())
1260 {
1261 StringBuffer msgBuffer =
1262 new StringBuffer("550 - Rejected - This message has been rejected as the message size of ");
1263 msgBuffer.append(getMessageIn().getSize() * 1000 / 1024 / 1000f);
1264 msgBuffer.append("KB exceeds the maximum permitted size of ");
1265 msgBuffer.append(getMaxMessageSizeLimit() / 1024);
1266 msgBuffer.append("KB.");
1267 mail.setErrorMessage(msgBuffer.toString());
1268 }
1269 }
1270
1271 /**
1272 * Sets the Blacklisted.
1273 * @param blacklisted The blacklisted to set
1274 */
1275 protected void setBlacklistedRecipient(boolean blacklisted)
1276 {
1277 fieldBlacklistedRecipient = blacklisted;
1278 }
1279
1280 /**
1281 * Returns the recipientNotFound.
1282 * @return boolean
1283 */
1284 protected boolean isRecipientNotFound()
1285 {
1286 return fieldRecipientNotFound;
1287 }
1288
1289 /**
1290 * Sets the recipientNotFound.
1291 * @param recipientNotFound The recipientNotFound to set
1292 */
1293 protected void setRecipientNotFound(boolean recipientNotFound)
1294 {
1295 fieldRecipientNotFound = recipientNotFound;
1296 }
1297
1298 /**
1299 * Returns the remoteDomain, lazily initialised as required.
1300 * @return String
1301 */
1302 protected String getRemoteDomain() throws MessagingException
1303 {
1304 String remoteDomain;
1305 if (null == (remoteDomain = getRemoteDomainBasic()))
1306 {
1307 updateRemoteDomain();
1308 return getRemoteDomain();
1309 }
1310 return remoteDomain;
1311 }
1312
1313 /**
1314 * Returns the remoteDomain.
1315 * @return String
1316 */
1317 private String getRemoteDomainBasic()
1318 {
1319 return fieldRemoteDomain;
1320 }
1321
1322 /**
1323 * Sets the remoteDomain.
1324 * @param remoteDomain The remoteDomain to set
1325 */
1326 protected void setRemoteDomain(String remoteDomain)
1327 {
1328 fieldRemoteDomain = remoteDomain;
1329 }
1330
1331 /**
1332 * Updates the remoteDomain.
1333 */
1334 protected void updateRemoteDomain() throws MessagingException
1335 {
1336 setRemoteDomain(computeRemoteDomain());
1337 }
1338
1339 /**
1340 * Answer the IP Address of the remote server for the message being
1341 * processed.
1342 * @return String
1343 * @throws MessagingException
1344 * @throws UnknownHostException
1345 */
1346 protected String computeRemoteAddress()
1347 throws MessagingException, UnknownHostException
1348 {
1349 String domain = getRemoteDomain();
1350 String address = null;
1351 String validatedAddress = null;
1352 int ipAddressStart = domain.indexOf('[');
1353 int ipAddressEnd = -1;
1354 if (ipAddressStart > -1)
1355 ipAddressEnd = domain.indexOf(']', ipAddressStart);
1356 if (ipAddressEnd > -1)
1357 address = domain.substring(ipAddressStart + 1, ipAddressEnd);
1358 else
1359 {
1360 int hostNameEnd = domain.indexOf(' ');
1361 if (hostNameEnd == -1)
1362 hostNameEnd = domain.length();
1363 address = domain.substring(0, hostNameEnd);
1364 }
1365 validatedAddress = org.apache.james.dnsserver.DNSServer.getByName(address).getHostAddress();
1366
1367 return validatedAddress;
1368 }
1369
1370 /**
1371 * Answer the Canonical host name of the remote server for the message
1372 * being processed.
1373 * @return String
1374 * @throws MessagingException
1375 * @throws UnknownHostException
1376 */
1377 protected String computeRemoteHostName()
1378 throws MessagingException, UnknownHostException
1379 {
1380 // These shenanigans are required to get the fully qualified
1381 // hostname prior to JDK 1.4 in which get getCanonicalHostName()
1382 // does the job for us
1383 InetAddress addr1 = org.apache.james.dnsserver.DNSServer.getByName(getRemoteAddress());
1384 InetAddress addr2 = org.apache.james.dnsserver.DNSServer.getByName(addr1.getHostAddress());
1385 return addr2.getHostName();
1386 }
1387
1388 /**
1389 * Returns the remoteAddress, lazily initialised as required.
1390 * @return String
1391 */
1392 protected String getRemoteAddress()
1393 throws MessagingException, UnknownHostException
1394 {
1395 String remoteAddress;
1396 if (null == (remoteAddress = getRemoteAddressBasic()))
1397 {
1398 updateRemoteAddress();
1399 return getRemoteAddress();
1400 }
1401 return remoteAddress;
1402 }
1403
1404 /**
1405 * Returns the remoteAddress.
1406 * @return String
1407 */
1408 private String getRemoteAddressBasic()
1409 {
1410 return fieldRemoteAddress;
1411 }
1412
1413 /**
1414 * Returns the remoteHostName, lazily initialised as required.
1415 * @return String
1416 */
1417 protected String getRemoteHostName()
1418 throws MessagingException, UnknownHostException
1419 {
1420 String remoteHostName;
1421 if (null == (remoteHostName = getRemoteHostNameBasic()))
1422 {
1423 updateRemoteHostName();
1424 return getRemoteHostName();
1425 }
1426 return remoteHostName;
1427 }
1428
1429 /**
1430 * Returns the remoteHostName.
1431 * @return String
1432 */
1433 private String getRemoteHostNameBasic()
1434 {
1435 return fieldRemoteHostName;
1436 }
1437
1438 /**
1439 * Sets the remoteAddress.
1440 * @param remoteAddress The remoteAddress to set
1441 */
1442 protected void setRemoteAddress(String remoteAddress)
1443 {
1444 fieldRemoteAddress = remoteAddress;
1445 }
1446
1447 /**
1448 * Updates the remoteAddress.
1449 */
1450 protected void updateRemoteAddress()
1451 throws MessagingException, UnknownHostException
1452 {
1453 setRemoteAddress(computeRemoteAddress());
1454 }
1455
1456 /**
1457 * Sets the remoteHostName.
1458 * @param remoteHostName The remoteHostName to set
1459 */
1460 protected void setRemoteHostName(String remoteHostName)
1461 {
1462 fieldRemoteHostName = remoteHostName;
1463 }
1464
1465 /**
1466 * Updates the remoteHostName.
1467 */
1468 protected void updateRemoteHostName()
1469 throws MessagingException, UnknownHostException
1470 {
1471 setRemoteHostName(computeRemoteHostName());
1472 }
1473
1474 /**
1475 * Returns the rFC2822RECEIVEDHeaderFields.
1476 * @return String
1477 */
1478 public static String getRFC2822RECEIVEDHeaderFields()
1479 {
1480 return fieldRFC2822RECEIVEDHeaderFields;
1481 }
1482
1483 /**
1484 * Returns the maxMessageSizeExceeded, lazily initialised as required.
1485 * @return Boolean
1486 */
1487 protected Boolean isMaxMessageSizeExceeded() throws MessagingException
1488 {
1489 Boolean isMaxMessageSizeExceeded = null;
1490 if (null
1491 == (isMaxMessageSizeExceeded = isMaxMessageSizeExceededBasic()))
1492 {
1493 updateMaxMessageSizeExceeded();
1494 return isMaxMe