View Javadoc

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