Index: default.properties =================================================================== --- default.properties (revision 327257) +++ default.properties (working copy) @@ -50,6 +50,9 @@ build.lib = ${build.dir}/lib build.src = ${build.dir}/src build.classes = ${build.dir}/classes +build.test = ${build.dir}/test +build.test.classes = ${build.test}/classes +build.test.reports = ${build.test}/reports build.javadocs = ${build.docs}/javadocs build.docs = ${build.dir}/docs build.mailetdocs = ${build.dir}/mailetdocs @@ -62,6 +65,7 @@ src.dir=${james.dir}/src java.dir=${src.dir}/java +junitjava.dir=${src.dir}/test conf.dir=${src.dir}/conf xdocs.dir=${src.dir}/xdocs docs.src=${xdocs.dir} @@ -83,3 +87,7 @@ www.dir = ${james.dir}/www +# +# +# + Index: tools/lib/junit.jar =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: tools/lib/junit.jar ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Index: tools/lib/ristretto-1.0-all.jar =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: tools/lib/ristretto-1.0-all.jar ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Index: tools/lib/LICENSE.ristretto.txt =================================================================== --- tools/lib/LICENSE.ristretto.txt (revision 0) +++ tools/lib/LICENSE.ristretto.txt (revision 0) @@ -0,0 +1,35 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ristretto Mail API. + * + * The Initial Developers of the Original Code are + * Timo Stich and Frederik Dietz. + * Portions created by the Initial Developers are Copyright (C) 2004 + * All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ \ No newline at end of file Index: src/test/org/apache/james/test/mock/james/MockMailServer.java =================================================================== --- src/test/org/apache/james/test/mock/james/MockMailServer.java (revision 0) +++ src/test/org/apache/james/test/mock/james/MockMailServer.java (revision 0) @@ -0,0 +1,110 @@ +/*********************************************************************** + * Copyright (c) 2000-2005 The Apache Software Foundation. * + * All rights reserved. * + * ------------------------------------------------------------------- * + * Licensed under the Apache License, Version 2.0 (the "License"); you * + * may not use this file except in compliance with the License. You * + * may obtain a copy of the License at: * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * + * implied. See the License for the specific language governing * + * permissions and limitations under the License. * + ***********************************************************************/ +package org.apache.james.test.mock.james; + +import org.apache.james.services.MailRepository; +import org.apache.james.services.MailServer; +import org.apache.james.smtpserver.MessageSizeException; +import org.apache.mailet.Mail; +import org.apache.mailet.MailAddress; + +import javax.mail.Address; +import javax.mail.MessagingException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; + +public class MockMailServer implements MailServer { + + private final HashMap m_users = new HashMap(); + + private int m_counter = 0; + private int m_maxMessageSizeBytes = 0; + + private final ArrayList mails = new ArrayList(); + + public void sendMail(MailAddress sender, Collection recipients, MimeMessage msg) throws MessagingException { + Object[] mailObjects = new Object[]{sender, recipients, msg}; + mails.add(mailObjects); + } + + public void sendMail(MailAddress sender, Collection recipients, InputStream msg) throws MessagingException { + Object[] mailObjects = new Object[]{sender, recipients, msg}; + mails.add(mailObjects); + } + + public void sendMail(Mail mail) throws MessagingException { + int bodySize = mail.getMessage().getSize(); + try { + if (m_maxMessageSizeBytes != 0 && m_maxMessageSizeBytes < bodySize) throw new MessageSizeException(); + } catch (MessageSizeException e) { + throw new MessagingException("message size exception is nested", e); + } + sendMail(mail.getSender(), mail.getRecipients(), mail.getMessage()); + } + + public void sendMail(MimeMessage message) throws MessagingException { + // taken from class org.apache.james.James + MailAddress sender = new MailAddress((InternetAddress)message.getFrom()[0]); + Collection recipients = new HashSet(); + Address addresses[] = message.getAllRecipients(); + if (addresses != null) { + for (int i = 0; i < addresses.length; i++) { + // Javamail treats the "newsgroups:" header field as a + // recipient, so we want to filter those out. + if ( addresses[i] instanceof InternetAddress ) { + recipients.add(new MailAddress((InternetAddress)addresses[i])); + } + } + } + sendMail(sender, recipients, message); + } + + public MailRepository getUserInbox(String userName) { + return null; // trivial implementation + } + + public synchronized String getId() { + m_counter++; + return "MockMailServer-ID-" + m_counter; + } + + public boolean addUser(String userName, String password) { + m_users.put(userName, password); + return true; + } + + public boolean isLocalServer(String serverName) { + return "localhost".equals(serverName); + } + + public Object[] getLastMail() + { + if (mails.size() == 0) return null; + return (Object[])mails.get(mails.size()-1); + } + + public void setMaxMessageSizeBytes(int maxMessageSizeBytes) { + m_maxMessageSizeBytes = maxMessageSizeBytes; + } +} + + Index: src/test/org/apache/james/test/mock/james/MockUserRepository.java =================================================================== --- src/test/org/apache/james/test/mock/james/MockUserRepository.java (revision 0) +++ src/test/org/apache/james/test/mock/james/MockUserRepository.java (revision 0) @@ -0,0 +1,94 @@ +/*********************************************************************** + * Copyright (c) 2000-2005 The Apache Software Foundation. * + * All rights reserved. * + * ------------------------------------------------------------------- * + * Licensed under the Apache License, Version 2.0 (the "License"); you * + * may not use this file except in compliance with the License. You * + * may obtain a copy of the License at: * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * + * implied. See the License for the specific language governing * + * permissions and limitations under the License. * + ***********************************************************************/ +package org.apache.james.test.mock.james; + +import org.apache.james.services.User; +import org.apache.james.services.UsersRepository; +import org.apache.james.userrepository.DefaultUser; +import org.apache.james.util.Base64; +import org.apache.james.security.DigestUtil; + +import java.util.HashMap; +import java.util.Iterator; + +public class MockUserRepository implements UsersRepository { + + private final HashMap m_users = new HashMap(); + + public boolean addUser(User user) { + m_users.put(user.getUserName(), user); + return true; + } + + public void addUser(String name, Object attributes) { + try { + addUser(new DefaultUser(name, DigestUtil.digestString(((String)attributes), "SHA"), "SHA")); + } catch (Exception e) { + e.printStackTrace(); // encoding failed + } + } + + public Object getAttributes(String name) { + return null; // trivial implementation + } + + public User getUserByName(String name) { + return (User) m_users.get(name); + } + + public User getUserByNameCaseInsensitive(String name) { + return null; // trivial implementation + } + + public String getRealName(String name) { + return ((User) m_users.get(name)).getUserName(); + } + + public boolean updateUser(User user) { + return false; // trivial implementation + } + + public void removeUser(String name) { + // trivial implementation + } + + public boolean contains(String name) { + return m_users.containsKey(name); + } + + public boolean containsCaseInsensitive(String name) { + return false; // trivial implementation + } + + public boolean test(String name, Object attributes) { + return false; // trivial implementation + } + + public boolean test(String name, String password) { + User user = getUserByName(name); + if (user == null) return false; + return user.verifyPassword(password); + } + + public int countUsers() { + return m_users.size(); + } + + public Iterator list() { + return m_users.values().iterator(); + } +} Index: src/test/org/apache/james/test/mock/avalon/MockServiceManager.java =================================================================== --- src/test/org/apache/james/test/mock/avalon/MockServiceManager.java (revision 0) +++ src/test/org/apache/james/test/mock/avalon/MockServiceManager.java (revision 0) @@ -0,0 +1,42 @@ +/*********************************************************************** + * Copyright (c) 1999-2005 The Apache Software Foundation. * + * All rights reserved. * + * ------------------------------------------------------------------- * + * Licensed under the Apache License, Version 2.0 (the "License"); you * + * may not use this file except in compliance with the License. You * + * may obtain a copy of the License at: * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * + * implied. See the License for the specific language governing * + * permissions and limitations under the License. * + ***********************************************************************/ +package org.apache.james.test.mock.avalon; + +import org.apache.avalon.framework.service.ServiceException; + +import java.util.HashMap; + +public class MockServiceManager implements org.apache.avalon.framework.service.ServiceManager { + + private HashMap m_serviceMap = new HashMap(); + + public Object lookup(String serviceName) throws ServiceException { + return m_serviceMap.get(serviceName); + } + + public boolean hasService(String serviceName) { + return m_serviceMap.get(serviceName) != null; + } + + public void put(String name, Object service) { + m_serviceMap.put(name, service); + } + + public void release(Object object) { + // trivial implementation + } +} Index: src/test/org/apache/james/test/mock/avalon/MockLogger.java =================================================================== --- src/test/org/apache/james/test/mock/avalon/MockLogger.java (revision 0) +++ src/test/org/apache/james/test/mock/avalon/MockLogger.java (revision 0) @@ -0,0 +1,87 @@ +/*********************************************************************** + * Copyright (c) 1999-2005 The Apache Software Foundation. * + * All rights reserved. * + * ------------------------------------------------------------------- * + * Licensed under the Apache License, Version 2.0 (the "License"); you * + * may not use this file except in compliance with the License. You * + * may obtain a copy of the License at: * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * + * implied. See the License for the specific language governing * + * permissions and limitations under the License. * + ***********************************************************************/ +package org.apache.james.test.mock.avalon; + +import org.apache.avalon.framework.logger.Logger; + +public class MockLogger implements Logger { + + public void debug(java.lang.String string) { + System.out.println(string); + } + + public void debug(java.lang.String string, java.lang.Throwable throwable) { + System.out.println(string + throwable.toString()); + } + + public boolean isDebugEnabled() { + return true; + } + + public void info(java.lang.String string) { + System.out.println(string); + } + + public void info(java.lang.String string, java.lang.Throwable throwable) { + System.out.println(string + throwable.toString()); + } + + public boolean isInfoEnabled() { + return true; + } + + public void warn(java.lang.String string) { + System.out.println(string); + } + + public void warn(java.lang.String string, java.lang.Throwable throwable) { + System.out.println(string + throwable.toString()); + } + + public boolean isWarnEnabled() { + return true; + } + + public void error(java.lang.String string) { + System.out.println(string); + } + + public void error(java.lang.String string, java.lang.Throwable throwable) { + System.out.println(string + throwable.toString()); + } + + public boolean isErrorEnabled() { + return true; + } + + public void fatalError(java.lang.String string) { + System.out.println(string); + } + + public void fatalError(java.lang.String string, java.lang.Throwable throwable) { + System.out.println(string + throwable.toString()); + } + + public boolean isFatalErrorEnabled() { + return true; + } + + public org.apache.avalon.framework.logger.Logger getChildLogger(java.lang.String string) { + return this; + } + +} Index: src/test/org/apache/james/test/mock/avalon/MockThreadManager.java =================================================================== --- src/test/org/apache/james/test/mock/avalon/MockThreadManager.java (revision 0) +++ src/test/org/apache/james/test/mock/avalon/MockThreadManager.java (revision 0) @@ -0,0 +1,38 @@ +/*********************************************************************** + * Copyright (c) 2000-2005 The Apache Software Foundation. * + * All rights reserved. * + * ------------------------------------------------------------------- * + * Licensed under the Apache License, Version 2.0 (the "License"); you * + * may not use this file except in compliance with the License. You * + * may obtain a copy of the License at: * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * + * implied. See the License for the specific language governing * + * permissions and limitations under the License. * + ***********************************************************************/ +package org.apache.james.test.mock.avalon; + +import org.apache.avalon.cornerstone.services.threads.ThreadManager; +import org.apache.avalon.excalibur.thread.impl.DefaultThreadPool; +import org.apache.excalibur.thread.ThreadPool; + +public class MockThreadManager implements ThreadManager { + public ThreadPool getThreadPool(String string) throws IllegalArgumentException { + return getDefaultThreadPool(); + } + + public ThreadPool getDefaultThreadPool() { + try { + DefaultThreadPool defaultThreadPool = new DefaultThreadPool(1); + defaultThreadPool.enableLogging(new MockLogger()); + return defaultThreadPool; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} Index: src/test/org/apache/james/test/mock/avalon/MockSocketManager.java =================================================================== --- src/test/org/apache/james/test/mock/avalon/MockSocketManager.java (revision 0) +++ src/test/org/apache/james/test/mock/avalon/MockSocketManager.java (revision 0) @@ -0,0 +1,40 @@ +/*********************************************************************** + * Copyright (c) 2000-2005 The Apache Software Foundation. * + * All rights reserved. * + * ------------------------------------------------------------------- * + * Licensed under the Apache License, Version 2.0 (the "License"); you * + * may not use this file except in compliance with the License. You * + * may obtain a copy of the License at: * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * + * implied. See the License for the specific language governing * + * permissions and limitations under the License. * + ***********************************************************************/ +package org.apache.james.test.mock.avalon; + +import org.apache.avalon.cornerstone.services.sockets.SocketManager; +import org.apache.avalon.cornerstone.services.sockets.ServerSocketFactory; +import org.apache.avalon.cornerstone.services.sockets.SocketFactory; +import org.apache.avalon.cornerstone.blocks.sockets.DefaultServerSocketFactory; +import org.apache.avalon.cornerstone.blocks.sockets.DefaultSocketFactory; + +public class MockSocketManager implements SocketManager { + private int m_port; + + public MockSocketManager(int port) + { + m_port = port; + } + + public ServerSocketFactory getServerSocketFactory(String string) throws Exception { + return new DefaultServerSocketFactory(); + } + + public SocketFactory getSocketFactory(String string) throws Exception { + return new DefaultSocketFactory(); + } +} Index: src/test/org/apache/james/test/mock/mailet/MockMailContext.java =================================================================== --- src/test/org/apache/james/test/mock/mailet/MockMailContext.java (revision 0) +++ src/test/org/apache/james/test/mock/mailet/MockMailContext.java (revision 0) @@ -0,0 +1,113 @@ +/*********************************************************************** + * Copyright (c) 1999-2005 The Apache Software Foundation. * + * All rights reserved. * + * ------------------------------------------------------------------- * + * Licensed under the Apache License, Version 2.0 (the "License"); you * + * may not use this file except in compliance with the License. You * + * may obtain a copy of the License at: * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * + * implied. See the License for the specific language governing * + * permissions and limitations under the License. * + ***********************************************************************/ +package org.apache.james.test.mock.mailet; + +import org.apache.mailet.MailetContext; +import org.apache.mailet.Mail; +import org.apache.mailet.MailAddress; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import java.util.Collection; +import java.util.Iterator; + +public class MockMailContext implements MailetContext { + + public void bounce(Mail mail, String message) throws MessagingException { + // trivial implementation + } + + public void bounce(Mail mail, String message, MailAddress bouncer) throws MessagingException { + // trivial implementation + } + + public Collection getMailServers(String host) { + return null; // trivial implementation + } + + public MailAddress getPostmaster() { + return null; // trivial implementation + } + + public Object getAttribute(String name) { + return null; // trivial implementation + } + + public Iterator getAttributeNames() { + return null; // trivial implementation + } + + public int getMajorVersion() { + return 0; // trivial implementation + } + + public int getMinorVersion() { + return 0; // trivial implementation + } + + public String getServerInfo() { + return null; // trivial implementation + } + + public boolean isLocalServer(String serverName) { + return false; // trivial implementation + } + + public boolean isLocalUser(String userAccount) { + return false; // trivial implementation + } + + public void log(String message) { + // trivial implementation + } + + public void log(String message, Throwable t) { + // trivial implementation + } + + public void removeAttribute(String name) { + // trivial implementation + } + + public void sendMail(MimeMessage msg) throws MessagingException { + // trivial implementation + } + + public void sendMail(MailAddress sender, Collection recipients, MimeMessage msg) throws MessagingException { + // trivial implementation + } + + public void sendMail(MailAddress sender, Collection recipients, MimeMessage msg, String state) throws MessagingException { + // trivial implementation + } + + public void sendMail(Mail mail) throws MessagingException { + // trivial implementation + } + + public void setAttribute(String name, Object object) { + // trivial implementation + } + + public void storeMail(MailAddress sender, MailAddress recipient, MimeMessage msg) throws MessagingException { + // trivial implementation + } + + public Iterator getSMTPHostAddresses(String domainName) { + return null; // trivial implementation + } +} Index: src/test/org/apache/james/test/util/Util.java =================================================================== --- src/test/org/apache/james/test/util/Util.java (revision 0) +++ src/test/org/apache/james/test/util/Util.java (revision 0) @@ -0,0 +1,33 @@ +/*********************************************************************** + * Copyright (c) 1999-2005 The Apache Software Foundation. * + * All rights reserved. * + * ------------------------------------------------------------------- * + * Licensed under the Apache License, Version 2.0 (the "License"); you * + * may not use this file except in compliance with the License. You * + * may obtain a copy of the License at: * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * + * implied. See the License for the specific language governing * + * permissions and limitations under the License. * + ***********************************************************************/ +package org.apache.james.test.util; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.DefaultConfiguration; + +public class Util { + + public static int getRandomNonPrivilegedPort() { + return ((int)( Math.random() * 1000) + 3000); + } + + public static Configuration getValuedConfiguration(String name, String value) { + DefaultConfiguration defaultConfiguration = new DefaultConfiguration(name); + defaultConfiguration.setValue(value); + return defaultConfiguration; + } +} Index: src/test/org/apache/james/smtpserver/SMTPServerTest.java =================================================================== --- src/test/org/apache/james/smtpserver/SMTPServerTest.java (revision 0) +++ src/test/org/apache/james/smtpserver/SMTPServerTest.java (revision 0) @@ -0,0 +1,335 @@ +/*********************************************************************** + * Copyright (c) 1999-2005 The Apache Software Foundation. * + * All rights reserved. * + * ------------------------------------------------------------------- * + * Licensed under the Apache License, Version 2.0 (the "License"); you * + * may not use this file except in compliance with the License. You * + * may obtain a copy of the License at: * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * + * implied. See the License for the specific language governing * + * permissions and limitations under the License. * + ***********************************************************************/ +package org.apache.james.smtpserver; + +import junit.framework.TestCase; +import org.apache.avalon.cornerstone.services.sockets.SocketManager; +import org.apache.avalon.cornerstone.services.threads.ThreadManager; +import org.apache.james.services.JamesConnectionManager; +import org.apache.james.test.mock.avalon.MockLogger; +import org.apache.james.test.mock.avalon.MockServiceManager; +import org.apache.james.test.mock.avalon.MockSocketManager; +import org.apache.james.test.mock.avalon.MockThreadManager; +import org.apache.james.test.mock.james.MockMailServer; +import org.apache.james.test.mock.james.MockUserRepository; +import org.apache.james.test.mock.mailet.MockMailContext; +import org.apache.james.test.util.Util; +import org.apache.james.util.Base64; +import org.apache.james.util.connection.SimpleConnectionManager; +import org.columba.ristretto.composer.MimeTreeRenderer; +import org.columba.ristretto.io.CharSequenceSource; +import org.columba.ristretto.message.*; +import org.columba.ristretto.smtp.SMTPException; +import org.columba.ristretto.smtp.SMTPProtocol; +import org.columba.ristretto.smtp.SMTPResponse; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.Arrays; +import java.util.List; + +/** + * Tests the org.apache.james.smtpserver.SMTPServer unit + */ +public class SMTPServerTest extends TestCase { + private int m_smtpListenerPort = Util.getRandomNonPrivilegedPort(); + private MockMailServer m_mailServer; + private SMTPTestConfiguration m_testConfiguration; + private SMTPServer m_smtpServer; + private MockUserRepository m_userRepository = new MockUserRepository(); + + public SMTPServerTest() { + super("SMTPServerTest"); + } + + protected void setUp() throws Exception { + m_smtpServer = new SMTPServer(); + m_smtpServer.enableLogging(new MockLogger()); + + m_smtpServer.service(setUpServiceManager()); + m_testConfiguration = new SMTPTestConfiguration(m_smtpListenerPort); + } + + private void finishSetUp(SMTPTestConfiguration testConfiguration) throws Exception { + testConfiguration.init(); + m_smtpServer.configure(testConfiguration); + m_smtpServer.initialize(); + m_mailServer.setMaxMessageSizeBytes(m_testConfiguration.getMaxMessageSize()); + } + + private MockServiceManager setUpServiceManager() { + MockServiceManager serviceManager = new MockServiceManager(); + SimpleConnectionManager connectionManager = new SimpleConnectionManager(); + connectionManager.enableLogging(new MockLogger()); + serviceManager.put(JamesConnectionManager.ROLE, connectionManager); + serviceManager.put("org.apache.mailet.MailetContext", new MockMailContext()); + m_mailServer = new MockMailServer(); + serviceManager.put("org.apache.james.services.MailServer", m_mailServer); + serviceManager.put("org.apache.james.services.UsersRepository", m_userRepository); + serviceManager.put(SocketManager.ROLE, new MockSocketManager(m_smtpListenerPort)); + serviceManager.put(ThreadManager.ROLE, new MockThreadManager()); + return serviceManager; + } + + public void testSimpleMailSendWithEHLO() throws Exception, SMTPException { + finishSetUp(m_testConfiguration); + + SMTPProtocol smtpProtocol = new SMTPProtocol("127.0.0.1", m_smtpListenerPort); + smtpProtocol.openPort(); + + // no message there, yet + assertNull("no mail received by mail server", m_mailServer.getLastMail()); + + String[] capabilityStrings = smtpProtocol.ehlo(InetAddress.getLocalHost()); + assertEquals("capabilities", 3, capabilityStrings.length); + List capabilitieslist = Arrays.asList(capabilityStrings); + assertTrue("capabilities present PIPELINING", capabilitieslist.contains("PIPELINING")); + assertTrue("capabilities present ENHANCEDSTATUSCODES", capabilitieslist.contains("ENHANCEDSTATUSCODES")); + assertTrue("capabilities present 8BITMIME", capabilitieslist.contains("8BITMIME")); + + smtpProtocol.mail(new Address("mail@localhost")); + smtpProtocol.rcpt(new Address("mail@localhost")); + + smtpProtocol.data(MimeTreeRenderer.getInstance().renderMimePart(createMail())); + + smtpProtocol.quit(); + + // mail was propagated by SMTPServer + assertNotNull("mail received by mail server", m_mailServer.getLastMail()); + } + + public void testSimpleMailSendWithHELO() throws Exception, SMTPException { + finishSetUp(m_testConfiguration); + + SMTPProtocol smtpProtocol = new SMTPProtocol("127.0.0.1", m_smtpListenerPort); + smtpProtocol.openPort(); + + // no message there, yet + assertNull("no mail received by mail server", m_mailServer.getLastMail()); + + smtpProtocol.helo(InetAddress.getLocalHost()); + + smtpProtocol.mail(new Address("mail@localhost")); + smtpProtocol.rcpt(new Address("mail@localhost")); + + smtpProtocol.data(MimeTreeRenderer.getInstance().renderMimePart(createMail())); + + smtpProtocol.quit(); + + // mail was propagated by SMTPServer + assertNotNull("mail received by mail server", m_mailServer.getLastMail()); + } + + private LocalMimePart createMail() { + MimeHeader mimeHeader = new MimeHeader(new Header()); + mimeHeader.set("Mime-Version", "1.0"); + LocalMimePart mail = new LocalMimePart(mimeHeader); + MimeHeader header = mail.getHeader(); + header.setMimeType(new MimeType("text", "plain")); + + mail.setBody(new CharSequenceSource("James Unit Test Body")); + return mail; + } + + public void testAuth() throws Exception, SMTPException { + m_testConfiguration.setAuthorizedAddresses("128.0.0.1/8"); + m_testConfiguration.setAuthorizingAnnounce(); + finishSetUp(m_testConfiguration); + + MySMTPProtocol smtpProtocol = new MySMTPProtocol("127.0.0.1", m_smtpListenerPort); + smtpProtocol.openPort(); + + String[] capabilityStrings = smtpProtocol.ehlo(InetAddress.getLocalHost()); + List capabilitieslist = Arrays.asList(capabilityStrings); + assertTrue("anouncing auth required", capabilitieslist.contains("AUTH LOGIN PLAIN")); + // is this required or just for compatibility? assertTrue("anouncing auth required", capabilitieslist.contains("AUTH=LOGIN PLAIN")); + + String userName = "test_user_smtp"; + String noexistUserName = "noexist_test_user_smtp"; + + smtpProtocol.sendCommand("AUTH FOO", null); + SMTPResponse response = smtpProtocol.getResponse(); + assertEquals("expected error: unrecognized authentication type", 504, response.getCode()); + + smtpProtocol.mail(new Address(userName)); + + try { + smtpProtocol.rcpt(new Address("mail@sample.com")); + fail("no auth required"); + } catch (SMTPException e) { + assertEquals("expected 530 error", 530, e.getCode()); + } + + assertFalse("user not existing", m_userRepository.contains(noexistUserName)); + try { + smtpProtocol.auth("PLAIN", noexistUserName, "pwd".toCharArray()); + fail("auth succeeded for non-existing user"); + } catch (SMTPException e) { + assertEquals("expected error", 535, e.getCode()); + } + + m_userRepository.addUser(userName, "pwd"); + try { + smtpProtocol.auth("PLAIN", userName, "wrongpwd".toCharArray()); + fail("auth succeeded with wrong password"); + } catch (SMTPException e) { + assertEquals("expected error", 535, e.getCode()); + } + + try { + smtpProtocol.auth("PLAIN", userName, "pwd".toCharArray()); + } catch (SMTPException e) { + e.printStackTrace(); + fail("authentication failed"); + } + + smtpProtocol.sendCommand("AUTH PLAIN ", new String[]{Base64.encodeAsString("\0" + userName + "\0pwd")}); + response = smtpProtocol.getResponse(); + assertEquals("expected error: User has previously authenticated.", 503, response.getCode()); + + smtpProtocol.rcpt(new Address("mail@sample.com")); + smtpProtocol.data(MimeTreeRenderer.getInstance().renderMimePart(createMail())); + + smtpProtocol.quit(); + + // mail was propagated by SMTPServer + assertNotNull("mail received by mail server", m_mailServer.getLastMail()); + } + + public void testNoRecepientSpecified() throws Exception, SMTPException { + finishSetUp(m_testConfiguration); + + MySMTPProtocol smtpProtocol = new MySMTPProtocol("127.0.0.1", m_smtpListenerPort); + smtpProtocol.openPort(); + + smtpProtocol.ehlo(InetAddress.getLocalHost()); + + smtpProtocol.mail(new Address("mail@sample.com")); + + // left out for test smtpProtocol.rcpt(new Address("mail@localhost")); + + try { + smtpProtocol.data(MimeTreeRenderer.getInstance().renderMimePart(createMail())); + fail("sending succeeded without recepient"); + } catch (Exception e) { + // test succeeded + } + + smtpProtocol.quit(); + + // mail was propagated by SMTPServer + assertNull("no mail received by mail server", m_mailServer.getLastMail()); + } + + public void testRelayingDenied() throws Exception, SMTPException { + m_testConfiguration.setAuthorizedAddresses("128.0.0.1/8"); + finishSetUp(m_testConfiguration); + + SMTPProtocol smtpProtocol = new SMTPProtocol("127.0.0.1", m_smtpListenerPort); + smtpProtocol.openPort(); + + smtpProtocol.ehlo(InetAddress.getLocalHost()); + + smtpProtocol.mail(new Address("mail@sample.com")); + try { + smtpProtocol.rcpt(new Address("maila@sample.com")); + fail("relaying allowed"); + } catch (SMTPException e) { + assertEquals("expected 550 error", 550, e.getCode()); + } + } + + public void testHandleAnnouncedMessageSizeLimitExceeded() throws Exception, SMTPException { + m_testConfiguration.setMaxMessageSize(1); // set message limit to 1kb + finishSetUp(m_testConfiguration); + + MySMTPProtocol smtpProtocol = new MySMTPProtocol("127.0.0.1", m_smtpListenerPort); + smtpProtocol.openPort(); + + smtpProtocol.ehlo(InetAddress.getLocalHost()); + + smtpProtocol.sendCommand("MAIL FROM: SIZE=1025", null); + SMTPResponse response = smtpProtocol.getResponse(); + assertEquals("expected error: max msg size exceeded", 552, response.getCode()); + + smtpProtocol.rcpt(new Address("mail@localhost")); + } + + public void testHandleMessageSizeLimitExceeded() throws Exception, SMTPException { + m_testConfiguration.setMaxMessageSize(1); // set message limit to 1kb + finishSetUp(m_testConfiguration); + + MySMTPProtocol smtpProtocol = new MySMTPProtocol("127.0.0.1", m_smtpListenerPort); + smtpProtocol.openPort(); + + smtpProtocol.ehlo(InetAddress.getLocalHost()); + + smtpProtocol.mail(new Address("mail@localhost")); + smtpProtocol.rcpt(new Address("mail@localhost")); + + MimeHeader mimeHeader = new MimeHeader(new Header()); + mimeHeader.set("Mime-Version", "1.0"); + LocalMimePart mail = new LocalMimePart(mimeHeader); + MimeHeader header = mail.getHeader(); + header.setMimeType(new MimeType("text", "plain")); + + // create Body with more than 1kb + StringBuffer body = new StringBuffer(); + body.append("1234567810123456782012345678301234567840123456785012345678601234567870123456788012345678901234567100"); + body.append("1234567810123456782012345678301234567840123456785012345678601234567870123456788012345678901234567100"); + body.append("1234567810123456782012345678301234567840123456785012345678601234567870123456788012345678901234567100"); + body.append("1234567810123456782012345678301234567840123456785012345678601234567870123456788012345678901234567100"); + body.append("1234567810123456782012345678301234567840123456785012345678601234567870123456788012345678901234567100"); + body.append("1234567810123456782012345678301234567840123456785012345678601234567870123456788012345678901234567100"); + body.append("1234567810123456782012345678301234567840123456785012345678601234567870123456788012345678901234567100"); + body.append("1234567810123456782012345678301234567840123456785012345678601234567870123456788012345678901234567100"); + body.append("1234567810123456782012345678301234567840123456785012345678601234567870123456788012345678901234567100"); + body.append("1234567810123456782012345678301234567840123456785012345678601234567870123456788012345678901234567100"); + body.append("1234567810123456782012345678301234567840123456785012345678601234567870123456788012345678901234567100"); + body.append("1234567810123456782012345"); // 1025 chars + + mail.setBody(new CharSequenceSource(body.toString())); + + try { + smtpProtocol.data(MimeTreeRenderer.getInstance().renderMimePart(mail)); + fail("message size exceeded not recognized"); + } catch (SMTPException e) { + assertEquals("expected 552 error", 552, e.getCode()); + } + + } +} + +class MySMTPProtocol extends SMTPProtocol +{ + + public MySMTPProtocol(String s, int i) { + super(s, i); + } + + public MySMTPProtocol(String s) { + super(s); + } + + public void sendCommand(String string, String[] strings) throws IOException { + super.sendCommand(string, strings); + } + + public SMTPResponse getResponse() throws IOException, SMTPException { + return super.readSingleLineResponse(); + } +} Index: src/test/org/apache/james/smtpserver/SMTPTestConfiguration.java =================================================================== --- src/test/org/apache/james/smtpserver/SMTPTestConfiguration.java (revision 0) +++ src/test/org/apache/james/smtpserver/SMTPTestConfiguration.java (revision 0) @@ -0,0 +1,109 @@ +/*********************************************************************** + * Copyright (c) 2000-2005 The Apache Software Foundation. * + * All rights reserved. * + * ------------------------------------------------------------------- * + * Licensed under the Apache License, Version 2.0 (the "License"); you * + * may not use this file except in compliance with the License. You * + * may obtain a copy of the License at: * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * + * implied. See the License for the specific language governing * + * permissions and limitations under the License. * + ***********************************************************************/ + + +package org.apache.james.smtpserver; + +import org.apache.avalon.framework.configuration.DefaultConfiguration; +import org.apache.james.test.util.Util; + +public class SMTPTestConfiguration extends DefaultConfiguration { + + private int m_smtpListenerPort; + private int m_maxMessageSize = 0; + private String m_authorizedAddresses = "127.0.0.0/8"; + private String m_authorizingMode = "false"; + private boolean m_verifyIdentity = false; + + public SMTPTestConfiguration(int smtpListenerPort) { + super("smptserver"); + + m_smtpListenerPort = smtpListenerPort; + } + + public void setMaxMessageSize(int kilobytes) + { + m_maxMessageSize = kilobytes; + } + + public int getMaxMessageSize() { + return m_maxMessageSize; + } + + public String getAuthorizedAddresses() { + return m_authorizedAddresses; + } + + public void setAuthorizedAddresses(String authorizedAddresses) { + m_authorizedAddresses = authorizedAddresses; + } + + public void setAuthorizingNotRequired() { + m_authorizingMode = "false"; + m_verifyIdentity = false; + } + + public void setAuthorizingRequired() { + m_authorizingMode = "true"; + m_verifyIdentity = true; + } + + public void setAuthorizingAnnounce() { + m_authorizingMode = "announce"; + m_verifyIdentity = true; + } + + public void init() { + + setAttribute("enabled", true); + + addChild(Util.getValuedConfiguration("port", "" + m_smtpListenerPort)); + + DefaultConfiguration handlerConfig = new DefaultConfiguration("handler"); + handlerConfig.addChild(Util.getValuedConfiguration("helloName", "myMailServer")); + handlerConfig.addChild(Util.getValuedConfiguration("connectiontimeout", "360000")); + handlerConfig.addChild(Util.getValuedConfiguration("authorizedAddresses", m_authorizedAddresses)); + handlerConfig.addChild(Util.getValuedConfiguration("maxmessagesize", "" + m_maxMessageSize)); + handlerConfig.addChild(Util.getValuedConfiguration("authRequired", m_authorizingMode)); + if (m_verifyIdentity) handlerConfig.addChild(Util.getValuedConfiguration("verifyIdentity", "" + m_verifyIdentity)); + + DefaultConfiguration handlerChainConfig = new DefaultConfiguration("handlerchain"); + handlerChainConfig.addChild(createCommandHandlerConfiguration("HELO", HeloCmdHandler.class)); + handlerChainConfig.addChild(createCommandHandlerConfiguration("EHLO", EhloCmdHandler.class)); + handlerChainConfig.addChild(createCommandHandlerConfiguration("AUTH", AuthCmdHandler.class)); + handlerChainConfig.addChild(createCommandHandlerConfiguration("VRFY", VrfyCmdHandler.class)); + handlerChainConfig.addChild(createCommandHandlerConfiguration("EXPN", ExpnCmdHandler.class)); + handlerChainConfig.addChild(createCommandHandlerConfiguration("MAIL", MailCmdHandler.class)); + handlerChainConfig.addChild(createCommandHandlerConfiguration("RCPT", RcptCmdHandler.class)); + handlerChainConfig.addChild(createCommandHandlerConfiguration("DATA", DataCmdHandler.class)); + handlerChainConfig.addChild(createCommandHandlerConfiguration("RSET", RsetCmdHandler.class)); + handlerChainConfig.addChild(createCommandHandlerConfiguration("HELP", HelpCmdHandler.class)); + handlerChainConfig.addChild(createCommandHandlerConfiguration("QUIT", QuitCmdHandler.class)); + + handlerConfig.addChild(handlerChainConfig); + addChild(handlerConfig); + } + + private DefaultConfiguration createCommandHandlerConfiguration(String command, Class commandClass) { + DefaultConfiguration cmdHandlerConfig = new DefaultConfiguration("handler"); + cmdHandlerConfig.setAttribute("command", command); + String classname = commandClass.getName(); + cmdHandlerConfig.setAttribute("class", classname); + return cmdHandlerConfig; + } + +} Index: src/java/org/apache/james/security/DigestUtil.java =================================================================== --- src/java/org/apache/james/security/DigestUtil.java (revision 327257) +++ src/java/org/apache/james/security/DigestUtil.java (working copy) @@ -143,7 +143,7 @@ * * @throws NoSuchAlgorithmException if the algorithm passed in cannot be found */ - public static String digestString(String pass, String algorithm ) + public static String digestString(String pass, String algorithm ) throws NoSuchAlgorithmException { MessageDigest md; Index: src/java/org/apache/james/smtpserver/MailCmdHandler.java =================================================================== --- src/java/org/apache/james/smtpserver/MailCmdHandler.java (revision 327257) +++ src/java/org/apache/james/smtpserver/MailCmdHandler.java (working copy) @@ -192,10 +192,11 @@ // Let the client know that the size limit has been hit. String responseString = "552 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SYSTEM_MSG_TOO_BIG)+" Message size exceeds fixed maximum message size"; session.writeResponse(responseString); + Object sender = session.getState().get(SENDER); StringBuffer errorBuffer = new StringBuffer(256) .append("Rejected message from ") - .append(session.getState().get(SENDER).toString()) + .append(/*sender == null ? "UNSPECIFIED" : */sender.toString()) .append(" from host ") .append(session.getRemoteHost()) .append(" (") Index: build.xml =================================================================== --- build.xml (revision 327257) +++ build.xml (working copy) @@ -740,6 +740,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +