noel 2003/08/30 11:52:18
Modified: . Tag: branch_2_1_fcs build.xml
src/conf Tag: branch_2_1_fcs james-assembly.xml
james-config.xml james-server.xml
Added: src/conf Tag: branch_2_1_fcs james-fetchmail.xml
src/java/org/apache/james/fetchmail Tag: branch_2_1_fcs
Account.java FetchMail.java FetchScheduler.java
FetchScheduler.xinfo FolderProcessor.java
MessageProcessor.java ParsedConfiguration.java
ProcessorAbstract.java ReaderInputStream.java
StoreProcessor.java
Log:
Added new FetchMail service. Enhanced and backported from the MAIN branch by Steve
Brewin.
Revision Changes Path
No revision
No revision
1.116.2.13 +5 -2 james-server/build.xml
Index: build.xml
===================================================================
RCS file: /home/cvs/james-server/build.xml,v
retrieving revision 1.116.2.12
retrieving revision 1.116.2.13
diff -u -r1.116.2.12 -r1.116.2.13
--- build.xml 28 Aug 2003 16:42:27 -0000 1.116.2.12
+++ build.xml 30 Aug 2003 18:52:18 -0000 1.116.2.13
@@ -21,7 +21,7 @@
<property file="${user.home}/.ant.properties"/>
<property name="name" value="james"/>
<property name="Name" value="James"/>
- <property name="version" value="2.2.0a-dev"/>
+ <property name="version" value="2.2.0a10"/>
<property name="year" value="1999-2003"/>
<!-- There should be no need to override default compiler but need to change
javac task to run without this -->
@@ -407,6 +407,9 @@
</zipfileset>
<zipfileset dir="${conf.dir}" fullpath="conf/james-liststores.xml">
<include name="james-liststores.xml"/>
+ </zipfileset>
+ <zipfileset dir="${conf.dir}" fullpath="conf/james-fetchmail.xml">
+ <include name="james-fetchmail.xml"/>
</zipfileset>
</sar>
</target>
No revision
No revision
1.13.4.2 +8 -0 james-server/src/conf/james-assembly.xml
Index: james-assembly.xml
===================================================================
RCS file: /home/cvs/james-server/src/conf/james-assembly.xml,v
retrieving revision 1.13.4.1
retrieving revision 1.13.4.2
diff -u -r1.13.4.1 -r1.13.4.2
--- james-assembly.xml 17 May 2003 07:16:22 -0000 1.13.4.1
+++ james-assembly.xml 30 Aug 2003 18:52:18 -0000 1.13.4.2
@@ -108,6 +108,14 @@
role="org.apache.avalon.cornerstone.services.scheduler.TimeScheduler"/>
<provide name="James" role="org.apache.james.services.MailServer"/>
</block>
+
+ <!-- FetchMail Service -->
+ <block name="fetchmail" class="org.apache.james.fetchmail.FetchScheduler" >
+ <provide name="scheduler"
+
role="org.apache.avalon.cornerstone.services.scheduler.TimeScheduler"/>
+ <provide name="James" role="org.apache.james.services.MailServer"/>
+ <provide name="users-store" role="org.apache.james.services.UsersStore"/>
+ </block>
<!-- The High Level Storage block -->
<block name="mailstore" class="org.apache.james.core.AvalonMailStore" >
1.40.2.17 +14 -3 james-server/src/conf/james-config.xml
Index: james-config.xml
===================================================================
RCS file: /home/cvs/james-server/src/conf/james-config.xml,v
retrieving revision 1.40.2.16
retrieving revision 1.40.2.17
diff -u -r1.40.2.16 -r1.40.2.17
--- james-config.xml 28 Aug 2003 16:34:15 -0000 1.40.2.16
+++ james-config.xml 30 Aug 2003 18:52:18 -0000 1.40.2.17
@@ -2,6 +2,7 @@
<!DOCTYPE config [
<!ENTITY listserverConfig SYSTEM "file:../apps/james/conf/james-listmanager.xml">
<!ENTITY listserverStores SYSTEM "file:../apps/james/conf/james-liststores.xml">
+<!ENTITY fetchmailConfig SYSTEM "file:../apps/james/conf/james-fetchmail.xml">
]>
<!-- Configuration file for the Apache Jakarta James server -->
@@ -99,8 +100,9 @@
<!-- Fetch pop block, fetches mail from POP3 servers and inserts it into the
incoming spool -->
<!-- Warning: It is important to prevent mail from looping by setting the -->
- <!-- fetched domains in the <servernames> section of the <James> block -->
- <!-- above. This block is disabled by default. -->
+ <!-- fetched domains in the <servernames> section of the <James> block -->
+ <!-- above. This block is disabled by default. -->
+ <!-- FetchPOP is being deprecated in favor of FetchMail -->
<fetchpop enabled="false">
<!-- You can have as many fetch tasks as you want, but each must have a -->
<!-- unique name by which it identified -->
@@ -116,6 +118,15 @@
</fetch>
</fetchpop>
+ <!-- This is an example configuration for FetchMail, a JavaMail based gateway
-->
+ <!-- service that pulls messages from other sources, and inserts them into the
-->
+ <!-- spool. They are then processed normally, although FetchMail generally
-->
+ <!-- has to fabricate some of the envelope information. FetchMail should be
-->
+ <!-- considered a mail gateway, rather than a relay, in RFC terms.
-->
+ <!-- Fetchmail is a functionally richer replacement for FetchPOP.
-->
+ <!-- CHECKME: FetchMail is disabled by default, and must be configured to use.
-->
+ <!-- Edit the file referred to by fetchmailConfig to enable and configure.
-->
+ &fetchmailConfig;
<!-- The James Spool Manager block -->
<!-- -->
1.11.4.3 +14 -0 james-server/src/conf/james-server.xml
Index: james-server.xml
===================================================================
RCS file: /home/cvs/james-server/src/conf/james-server.xml,v
retrieving revision 1.11.4.2
retrieving revision 1.11.4.3
diff -u -r1.11.4.2 -r1.11.4.3
--- james-server.xml 9 Feb 2003 06:55:52 -0000 1.11.4.2
+++ james-server.xml 30 Aug 2003 18:52:18 -0000 1.11.4.3
@@ -77,6 +77,9 @@
<category name="fetchpop" log-level="INFO">
<log-target id-ref="fetchpop-target"/>
</category>
+ <category name="fetchmail" log-level="INFO">
+ <log-target id-ref="fetchmail-target"/>
+ </category>
</categories>
<!-- Logger targets -->
@@ -271,6 +274,17 @@
</file>
<file id="fetchpop-target">
<filename>${app.home}/logs/fetchpop</filename>
+ <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}:
%{message}\n%{throwable}</format>
+ <append>true</append>
+ <rotation type="unique" pattern="-yyyy-MM-dd-HH-mm" suffix=".log">
+ <or>
+ <date>dd</date>
+ <size>10485760</size>
+ </or>
+ </rotation>
+ </file>
+ <file id="fetchmail-target">
+ <filename>${app.home}/logs/fetchmail</filename>
<format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}:
%{message}\n%{throwable}</format>
<append>true</append>
<rotation type="unique" pattern="-yyyy-MM-dd-HH-mm" suffix=".log">
No revision
Index: james-server.xml
===================================================================
RCS file: /home/cvs/james-server/src/conf/james-server.xml,v
retrieving revision 1.11.4.2
retrieving revision 1.11.4.3
diff -u -r1.11.4.2 -r1.11.4.3
--- james-server.xml 9 Feb 2003 06:55:52 -0000 1.11.4.2
+++ james-server.xml 30 Aug 2003 18:52:18 -0000 1.11.4.3
@@ -77,6 +77,9 @@
<category name="fetchpop" log-level="INFO">
<log-target id-ref="fetchpop-target"/>
</category>
+ <category name="fetchmail" log-level="INFO">
+ <log-target id-ref="fetchmail-target"/>
+ </category>
</categories>
<!-- Logger targets -->
@@ -271,6 +274,17 @@
</file>
<file id="fetchpop-target">
<filename>${app.home}/logs/fetchpop</filename>
+ <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}:
%{message}\n%{throwable}</format>
+ <append>true</append>
+ <rotation type="unique" pattern="-yyyy-MM-dd-HH-mm" suffix=".log">
+ <or>
+ <date>dd</date>
+ <size>10485760</size>
+ </or>
+ </rotation>
+ </file>
+ <file id="fetchmail-target">
+ <filename>${app.home}/logs/fetchmail</filename>
<format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}:
%{message}\n%{throwable}</format>
<append>true</append>
<rotation type="unique" pattern="-yyyy-MM-dd-HH-mm" suffix=".log">
No revision
Index: james-server.xml
===================================================================
RCS file: /home/cvs/james-server/src/conf/james-server.xml,v
retrieving revision 1.11.4.2
retrieving revision 1.11.4.3
diff -u -r1.11.4.2 -r1.11.4.3
--- james-server.xml 9 Feb 2003 06:55:52 -0000 1.11.4.2
+++ james-server.xml 30 Aug 2003 18:52:18 -0000 1.11.4.3
@@ -77,6 +77,9 @@
<category name="fetchpop" log-level="INFO">
<log-target id-ref="fetchpop-target"/>
</category>
+ <category name="fetchmail" log-level="INFO">
+ <log-target id-ref="fetchmail-target"/>
+ </category>
</categories>
<!-- Logger targets -->
@@ -271,6 +274,17 @@
</file>
<file id="fetchpop-target">
<filename>${app.home}/logs/fetchpop</filename>
+ <format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}:
%{message}\n%{throwable}</format>
+ <append>true</append>
+ <rotation type="unique" pattern="-yyyy-MM-dd-HH-mm" suffix=".log">
+ <or>
+ <date>dd</date>
+ <size>10485760</size>
+ </or>
+ </rotation>
+ </file>
+ <file id="fetchmail-target">
+ <filename>${app.home}/logs/fetchmail</filename>
<format>%{time:dd/MM/yy HH:mm:ss} %5.5{priority} %{category}:
%{message}\n%{throwable}</format>
<append>true</append>
<rotation type="unique" pattern="-yyyy-MM-dd-HH-mm" suffix=".log">
1.1.2.1 +195 -0 james-server/src/conf/Attic/james-fetchmail.xml
No revision
No revision
1.1.2.1 +258 -0
james-server/src/java/org/apache/james/fetchmail/Attic/Account.java
1.9.2.1 +618 -342 james-server/src/java/org/apache/james/fetchmail/FetchMail.java
Index: FetchMail.java
===================================================================
RCS file: /home/cvs/james-server/src/java/org/apache/james/fetchmail/FetchMail.java,v
retrieving revision 1.9
retrieving revision 1.9.2.1
diff -u -r1.9 -r1.9.2.1
--- FetchMail.java 28 Apr 2003 14:00:40 -0000 1.9
+++ FetchMail.java 30 Aug 2003 18:52:18 -0000 1.9.2.1
@@ -55,24 +55,14 @@
* originally written at the National Center for Supercomputing Applications,
* University of Illinois, Urbana-Champaign.
*/
-
package org.apache.james.fetchmail;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.Properties;
-
-import javax.mail.Address;
-import javax.mail.Flags;
-import javax.mail.Folder;
-import javax.mail.Message;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
import javax.mail.MessagingException;
-import javax.mail.Session;
-import javax.mail.Store;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.ParseException;
import org.apache.avalon.cornerstone.services.scheduler.Target;
import org.apache.avalon.framework.configuration.Configurable;
@@ -81,398 +71,684 @@
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
-import org.apache.james.core.MailImpl;
import org.apache.james.services.MailServer;
-import org.apache.mailet.Mail;
-import org.apache.mailet.MailAddress;
+import org.apache.james.services.UsersStore;
+import org.apache.james.services.UsersRepository;
/**
- *
- * A class which fetches mail from a single account and inserts it
- * into the incoming spool
- *
- * $Id$
- *
+ * <p>Class <code>FetchMail</code> is an Avalon task that is periodically
+ * triggered to fetch mail from a JavaMail Message Store.</p>
+ *
+ * <p>The lifecycle of an instance of <code>FetchMail</code> is managed by
+ * Avalon. The <code>configure(Configuration)</code> method is invoked to parse
+ * and validate Configuration properties. The targetTriggered(String) method is
+ * invoked to execute the task.</p>
+ *
+ * <p>When triggered, a sorted list of Message Store Accounts to be processed is
+ * built. Each Message Store Account is processed by delegating to
+ * <code>StoreProcessor</code>.</p>
+ *
+ * <p>There are two kinds of Message Store Accounts, static and dynamic. Static
+ * accounts are expliciltly declared in the Configuration. Dynamic accounts are
+ * built each time the task is executed, one per each user defined to James,
+ * using the James user name with a configurable prefix and suffix to define
+ * the host user identity and recipient identity for each Account. Dynamic
+ * accounts allow <code>FetchMail</code> to fetch mail for all James users
+ * without modifying the Configuration parameters or restarting the Avalon
+ * server.</p>
+ *
+ * <p>To fully understand the operations supported by this task, read the Class
+ * documention for each Class in the delegation chain starting with this
+ * class' delegate, <code>StoreProcessor</code>. </p>
+ *
+ * <p>Creation Date: 24-May-03</p>
+ *
*/
-public class FetchMail extends AbstractLogEnabled implements Configurable, Target {
- /**
- * The MailServer service
- */
- private MailServer server;
+public class FetchMail extends AbstractLogEnabled implements Configurable, Target
+{
/**
- * The user to send the fetched mail to
+ * Creation Date: 06-Jun-03
*/
- private MailAddress recipient;
+ private class ParsedDynamicAccountParameters
+ {
+ private String fieldUserPrefix;
+ private String fieldUserSuffix;
+
+ private String fieldPassword;
+
+ private int fieldSequenceNumber;
+
+ private boolean fieldIgnoreRecipientHeader;
+ private String fieldRecipientPrefix;
+ private String fieldRecipientSuffix;
+
+ /**
+ * Constructor for ParsedDynamicAccountParameters.
+ */
+ private ParsedDynamicAccountParameters()
+ {
+ super();
+ }
- /**
- * Don't parse header looking for recipient
- */
- private boolean ignoreOriginalRecipient;
+ /**
+ * Constructor for ParsedDynamicAccountParameters.
+ */
+ public ParsedDynamicAccountParameters(
+ int sequenceNumber,
+ Configuration configuration)
+ throws ConfigurationException
+ {
+ this();
+ setSequenceNumber(sequenceNumber);
+ setUserPrefix(configuration.getAttribute("userprefix", ""));
+ setUserSuffix(configuration.getAttribute("usersuffix", ""));
+ setRecipientPrefix(configuration.getAttribute("recipientprefix", ""));
+ setRecipientSuffix(configuration.getAttribute("recipientsuffix", ""));
+ setPassword(configuration.getAttribute("password"));
+ setIgnoreRecipientHeader(
+ configuration.getAttributeAsBoolean("ignorercpt-header"));
+ }
+
+ /**
+ * Returns the recipientprefix.
+ * @return String
+ */
+ public String getRecipientPrefix()
+ {
+ return fieldRecipientPrefix;
+ }
- /**
- * The unique, identifying name for this task
- */
- private String fetchTaskName;
+ /**
+ * Returns the recipientsuffix.
+ * @return String
+ */
+ public String getRecipientSuffix()
+ {
+ return fieldRecipientSuffix;
+ }
+ /**
+ * Returns the userprefix.
+ * @return String
+ */
+ public String getUserPrefix()
+ {
+ return fieldUserPrefix;
+ }
+
+ /**
+ * Returns the userSuffix.
+ * @return String
+ */
+ public String getUserSuffix()
+ {
+ return fieldUserSuffix;
+ }
+
+ /**
+ * Sets the recipientprefix.
+ * @param recipientprefix The recipientprefix to set
+ */
+ protected void setRecipientPrefix(String recipientprefix)
+ {
+ fieldRecipientPrefix = recipientprefix;
+ }
+
+ /**
+ * Sets the recipientsuffix.
+ * @param recipientsuffix The recipientsuffix to set
+ */
+ protected void setRecipientSuffix(String recipientsuffix)
+ {
+ fieldRecipientSuffix = recipientsuffix;
+ }
+
+ /**
+ * Sets the userprefix.
+ * @param userprefix The userprefix to set
+ */
+ protected void setUserPrefix(String userprefix)
+ {
+ fieldUserPrefix = userprefix;
+ }
+
+ /**
+ * Sets the userSuffix.
+ * @param userSuffix The userSuffix to set
+ */
+ protected void setUserSuffix(String userSuffix)
+ {
+ fieldUserSuffix = userSuffix;
+ }
+
+ /**
+ * Returns the password.
+ * @return String
+ */
+ public String getPassword()
+ {
+ return fieldPassword;
+ }
+
+ /**
+ * Sets the ignoreRecipientHeader.
+ * @param ignoreRecipientHeader The ignoreRecipientHeader to set
+ */
+ protected void setIgnoreRecipientHeader(boolean ignoreRecipientHeader)
+ {
+ fieldIgnoreRecipientHeader = ignoreRecipientHeader;
+ }
+
+ /**
+ * Sets the password.
+ * @param password The password to set
+ */
+ protected void setPassword(String password)
+ {
+ fieldPassword = password;
+ }
+
+ /**
+ * Returns the ignoreRecipientHeader.
+ * @return boolean
+ */
+ public boolean isIgnoreRecipientHeader()
+ {
+ return fieldIgnoreRecipientHeader;
+ }
+
+ /**
+ * Returns the sequenceNumber.
+ * @return int
+ */
+ public int getSequenceNumber()
+ {
+ return fieldSequenceNumber;
+ }
+
+ /**
+ * Sets the sequenceNumber.
+ * @param sequenceNumber The sequenceNumber to set
+ */
+ protected void setSequenceNumber(int sequenceNumber)
+ {
+ fieldSequenceNumber = sequenceNumber;
+ }
+
+ }
/**
- * The server host name for this fetch task
+ * @see
org.apache.avalon.cornerstone.services.scheduler.Target#targetTriggered(String)
*/
- private String sHost;
+ private boolean fieldFetching = false;
+
/**
- * The user name for this fetch task
+ * The Configuration for this task
*/
- private String sUser;
-
+ private ParsedConfiguration fieldConfiguration;
+
/**
- * The user password for this fetch task
+ * A List of ParsedDynamicAccountParameters, one for every <alllocal> entry
+ * in the configuration.
*/
- private String sPass;
-
+ private List fieldParsedDynamicAccountParameters;
+
/**
- * Keep retrieved messages on the remote mailserver. Normally, messages
- * are deleted from the folder on the mailserver after they have been retrieved
+ * The Static Accounts for this task.
+ * These are setup when the task is configured.
*/
- private boolean bKeep = false;
-
+ private List fieldStaticAccounts;
+
/**
- * Retrieve both old (seen) and new messages from the mailserver. The default
- * is to fetch only messages the server has not marked as seen.
+ * The Dynamic Accounts for this task.
+ * These are setup each time the is run.
*/
- private boolean bAll = false;
-
- /**
- * Recurse folders if available?
+ private List fieldDynamicAccounts;
+
+ /**
+ * The MailServer service
*/
- private boolean bRecurse = false;
-
- /**
- * The name of the javamail provider we want to user (pop3,imap,nntp,etc...)
- *
+ private MailServer fieldServer;
+
+ /**
+ * The Local Users repository
*/
- private String javaMailProviderName = "pop3";
+ private UsersRepository fieldLocalUsers;
/**
- * The name of the folder to fetch from the javamail provider
- *
+ * Constructor for POP3mail.
*/
- private String javaMailFolderName = "INBOX";
-
+ public FetchMail()
+ {
+ super();
+ }
/**
- * @see
org.apache.avalon.cornerstone.services.scheduler.Target#targetTriggered(String)
+ * Method configure parses and validates the Configuration data and creates
+ * a new <code>ParsedConfiguration</code>, an <code>Account</code> for each
+ * configured static account and a <code>ParsedDynamicAccountParameters</code>
+ * for each dynamic account.
+ *
+ * @see
org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
*/
- private boolean fetching = false;
-
-
- public boolean processMessage(Session session, MimeMessage message, MimeMessage
received) {
-
- Collection recipients = new ArrayList(1);
- try {
-
-
- if (!ignoreOriginalRecipient) {
- String er = getEnvelopeRecipient(message);
- if (er != null) {
- recipients.add(new MailAddress(er));
- getLogger().info("Using original envelope recipient as new
envelope recipient");
- } else {
- Address[] to = message.getAllRecipients();
- if (to.length == 1) {
- recipients.add(new
- MailAddress((InternetAddress) to[0]));
- getLogger().info("Using To: header address as new envelope
recipient");
- } else {
- getLogger().info("Using configured recipient as new
envelope recipient");
- recipients.add(recipient);
- }
- }
- } else {
- getLogger().info("Using configured recipient as new envelope
recipient");
- recipients.add(recipient);
+ public void configure(Configuration configuration)
+ throws ConfigurationException
+ {
+
+ Configuration[] allAccounts = configuration.getChildren("accounts");
+ if (allAccounts.length < 1)
+ throw new ConfigurationException("Missing <accounts> section.");
+ if (allAccounts.length > 1)
+ throw new ConfigurationException("Too many <accounts> sections, there
must be exactly one");
+ Configuration accounts = allAccounts[0];
+
+ // Create an Account for every configured account
+ Configuration[] accountsChildren = accounts.getChildren();
+ if (accountsChildren.length < 1)
+ throw new ConfigurationException("Missing <account> section.");
+
+ for (int i = 0; i < accountsChildren.length; i++)
+ {
+ Configuration accountsChild = accountsChildren[i];
+
+ if (accountsChild.getName() == "alllocal")
+ {
+ // <allLocal> is dynamic, save the parameters for accounts to
+ // be created when the task is triggered
+ getParsedDynamicAccountParameters().add(
+ new ParsedDynamicAccountParameters(i, accountsChild));
+ continue;
}
-
-
-
- //
- // set the X-fetched-from header
- received.addHeader("X-fetched-from", fetchTaskName);
-
- Mail mail = new MailImpl(server.getId(), new
- MailAddress((InternetAddress) received.getFrom()[0]),
recipients, received);
-
-
- // Lets see if this mail has been bouncing by counting
- // the X-fetched-from headers
- // if it is then move it to the ERROR repository
- Enumeration enum = message.getMatchingHeaderLines(new
String[]{"X-fetched-from"});
- int count = 1;
- while (enum.hasMoreElements()) {
- Object o = enum.nextElement();
- count++;
- }
- if (count > 3) {
- mail.setState(Mail.ERROR);
- mail.setErrorMessage("This mail from FetchMail task " +
fetchTaskName + " seems to be bounceing!");
- getLogger().error("A message from FetchMail task " + fetchTaskName
+ " seems to be bounceing! Moved to Error repository");
- return false;
+ if (accountsChild.getName() == "account")
+ {
+ // Create an Account for the named user and
+ // add it to the list of static accounts
+ getStaticAccounts().add(
+ new Account(
+ i,
+ accountsChild.getAttribute("user"),
+ accountsChild.getAttribute("password"),
+ accountsChild.getAttribute("recipient"),
+ accountsChild.getAttributeAsBoolean(
+ "ignorercpt-header")));
+ continue;
}
- server.sendMail(mail);
- getLogger().debug("Spooled message to " +
- recipients.toString());
-
- //
- // logging if needed
- getLogger().debug("Sent message " + message.toString());
- return true;
- } catch (ParseException pe) {
- recipients.add(recipient);
- } catch (MessagingException innerE) {
- getLogger().error("can't insert message " + message.toString());
- }
- return false;
- }
-
- public boolean processFolder(Session session, Folder folder) {
-
- boolean ret = false;
-
- try {
-
- //
- // try to open read/write and if that fails try read-only
- try {
- folder.open(Folder.READ_WRITE);
- } catch (MessagingException ex) {
- try {
- folder.open(Folder.READ_ONLY);
- } catch (MessagingException ex2) {
- getLogger().debug(fetchTaskName + " Failed to open folder!");
- }
- }
-
-// int totalMessages = folder.getMessageCount();
-// if (totalMessages == 0) {
-// getLogger().debug(fetchTaskName + " Empty folder");
-// folder.close(false);
-// fetching = false;
-// return false;
-// }
-
- Message[] msgs = folder.getMessages();
- MimeMessage[] received = new MimeMessage[folder.getMessageCount()];
-
- int j = 0;
- for (int i = 0; i < msgs.length; i++, j++) {
- Flags flags = msgs[i].getFlags();
- MimeMessage message = (MimeMessage) msgs[i];
-
- //
- // saved recieved messages for further processing...
- received[j] = new MimeMessage(/*session,*/ message);
-
- received[j].addHeader("X-fetched-folder", folder.getFullName());
-
- if (bAll) {
- ret = processMessage(session, message, received[j]);
- } else if (message.isSet(Flags.Flag.SEEN)) {
- ret = processMessage(session, message, received[j]);
- }
+ throw new ConfigurationException(
+ "Illegal token: <"
+ + accountsChild.getName()
+ + "> in <accounts>");
+ }
+ setConfiguration(
+ new ParsedConfiguration(
+ configuration,
+ getLogger(),
+ getServer(),
+ getLocalUsers()));
+ }
- if (ret) {
- //
- // need to get the flags again just in case processMessage
- // has changed the flags....
- Flags f = received[j].getFlags();
-
- if (!bKeep) {
-
- message.setFlag(Flags.Flag.DELETED, true);
- } else {
- f.add(Flags.Flag.SEEN);
+ /**
+ * Method target triggered fetches mail for each configured account.
+ *
+ * @see
org.apache.avalon.cornerstone.services.scheduler.Target#targetTriggered(String)
+ */
+ public void targetTriggered(String arg0)
+ {
+ // if we are already fetching then just return
+ if (isFetching())
+ return;
- received[j].setFlags(f, true);
+ // Enter Fetching State
+ try
+ {
+ setFetching(true);
+ getLogger().info(
+ getConfiguration().getFetchTaskName()
+ + " fetcher starting fetches");
+
+ // Reset and get the dynamic accounts,
+ // merge with the static accounts and
+ // sort the accounts so they are in the order
+ // they were entered in config.xml
+ resetDynamicAccounts();
+ ArrayList mergedAccounts =
+ new ArrayList(
+ getDynamicAccounts().size() + getStaticAccounts().size());
+ mergedAccounts.addAll(getDynamicAccounts());
+ mergedAccounts.addAll(getStaticAccounts());
+ Collections.sort(mergedAccounts);
+
+ // Fetch each account
+ Iterator accounts = mergedAccounts.iterator();
+ while (accounts.hasNext())
+ {
+ Account account = (Account) accounts.next();
+ ParsedConfiguration configuration = getConfiguration();
+ configuration.setUser(account.getUser());
+ configuration.setPassword(account.getPassword());
+ configuration.setRecipient(account.getRecipient());
+ configuration.setIgnoreRecipientHeader(
+ account.isIgnoreRecipientHeader());
+ try
+ {
+ new StoreProcessor(configuration).process();
}
+ catch (MessagingException ex)
+ {
+ getLogger().debug(ex.toString());
}
}
- folder.close(true);
-
- //
- // see if this folder contains subfolders and recurse is true
- if (bRecurse) {
- if ((folder.getType() & Folder.HOLDS_FOLDERS) != 0) {
- //
- // folder contains subfolders...
- Folder folders[] = folder.list();
-
- for (int k = 0; k < folders.length; k++) {
- processFolder(session, folders[k]);
- }
-
- }
- }
- return true;
- } catch (MessagingException mex) {
- getLogger().debug(mex.toString());
+ }
+ catch (ConfigurationException ex)
+ {
+ getLogger().error(ex.toString());
+ }
+ finally
+ {
+ // Ensure the dynamic accounts are thrown away
+ resetDynamicAccounts();
+
+ getLogger().info(
+ getConfiguration().getFetchTaskName()
+ + " fetcher completed fetches");
- } /*catch (IOException ioex) {
- getLogger().debug(ioex.toString());
- } */
- fetching = false;
- return false;
+ // Exit Fetching State
+ setFetching(false);
+ }
}
+ /**
+ * Returns the fetching.
+ * @return boolean
+ */
+ protected boolean isFetching()
+ {
+ return fieldFetching;
+ }
- public void targetTriggered(String arg0) {
- Store store = null;
- Session session = null;
- Folder folder = null;
-
- // Get a Properties object
- Properties props = System.getProperties();
-
-
- //
- // if we are already fetching then just return
- if (fetching) return;
- fetching = true;
-
-
- if (getLogger().isDebugEnabled()) {
- getLogger().debug(fetchTaskName + " fetcher starting fetch");
+ /**
+ * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
+ */
+ public void service(final ServiceManager manager) throws ServiceException
+ {
+ try
+ {
+ setServer((MailServer) manager.lookup(MailServer.ROLE));
+ }
+ catch (ClassCastException cce)
+ {
+ StringBuffer errorBuffer =
+ new StringBuffer(128).append("Component ").append(
+ MailServer.ROLE).append(
+ "does not implement the required interface.");
+ throw new ServiceException("", errorBuffer.toString());
}
+ UsersStore usersStore =
+ (UsersStore) manager.lookup("org.apache.james.services.UsersStore");
+ setLocalUsers(usersStore.getRepository("LocalUsers"));
+ if (getLocalUsers() == null)
+ throw new ServiceException(
+ "",
+ "The user repository could not be found.");
+ }
- // Get a Session object
- session = Session.getDefaultInstance(props, null);
- // session.setDebug(debug);
- // Get a Store object
- try {
- store = session.getStore(javaMailProviderName);
- // Connect
- if (sHost != null || sUser != null || sPass != null)
- store.connect(sHost, sUser, sPass);
- else
- store.connect();
+
- // Open the Folder
- folder = store.getFolder(javaMailFolderName);
- if (folder == null) {
- getLogger().debug(fetchTaskName + " No default folder");
- }
+ /**
+ * Sets the fetching.
+ * @param fetching The fetching to set
+ */
+ protected void setFetching(boolean fetching)
+ {
+ fieldFetching = fetching;
+ }
- processFolder(session, folder);
+ /**
+ * Returns the server.
+ * @return MailServer
+ */
+ protected MailServer getServer()
+ {
+ return fieldServer;
+ }
- store.close();
- } catch (MessagingException ex) {
- getLogger().debug(fetchTaskName + ex.getMessage());
- }
- fetching = false;
+ /**
+ * Returns the configuration.
+ * @return ParsedConfiguration
+ */
+ protected ParsedConfiguration getConfiguration()
+ {
+ return fieldConfiguration;
}
/**
- * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
+ * Sets the configuration.
+ * @param configuration The configuration to set
*/
- public void service(final ServiceManager manager ) throws ServiceException {
- try {
- server = (MailServer) manager.lookup(MailServer.ROLE);
- } catch (ClassCastException cce) {
- StringBuffer errorBuffer =
- new StringBuffer(128).append("Component
").append(MailServer.ROLE).append(
- "does not implement the required interface.");
- throw new ServiceException("",errorBuffer.toString());
- }
+ protected void setConfiguration(ParsedConfiguration configuration)
+ {
+ fieldConfiguration = configuration;
}
+/**
+ * Sets the server.
+ * @param server The server to set
+ */
+protected void setServer(MailServer server)
+{
+ fieldServer = server;
+}
+
+/**
+ * Returns the localUsers.
+ * @return UsersRepository
+ */
+protected UsersRepository getLocalUsers()
+{
+ return fieldLocalUsers;
+}
+/**
+ * Sets the localUsers.
+ * @param localUsers The localUsers to set
+ */
+protected void setLocalUsers(UsersRepository localUsers)
+{
+ fieldLocalUsers = localUsers;
+}
+
+ /**
+ * Returns the accounts. Initializes if required.
+ * @return List
+ */
+ protected List getStaticAccounts()
+ {
+ List accounts = null;
+ if (null == (accounts = getStaticAccountsBasic()))
+ {
+ updateStaticAccounts();
+ return getStaticAccounts();
+ }
+ return fieldStaticAccounts;
+ }
+
/**
- * Try and parse the "for" parameter from a Received header
- * Maybe not the most accurate parsing in the world but it should do
- * I opted not to use ORO (maybe I should have)
- */
- private String getEnvelopeRecipient(MimeMessage msg) {
- try {
- Enumeration enum = msg.getMatchingHeaderLines(new String[]{"Received"});
- while (enum.hasMoreElements()) {
- String received = (String) enum.nextElement();
-
- int nextSearchAt = 0;
- int i = 0;
- int start = 0;
- int end = 0;
- boolean hasBracket = false;
- boolean usableAddress = false;
- while (!usableAddress && (i != -1)) {
- hasBracket = false;
- i = received.indexOf("for ", nextSearchAt);
- if (i > 0) {
- start = i + 4;
- end = 0;
- nextSearchAt = start;
- for (int c = start; c < received.length(); c++) {
- char ch = received.charAt(c);
- switch (ch) {
- case '<':
- hasBracket = true;
- continue;
- case '@':
- usableAddress = true;
- continue;
- case ' ':
- end = c;
- break;
- case ';':
- end = c;
- break;
- }
- if (end > 0)
- break;
- }
- }
- }
- if (usableAddress) {
- // lets try and grab the email address
- String mailFor = received.substring(start, end);
-
- // strip the <> around the address if there are any
- if (mailFor.startsWith("<") && mailFor.endsWith(">"))
- mailFor = mailFor.substring(1, (mailFor.length() - 1));
+ * Returns the staticAccounts.
+ * @return List
+ */
+ private List getStaticAccountsBasic()
+ {
+ return fieldStaticAccounts;
+ }
- return mailFor;
- }
+ /**
+ * Sets the accounts.
+ * @param accounts The accounts to set
+ */
+ protected void setStaticAccounts(List accounts)
+ {
+ fieldStaticAccounts = accounts;
+ }
+
+ /**
+ * Updates the staticAccounts.
+ */
+ protected void updateStaticAccounts()
+ {
+ setStaticAccounts(computeStaticAccounts());
+ }
+
+ /**
+ * Updates the allLocalParameters.
+ */
+ protected void updateAllLocalParameters()
+ {
+ setParsedDynamicAccountParameters(computeAllLocalParameters());
+ }
+
+ /**
+ * Updates the dynamicAccounts.
+ */
+ protected void updateDynamicAccounts() throws ConfigurationException
+ {
+ setDynamicAccounts(computeDynamicAccounts());
+ }
+
+ /**
+ * Computes the staticAccounts.
+ */
+ protected List computeStaticAccounts()
+ {
+ return new ArrayList();
+ }
+
+ /**
+ * Computes the allLocalParameters.
+ */
+ protected List computeAllLocalParameters()
+ {
+ return new ArrayList();
+ }
+
+ /**
+ * Computes the dynamicAccounts.
+ */
+ protected List computeDynamicAccounts() throws ConfigurationException
+ {
+ List accounts = new ArrayList(32);
+ Iterator parameterIterator = getParsedDynamicAccountParameters().iterator();
+
+ // Process each ParsedDynamicParameters
+ while (parameterIterator.hasNext())
+ {
+ ParsedDynamicAccountParameters parameters =
+ (ParsedDynamicAccountParameters) parameterIterator.next();
+ // Create an Account for each local user
+ Iterator usersIterator = getLocalUsers().list();
+ while (usersIterator.hasNext())
+ {
+ String userName = (String) usersIterator.next();
+ StringBuffer userBuffer =
+ new StringBuffer(parameters.getUserPrefix());
+ userBuffer.append(userName);
+ userBuffer.append(parameters.getUserSuffix());
+ String user = userBuffer.toString();
+
+ StringBuffer recipientBuffer =
+ new StringBuffer(parameters.getRecipientPrefix());
+ recipientBuffer.append(userName);
+ recipientBuffer.append(parameters.getRecipientSuffix());
+ String recipient = recipientBuffer.toString();
+
+ accounts.add(
+ new Account(
+ parameters.getSequenceNumber(),
+ user,
+ parameters.getPassword(),
+ recipient,
+ parameters.isIgnoreRecipientHeader()));
}
- } catch (MessagingException me) {
- getLogger().info("No Recieved headers found");
}
- return null;
+ return accounts;
+ }
+
+ /**
+ * Returns the dynamicAccounts. Initializes if required.
+ * @return List
+ */
+ protected List getDynamicAccounts() throws ConfigurationException
+ {
+ List accounts = null;
+ if (null == (accounts = getDynamicAccountsBasic()))
+ {
+ updateDynamicAccounts();
+ return getDynamicAccounts();
+ }
+ return fieldDynamicAccounts;
}
+
+ /**
+ * Returns the dynamicAccounts.
+ * @return List
+ */
+ private List getDynamicAccountsBasic()
+ {
+ return fieldDynamicAccounts;
+ }
/**
- * @see
org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
+ * Sets the dynamicAccounts.
+ * @param dynamicAccounts The dynamicAccounts to set
*/
- public void configure(Configuration conf) throws ConfigurationException {
- this.sHost = conf.getChild("host").getValue();
- this.sUser = conf.getChild("user").getValue();
- this.sPass = conf.getChild("password").getValue();
- this.fetchTaskName = conf.getAttribute("name");
- this.javaMailProviderName =
conf.getChild("javaMailProviderName").getValue();
- this.javaMailFolderName = conf.getChild("javaMailFolderName").getValue();
- try {
- this.recipient = new MailAddress(conf.getChild("recipient").getValue());
- } catch (ParseException pe) {
- throw new ConfigurationException("Invalid recipient address specified");
- }
- this.ignoreOriginalRecipient =
conf.getChild("recipient").getAttributeAsBoolean("ignorercpt-header");
- this.bAll = conf.getChild("fetchall").getValueAsBoolean();
- this.bKeep = conf.getChild("leaveonserver").getValueAsBoolean();
- this.bRecurse = conf.getChild("recursesubfolders").getValueAsBoolean();
- if (getLogger().isDebugEnabled()) {
- getLogger().info("Configured FetchMail fetch task " + fetchTaskName);
- }
+ protected void setDynamicAccounts(List dynamicAccounts)
+ {
+ fieldDynamicAccounts = dynamicAccounts;
+ }
+
+ /**
+ * Resets the dynamicAccounts.
+ */
+ protected void resetDynamicAccounts()
+ {
+ setDynamicAccounts(null);
+ }
+
+ /**
+ * Returns the allLocalParameters.
+ * @return List
+ */
+ protected List getParsedDynamicAccountParameters()
+ {
+ List accounts = null;
+ if (null == (accounts = getAllLocalParametersBasic()))
+ {
+ updateAllLocalParameters();
+ return getParsedDynamicAccountParameters();
+ }
+ return fieldParsedDynamicAccountParameters;
}
+
+ /**
+ * Returns the allLocalParameters.
+ * @return List
+ */
+ private List getAllLocalParametersBasic()
+ {
+ return fieldParsedDynamicAccountParameters;
+ }
+
+ /**
+ * Sets the allLocalParameters.
+ * @param allLocalParameters The allLocalParameters to set
+ */
+ protected void setParsedDynamicAccountParameters(List allLocalParameters)
+ {
+ fieldParsedDynamicAccountParameters = allLocalParameters;
+ }
+
}
1.8.2.1 +96 -26
james-server/src/java/org/apache/james/fetchmail/FetchScheduler.java
Index: FetchScheduler.java
===================================================================
RCS file:
/home/cvs/james-server/src/java/org/apache/james/fetchmail/FetchScheduler.java,v
retrieving revision 1.8
retrieving revision 1.8.2.1
diff -u -r1.8 -r1.8.2.1
--- FetchScheduler.java 28 Apr 2003 12:03:26 -0000 1.8
+++ FetchScheduler.java 30 Aug 2003 18:52:18 -0000 1.8.2.1
@@ -56,6 +56,64 @@
* University of Illinois, Urbana-Champaign.
*/
+/* ====================================================================
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2000-2003 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache", "Jakarta", "JAMES" and "Apache Software Foundation"
+ * must not be used to endorse or promote products derived from this
+ * software without prior written permission. For written
+ * permission, please contact [EMAIL PROTECTED]
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ * nor may "Apache" appear in their name, without prior written
+ * permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ * Portions of this software are based upon public domain software
+ * originally written at the National Center for Supercomputing Applications,
+ * University of Illinois, Urbana-Champaign.
+ */
+
package org.apache.james.fetchmail;
import java.util.ArrayList;
@@ -78,7 +136,7 @@
*
* $Id$
*
- * @see org.apache.james.fetchmail.FetchMail#configure(Configuration) FetchMail
+ * @see org.apache.james.fetchmail.FetchMailOriginal#configure(Configuration)
FetchMailOriginal
*
*/
public class FetchScheduler
@@ -110,61 +168,73 @@
/**
* @see org.apache.avalon.framework.service.Serviceable#service( ServiceManager
)
*/
- public void service(ServiceManager comp) throws ServiceException {
+ public void service(ServiceManager comp) throws ServiceException
+ {
m_manager = comp;
}
/**
* @see
org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
*/
- public void configure(Configuration conf) throws ConfigurationException {
+ public void configure(Configuration conf) throws ConfigurationException
+ {
this.conf = conf;
}
/**
* @see org.apache.avalon.framework.activity.Initializable#initialize()
*/
- public void initialize() throws Exception {
+ public void initialize() throws Exception
+ {
enabled = conf.getAttributeAsBoolean("enabled", false);
- if (enabled) {
+ if (enabled)
+ {
scheduler = (TimeScheduler) m_manager.lookup(TimeScheduler.ROLE);
Configuration[] fetchConfs = conf.getChildren("fetch");
- for (int i = 0; i < fetchConfs.length; i++) {
+ for (int i = 0; i < fetchConfs.length; i++)
+ {
FetchMail fetcher = new FetchMail();
Configuration fetchConf = fetchConfs[i];
String fetchTaskName = fetchConf.getAttribute("name");
- fetcher.enableLogging(getLogger().getChildLogger(fetchTaskName));
- fetcher.service( m_manager );
+ fetcher.enableLogging(
+ getLogger().getChildLogger(fetchTaskName));
+ fetcher.service(m_manager);
fetcher.configure(fetchConf);
- Integer interval = new
Integer(fetchConf.getChild("interval").getValue());
- PeriodicTimeTrigger fetchTrigger = new PeriodicTimeTrigger(0,
interval.intValue());
+ Integer interval =
+ new Integer(fetchConf.getChild("interval").getValue());
+ PeriodicTimeTrigger fetchTrigger =
+ new PeriodicTimeTrigger(0, interval.intValue());
- scheduler.addTrigger(fetchTaskName, fetchTrigger, fetcher );
+ scheduler.addTrigger(fetchTaskName, fetchTrigger, fetcher);
theFetchTaskNames.add(fetchTaskName);
}
- if( getLogger().isInfoEnabled() )
- {
+
+ if (getLogger().isInfoEnabled())
getLogger().info("FetchMail Started");
- }
- } else {
- if( getLogger().isInfoEnabled() )
- {
+ System.out.println("FetchMail Started");
+ }
+ else
+ {
+ if (getLogger().isInfoEnabled())
getLogger().info("FetchMail Disabled");
- }
+ System.out.println("FetchMail Disabled");
}
}
/**
* @see org.apache.avalon.framework.activity.Disposable#dispose()
*/
- public void dispose() {
- if (enabled) {
- getLogger().info( "FetchMail dispose..." );
+ public void dispose()
+ {
+ if (enabled)
+ {
+ getLogger().info("FetchMail dispose...");
Iterator nameIterator = theFetchTaskNames.iterator();
- while (nameIterator.hasNext()) {
- scheduler.removeTrigger((String)nameIterator.next());
+ while (nameIterator.hasNext())
+ {
+ scheduler.removeTrigger((String) nameIterator.next());
}
- getLogger().info( "FetchMail ...dispose end" );
+ getLogger().info("FetchMail ...dispose end");
}
- }
+ }
}
1.3.2.1 +3 -0
james-server/src/java/org/apache/james/fetchmail/FetchScheduler.xinfo
Index: FetchScheduler.xinfo
===================================================================
RCS file:
/home/cvs/james-server/src/java/org/apache/james/fetchmail/FetchScheduler.xinfo,v
retrieving revision 1.3
retrieving revision 1.3.2.1
diff -u -r1.3 -r1.3.2.1
--- FetchScheduler.xinfo 8 Feb 2003 04:12:25 -0000 1.3
+++ FetchScheduler.xinfo 30 Aug 2003 18:52:18 -0000 1.3.2.1
@@ -14,6 +14,9 @@
<dependency>
<service name="org.apache.james.services.MailServer" version="1.0"/>
</dependency>
+ <dependency>
+ <service name="org.apache.james.services.UsersStore" version="1.0"/>
+ </dependency>
<dependency>
<service
name="org.apache.avalon.cornerstone.services.scheduler.TimeScheduler" version="1.0"/>
</dependency>
1.1.2.1 +241 -0
james-server/src/java/org/apache/james/fetchmail/Attic/FolderProcessor.java
1.1.2.1 +850 -0
james-server/src/java/org/apache/james/fetchmail/Attic/MessageProcessor.java
1.1.2.1 +1071 -0
james-server/src/java/org/apache/james/fetchmail/Attic/ParsedConfiguration.java
1.1.2.1 +514 -0
james-server/src/java/org/apache/james/fetchmail/Attic/ProcessorAbstract.java
1.4.2.1 +1 -1
james-server/src/java/org/apache/james/fetchmail/ReaderInputStream.java
Index: ReaderInputStream.java
===================================================================
RCS file:
/home/cvs/james-server/src/java/org/apache/james/fetchmail/ReaderInputStream.java,v
retrieving revision 1.4
retrieving revision 1.4.2.1
diff -u -r1.4 -r1.4.2.1
1.1.2.1 +158 -0
james-server/src/java/org/apache/james/fetchmail/Attic/StoreProcessor.java
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]