View Javadoc

1   /************************************************************************
2    * Copyright (c) 1999-2006 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  
18  package org.apache.james.smtpserver;
19  
20  import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
21  import org.apache.avalon.excalibur.pool.DefaultPool;
22  import org.apache.avalon.excalibur.pool.HardResourceLimitingPool;
23  import org.apache.avalon.excalibur.pool.ObjectFactory;
24  import org.apache.avalon.excalibur.pool.Pool;
25  import org.apache.avalon.excalibur.pool.Poolable;
26  import org.apache.avalon.framework.activity.Initializable;
27  import org.apache.avalon.framework.configuration.Configuration;
28  import org.apache.avalon.framework.configuration.ConfigurationException;
29  import org.apache.avalon.framework.container.ContainerUtil;
30  import org.apache.avalon.framework.logger.LogEnabled;
31  import org.apache.avalon.framework.service.ServiceException;
32  import org.apache.avalon.framework.service.ServiceManager;
33  import org.apache.james.Constants;
34  import org.apache.james.core.AbstractJamesService;
35  import org.apache.james.services.MailServer;
36  import org.apache.james.services.UsersRepository;
37  import org.apache.james.util.NetMatcher;
38  import org.apache.james.util.watchdog.Watchdog;
39  import org.apache.james.util.watchdog.WatchdogFactory;
40  import org.apache.mailet.MailetContext;
41  
42  /***
43   * <p>Accepts SMTP connections on a server socket and dispatches them to SMTPHandlers.</p>
44   *
45   * <p>Also responsible for loading and parsing SMTP specific configuration.</p>
46   *
47   * @version 1.1.0, 06/02/2001
48   */
49  /*
50   * IMPORTANT: SMTPServer extends AbstractJamesService.  If you implement ANY
51   * lifecycle methods, you MUST call super.<method> as well.
52   */
53  public class SMTPServer extends AbstractJamesService implements SMTPServerMBean {
54  
55  
56      /***
57       * The handler chain - SMTPhandlers can lookup handlerchain to obtain
58       * Command handlers , Message handlers and connection handlers
59       */
60      SMTPHandlerChain handlerChain = new SMTPHandlerChain();
61  
62      /***
63       * The mailet context - we access it here to set the hello name for the Mailet API
64       */
65      MailetContext mailetcontext;
66  
67      /***
68       * The user repository for this server - used to authenticate
69       * users.
70       */
71      private UsersRepository users;
72  
73      /***
74       * The internal mail server service.
75       */
76      private MailServer mailServer;
77  
78      /***
79       * Whether authentication is required to use
80       * this SMTP server.
81       */
82      private final static int AUTH_DISABLED = 0;
83      private final static int AUTH_REQUIRED = 1;
84      private final static int AUTH_ANNOUNCE = 2;
85      private int authRequired = AUTH_DISABLED;
86  
87      /***
88       * Whether the server verifies that the user
89       * actually sending an email matches the
90       * authentication credentials attached to the
91       * SMTP interaction.
92       */
93      private boolean verifyIdentity = false;
94  
95      /***
96       * Whether the server needs helo to be send first
97       */
98      private boolean heloEhloEnforcement = false;
99      
100     /***
101      * This is a Network Matcher that should be configured to contain
102      * authorized networks that bypass SMTP AUTH requirements.
103      */
104     private NetMatcher authorizedNetworks = null;
105 
106     /***
107      * The maximum message size allowed by this SMTP server.  The default
108      * value, 0, means no limit.
109      */
110     private long maxMessageSize = 0;
111 
112     /***
113      * The number of bytes to read before resetting
114      * the connection timeout timer.  Defaults to
115      * 20 KB.
116      */
117     private int lengthReset = 20 * 1024;
118 
119     /***
120      * The pool used to provide SMTP Handler objects
121      */
122     private Pool theHandlerPool = null;
123 
124     /***
125      * The pool used to provide SMTP Handler objects
126      */
127     private ObjectFactory theHandlerFactory = new SMTPHandlerFactory();
128 
129     /***
130      * The factory used to generate Watchdog objects
131      */
132     private WatchdogFactory theWatchdogFactory;
133 
134     /***
135      * The configuration data to be passed to the handler
136      */
137     private SMTPHandlerConfigurationData theConfigData
138         = new SMTPHandlerConfigurationDataImpl();
139 
140     private ServiceManager serviceManager;
141 
142     /***
143      * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
144      */
145     public void service( final ServiceManager manager ) throws ServiceException {
146         super.service( manager );
147         serviceManager = manager;
148         mailetcontext = (MailetContext) manager.lookup("org.apache.mailet.MailetContext");
149         mailServer = (MailServer) manager.lookup(MailServer.ROLE);
150         users = (UsersRepository) manager.lookup(UsersRepository.ROLE);
151     }
152 
153     /***
154      * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
155      */
156     public void configure(final Configuration configuration) throws ConfigurationException {
157         super.configure(configuration);
158         if (isEnabled()) {
159             mailetcontext.setAttribute(Constants.HELLO_NAME, helloName);
160             Configuration handlerConfiguration = configuration.getChild("handler");
161             String authRequiredString = handlerConfiguration.getChild("authRequired").getValue("false").trim().toLowerCase();
162             if (authRequiredString.equals("true")) authRequired = AUTH_REQUIRED;
163             else if (authRequiredString.equals("announce")) authRequired = AUTH_ANNOUNCE;
164             else authRequired = AUTH_DISABLED;
165             verifyIdentity = handlerConfiguration.getChild("verifyIdentity").getValueAsBoolean(false);
166             if (authRequired != AUTH_DISABLED) {
167                 if (verifyIdentity) {
168                     getLogger().info("This SMTP server requires authentication and verifies that the authentication credentials match the sender address.");
169                 } else {
170                     getLogger().info("This SMTP server requires authentication, but doesn't verify that the authentication credentials match the sender address.");
171                 }
172             } else {
173                 getLogger().info("This SMTP server does not require authentication.");
174             }
175 
176             String authorizedAddresses = handlerConfiguration.getChild("authorizedAddresses").getValue(null);
177             if (authRequired == AUTH_DISABLED && authorizedAddresses == null) {
178                 /* if SMTP AUTH is not requred then we will use
179                  * authorizedAddresses to determine whether or not to
180                  * relay e-mail.  Therefore if SMTP AUTH is not
181                  * required, we will not relay e-mail unless the
182                  * sending IP address is authorized.
183                  *
184                  * Since this is a change in behavior for James v2,
185                  * create a default authorizedAddresses network of
186                  * 0.0.0.0/0, which matches all possible addresses, thus
187                  * preserving the current behavior.
188                  *
189                  * James v3 should require the <authorizedAddresses>
190                  * element.
191                  */
192                 authorizedAddresses = "0.0.0.0/0.0.0.0";
193             }
194 
195             if (authorizedAddresses != null) {
196                 java.util.StringTokenizer st = new java.util.StringTokenizer(authorizedAddresses, ", ", false);
197                 java.util.Collection networks = new java.util.ArrayList();
198                 while (st.hasMoreTokens()) {
199                     String addr = st.nextToken();
200                     networks.add(addr);
201                 }
202                 authorizedNetworks = new NetMatcher(networks);
203             }
204 
205             if (authorizedNetworks != null) {
206                 getLogger().info("Authorized addresses: " + authorizedNetworks.toString());
207             }
208 
209             // get the message size limit from the conf file and multiply
210             // by 1024, to put it in bytes
211             maxMessageSize = handlerConfiguration.getChild( "maxmessagesize" ).getValueAsLong( maxMessageSize ) * 1024;
212             if (maxMessageSize > 0) {
213                 getLogger().info("The maximum allowed message size is " + maxMessageSize + " bytes.");
214             } else {
215                 getLogger().info("No maximum message size is enforced for this server.");
216             }
217             // How many bytes to read before updating the timer that data is being transfered
218             lengthReset = configuration.getChild("lengthReset").getValueAsInteger(lengthReset);
219             if (lengthReset <= 0) {
220                 throw new ConfigurationException("The configured value for the idle timeout reset, " + lengthReset + ", is not valid.");
221             }
222             if (getLogger().isInfoEnabled()) {
223                 getLogger().info("The idle timeout will be reset every " + lengthReset + " bytes.");
224             }
225             
226             heloEhloEnforcement = handlerConfiguration.getChild("heloEhloEnforcement").getValueAsBoolean(true);
227             
228             if (authRequiredString.equals("true")) authRequired = AUTH_REQUIRED;
229 
230             //set the logger
231             ContainerUtil.enableLogging(handlerChain,getLogger());
232 
233             try {
234                 ContainerUtil.service(handlerChain,serviceManager);
235             } catch (ServiceException e) {
236                 if (getLogger().isErrorEnabled()) {
237                     getLogger().error("Failed to service handlerChain",e);
238                 }
239                 throw new ConfigurationException("Failed to service handlerChain");
240             }
241             
242             //read from the XML configuration and create and configure each of the handlers
243             ContainerUtil.configure(handlerChain,handlerConfiguration.getChild("handlerchain"));
244 
245         } else {
246             mailetcontext.setAttribute(Constants.HELLO_NAME, "localhost");
247         }
248     }
249 
250     /***
251      * @see org.apache.avalon.framework.activity.Initializable#initialize()
252      */
253     public void initialize() throws Exception {
254         super.initialize();
255         if (!isEnabled()) {
256             return;
257         }
258 
259         if (connectionLimit != null) {
260             theHandlerPool = new HardResourceLimitingPool(theHandlerFactory, 5, connectionLimit.intValue());
261             if (getLogger().isDebugEnabled()) {
262                 getLogger().debug("Using a bounded pool for SMTP handlers with upper limit " + connectionLimit.intValue());
263             }
264         } else {
265             // NOTE: The maximum here is not a real maximum.  The handler pool will continue to
266             //       provide handlers beyond this value.
267             theHandlerPool = new DefaultPool(theHandlerFactory, null, 5, 30);
268             getLogger().debug("Using an unbounded pool for SMTP handlers.");
269         }
270         if (theHandlerPool instanceof LogEnabled) {
271             ((LogEnabled)theHandlerPool).enableLogging(getLogger());
272         }
273         if (theHandlerPool instanceof Initializable) {
274             ((Initializable)theHandlerPool).initialize();
275         }
276 
277         theWatchdogFactory = getWatchdogFactory();
278     }
279 
280     /***
281      * @see org.apache.james.core.AbstractJamesService#getDefaultPort()
282      */
283      protected int getDefaultPort() {
284         return 25;
285      }
286 
287     /***
288      * @see org.apache.james.core.AbstractJamesService#getServiceType()
289      */
290     public String getServiceType() {
291         return "SMTP Service";
292     }
293 
294     /***
295      * @see org.apache.avalon.cornerstone.services.connection.AbstractHandlerFactory#newHandler()
296      */
297     protected ConnectionHandler newHandler()
298             throws Exception {
299         SMTPHandler theHandler = (SMTPHandler)theHandlerPool.get();
300 
301         if (getLogger().isDebugEnabled()) {
302             getLogger().debug("Getting SMTPHandler from pool.");
303         }
304         Watchdog theWatchdog = theWatchdogFactory.getWatchdog(theHandler.getWatchdogTarget());
305 
306         theHandler.setConfigurationData(theConfigData);
307 
308         theHandler.setWatchdog(theWatchdog);
309 
310         //pass the handler chain to every SMTPhandler
311         theHandler.setHandlerChain(handlerChain);
312 
313         return theHandler;
314     }
315 
316     /***
317      * @see org.apache.avalon.cornerstone.services.connection.ConnectionHandlerFactory#releaseConnectionHandler(ConnectionHandler)
318      */
319     public void releaseConnectionHandler( ConnectionHandler connectionHandler ) {
320         if (!(connectionHandler instanceof SMTPHandler)) {
321             throw new IllegalArgumentException("Attempted to return non-SMTPHandler to pool.");
322         }
323         if (getLogger().isDebugEnabled()) {
324             getLogger().debug("Returning SMTPHandler to pool.");
325         }
326         theHandlerPool.put((Poolable)connectionHandler);
327     }
328 
329     /***
330      * The factory for producing handlers.
331      */
332     private static class SMTPHandlerFactory
333         implements ObjectFactory {
334 
335         /***
336          * @see org.apache.avalon.excalibur.pool.ObjectFactory#newInstance()
337          */
338         public Object newInstance() throws Exception {
339             return new SMTPHandler();
340         }
341 
342         /***
343          * @see org.apache.avalon.excalibur.pool.ObjectFactory#getCreatedClass()
344          */
345         public Class getCreatedClass() {
346             return SMTPHandler.class;
347         }
348 
349         /***
350          * @see org.apache.avalon.excalibur.pool.ObjectFactory#decommision(Object)
351          */
352         public void decommission( Object object ) throws Exception {
353             return;
354         }
355     }
356 
357     /***
358      * A class to provide SMTP handler configuration to the handlers
359      */
360     private class SMTPHandlerConfigurationDataImpl
361         implements SMTPHandlerConfigurationData {
362 
363         /***
364          * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#getHelloName()
365          */
366         public String getHelloName() {
367             return SMTPServer.this.helloName;
368         }
369 
370         /***
371          * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#getResetLength()
372          */
373         public int getResetLength() {
374             return SMTPServer.this.lengthReset;
375         }
376 
377         /***
378          * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#getMaxMessageSize()
379          */
380         public long getMaxMessageSize() {
381             return SMTPServer.this.maxMessageSize;
382         }
383 
384         /***
385          * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#isAuthRequired(String)
386          */
387         public boolean isRelayingAllowed(String remoteIP) {
388             boolean relayingAllowed = false;
389             if (authorizedNetworks != null) {
390                 relayingAllowed = SMTPServer.this.authorizedNetworks.matchInetNetwork(remoteIP);
391             }
392             return relayingAllowed;
393         }
394 
395         /***
396          * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#isAuthRequired(String)
397          */
398         public boolean isAuthRequired(String remoteIP) {
399               if (SMTPServer.this.authRequired == AUTH_ANNOUNCE) return true;
400             boolean authRequired = SMTPServer.this.authRequired != AUTH_DISABLED;
401             if (authorizedNetworks != null) {
402                 authRequired = authRequired && !SMTPServer.this.authorizedNetworks.matchInetNetwork(remoteIP);
403             }
404             return authRequired;
405         }
406 
407         /***
408          * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#isAuthRequired()
409          */
410         public boolean isAuthRequired() {
411             return SMTPServer.this.authRequired != AUTH_DISABLED;
412         }
413 
414         /***
415          * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#isVerifyIdentity()
416          */
417         public boolean isVerifyIdentity() {
418             return SMTPServer.this.verifyIdentity;
419         }
420 
421         /***
422          * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#getMailServer()
423          */
424         public MailServer getMailServer() {
425             return SMTPServer.this.mailServer;
426         }
427 
428         /***
429          * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#getUsersRepository()
430          */
431         public UsersRepository getUsersRepository() {
432             return SMTPServer.this.users;
433         }
434         
435         /***
436          * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#useHeloEnforcement()
437          */
438         public boolean useHeloEhloEnforcement() {
439             return SMTPServer.this.heloEhloEnforcement;
440         }
441 
442     }
443 }