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
51
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
179
180
181
182
183
184
185
186
187
188
189
190
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
210
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
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
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
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
266
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
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 }