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
53
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
181
182
183
184
185
186
187
188
189
190
191
192
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
212
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
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
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
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
268
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
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 }