001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.smtpserver;
019:
020: import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
021: import org.apache.avalon.excalibur.pool.DefaultPool;
022: import org.apache.avalon.excalibur.pool.HardResourceLimitingPool;
023: import org.apache.avalon.excalibur.pool.ObjectFactory;
024: import org.apache.avalon.excalibur.pool.Pool;
025: import org.apache.avalon.excalibur.pool.Poolable;
026: import org.apache.avalon.framework.activity.Initializable;
027: import org.apache.avalon.framework.configuration.Configuration;
028: import org.apache.avalon.framework.configuration.ConfigurationException;
029: import org.apache.avalon.framework.container.ContainerUtil;
030: import org.apache.avalon.framework.logger.LogEnabled;
031: import org.apache.avalon.framework.service.ServiceException;
032: import org.apache.avalon.framework.service.ServiceManager;
033: import org.apache.james.Constants;
034: import org.apache.james.core.AbstractJamesService;
035: import org.apache.james.services.MailServer;
036: import org.apache.james.services.UsersRepository;
037: import org.apache.james.util.NetMatcher;
038: import org.apache.james.util.watchdog.Watchdog;
039: import org.apache.james.util.watchdog.WatchdogFactory;
040: import org.apache.mailet.MailetContext;
041:
042: /**
043: * <p>Accepts SMTP connections on a server socket and dispatches them to SMTPHandlers.</p>
044: *
045: * <p>Also responsible for loading and parsing SMTP specific configuration.</p>
046: *
047: * @version 1.1.0, 06/02/2001
048: */
049: /*
050: * IMPORTANT: SMTPServer extends AbstractJamesService. If you implement ANY
051: * lifecycle methods, you MUST call super.<method> as well.
052: */
053: public class SMTPServer extends AbstractJamesService implements
054: SMTPServerMBean {
055:
056: /**
057: * The handler chain - SMTPhandlers can lookup handlerchain to obtain
058: * Command handlers , Message handlers and connection handlers
059: */
060: SMTPHandlerChain handlerChain = new SMTPHandlerChain();
061:
062: /**
063: * The mailet context - we access it here to set the hello name for the Mailet API
064: */
065: MailetContext mailetcontext;
066:
067: /**
068: * The user repository for this server - used to authenticate
069: * users.
070: */
071: private UsersRepository users;
072:
073: /**
074: * The internal mail server service.
075: */
076: private MailServer mailServer;
077:
078: /**
079: * Whether authentication is required to use
080: * this SMTP server.
081: */
082: private final static int AUTH_DISABLED = 0;
083: private final static int AUTH_REQUIRED = 1;
084: private final static int AUTH_ANNOUNCE = 2;
085: private int authRequired = AUTH_DISABLED;
086:
087: /**
088: * Whether the server verifies that the user
089: * actually sending an email matches the
090: * authentication credentials attached to the
091: * SMTP interaction.
092: */
093: private boolean verifyIdentity = false;
094:
095: /**
096: * Whether the server needs helo to be send first
097: */
098: private boolean heloEhloEnforcement = false;
099:
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 = new SMTPHandlerConfigurationDataImpl();
138:
139: private ServiceManager serviceManager;
140:
141: /**
142: * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
143: */
144: public void service(final ServiceManager manager)
145: throws ServiceException {
146: super .service(manager);
147: serviceManager = manager;
148: mailetcontext = (MailetContext) manager
149: .lookup("org.apache.mailet.MailetContext");
150: mailServer = (MailServer) manager.lookup(MailServer.ROLE);
151: users = (UsersRepository) manager.lookup(UsersRepository.ROLE);
152: }
153:
154: /**
155: * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
156: */
157: public void configure(final Configuration configuration)
158: throws ConfigurationException {
159: super .configure(configuration);
160: if (isEnabled()) {
161: mailetcontext.setAttribute(Constants.HELLO_NAME, helloName);
162: Configuration handlerConfiguration = configuration
163: .getChild("handler");
164: String authRequiredString = handlerConfiguration.getChild(
165: "authRequired").getValue("false").trim()
166: .toLowerCase();
167: if (authRequiredString.equals("true"))
168: authRequired = AUTH_REQUIRED;
169: else if (authRequiredString.equals("announce"))
170: authRequired = AUTH_ANNOUNCE;
171: else
172: authRequired = AUTH_DISABLED;
173: verifyIdentity = handlerConfiguration.getChild(
174: "verifyIdentity").getValueAsBoolean(false);
175: if (authRequired != AUTH_DISABLED) {
176: if (verifyIdentity) {
177: getLogger()
178: .info(
179: "This SMTP server requires authentication and verifies that the authentication credentials match the sender address.");
180: } else {
181: getLogger()
182: .info(
183: "This SMTP server requires authentication, but doesn't verify that the authentication credentials match the sender address.");
184: }
185: } else {
186: getLogger()
187: .info(
188: "This SMTP server does not require authentication.");
189: }
190:
191: String authorizedAddresses = handlerConfiguration.getChild(
192: "authorizedAddresses").getValue(null);
193: if (authRequired == AUTH_DISABLED
194: && authorizedAddresses == null) {
195: /* if SMTP AUTH is not requred then we will use
196: * authorizedAddresses to determine whether or not to
197: * relay e-mail. Therefore if SMTP AUTH is not
198: * required, we will not relay e-mail unless the
199: * sending IP address is authorized.
200: *
201: * Since this is a change in behavior for James v2,
202: * create a default authorizedAddresses network of
203: * 0.0.0.0/0, which matches all possible addresses, thus
204: * preserving the current behavior.
205: *
206: * James v3 should require the <authorizedAddresses>
207: * element.
208: */
209: authorizedAddresses = "0.0.0.0/0.0.0.0";
210: }
211:
212: if (authorizedAddresses != null) {
213: java.util.StringTokenizer st = new java.util.StringTokenizer(
214: authorizedAddresses, ", ", false);
215: java.util.Collection networks = new java.util.ArrayList();
216: while (st.hasMoreTokens()) {
217: String addr = st.nextToken();
218: networks.add(addr);
219: }
220: authorizedNetworks = new NetMatcher(networks);
221: }
222:
223: if (authorizedNetworks != null) {
224: getLogger().info(
225: "Authorized addresses: "
226: + authorizedNetworks.toString());
227: }
228:
229: // get the message size limit from the conf file and multiply
230: // by 1024, to put it in bytes
231: maxMessageSize = handlerConfiguration.getChild(
232: "maxmessagesize").getValueAsLong(maxMessageSize) * 1024;
233: if (maxMessageSize > 0) {
234: getLogger().info(
235: "The maximum allowed message size is "
236: + maxMessageSize + " bytes.");
237: } else {
238: getLogger()
239: .info(
240: "No maximum message size is enforced for this server.");
241: }
242: // How many bytes to read before updating the timer that data is being transfered
243: lengthReset = configuration.getChild("lengthReset")
244: .getValueAsInteger(lengthReset);
245: if (lengthReset <= 0) {
246: throw new ConfigurationException(
247: "The configured value for the idle timeout reset, "
248: + lengthReset + ", is not valid.");
249: }
250: if (getLogger().isInfoEnabled()) {
251: getLogger().info(
252: "The idle timeout will be reset every "
253: + lengthReset + " bytes.");
254: }
255:
256: heloEhloEnforcement = handlerConfiguration.getChild(
257: "heloEhloEnforcement").getValueAsBoolean(true);
258:
259: if (authRequiredString.equals("true"))
260: authRequired = AUTH_REQUIRED;
261:
262: //set the logger
263: ContainerUtil.enableLogging(handlerChain, getLogger());
264:
265: try {
266: ContainerUtil.service(handlerChain, serviceManager);
267: } catch (ServiceException e) {
268: if (getLogger().isErrorEnabled()) {
269: getLogger().error("Failed to service handlerChain",
270: e);
271: }
272: throw new ConfigurationException(
273: "Failed to service handlerChain");
274: }
275:
276: //read from the XML configuration and create and configure each of the handlers
277: ContainerUtil.configure(handlerChain, handlerConfiguration
278: .getChild("handlerchain"));
279:
280: } else {
281: mailetcontext.setAttribute(Constants.HELLO_NAME,
282: "localhost");
283: }
284: }
285:
286: /**
287: * @see org.apache.avalon.framework.activity.Initializable#initialize()
288: */
289: public void initialize() throws Exception {
290: super .initialize();
291: if (!isEnabled()) {
292: return;
293: }
294:
295: if (connectionLimit != null) {
296: theHandlerPool = new HardResourceLimitingPool(
297: theHandlerFactory, 5, connectionLimit.intValue());
298: if (getLogger().isDebugEnabled()) {
299: getLogger().debug(
300: "Using a bounded pool for SMTP handlers with upper limit "
301: + connectionLimit.intValue());
302: }
303: } else {
304: // NOTE: The maximum here is not a real maximum. The handler pool will continue to
305: // provide handlers beyond this value.
306: theHandlerPool = new DefaultPool(theHandlerFactory, null,
307: 5, 30);
308: getLogger().debug(
309: "Using an unbounded pool for SMTP handlers.");
310: }
311: if (theHandlerPool instanceof LogEnabled) {
312: ((LogEnabled) theHandlerPool).enableLogging(getLogger());
313: }
314: if (theHandlerPool instanceof Initializable) {
315: ((Initializable) theHandlerPool).initialize();
316: }
317:
318: theWatchdogFactory = getWatchdogFactory();
319: }
320:
321: /**
322: * @see org.apache.james.core.AbstractJamesService#getDefaultPort()
323: */
324: protected int getDefaultPort() {
325: return 25;
326: }
327:
328: /**
329: * @see org.apache.james.core.AbstractJamesService#getServiceType()
330: */
331: public String getServiceType() {
332: return "SMTP Service";
333: }
334:
335: /**
336: * @see org.apache.avalon.cornerstone.services.connection.AbstractHandlerFactory#newHandler()
337: */
338: protected ConnectionHandler newHandler() throws Exception {
339: SMTPHandler theHandler = (SMTPHandler) theHandlerPool.get();
340:
341: if (getLogger().isDebugEnabled()) {
342: getLogger().debug("Getting SMTPHandler from pool.");
343: }
344: Watchdog theWatchdog = theWatchdogFactory
345: .getWatchdog(theHandler.getWatchdogTarget());
346:
347: theHandler.setConfigurationData(theConfigData);
348:
349: theHandler.setWatchdog(theWatchdog);
350:
351: //pass the handler chain to every SMTPhandler
352: theHandler.setHandlerChain(handlerChain);
353:
354: return theHandler;
355: }
356:
357: /**
358: * @see org.apache.avalon.cornerstone.services.connection.ConnectionHandlerFactory#releaseConnectionHandler(ConnectionHandler)
359: */
360: public void releaseConnectionHandler(
361: ConnectionHandler connectionHandler) {
362: if (!(connectionHandler instanceof SMTPHandler)) {
363: throw new IllegalArgumentException(
364: "Attempted to return non-SMTPHandler to pool.");
365: }
366: if (getLogger().isDebugEnabled()) {
367: getLogger().debug("Returning SMTPHandler to pool.");
368: }
369: theHandlerPool.put((Poolable) connectionHandler);
370: }
371:
372: /**
373: * The factory for producing handlers.
374: */
375: private static class SMTPHandlerFactory implements ObjectFactory {
376:
377: /**
378: * @see org.apache.avalon.excalibur.pool.ObjectFactory#newInstance()
379: */
380: public Object newInstance() throws Exception {
381: return new SMTPHandler();
382: }
383:
384: /**
385: * @see org.apache.avalon.excalibur.pool.ObjectFactory#getCreatedClass()
386: */
387: public Class getCreatedClass() {
388: return SMTPHandler.class;
389: }
390:
391: /**
392: * @see org.apache.avalon.excalibur.pool.ObjectFactory#decommision(Object)
393: */
394: public void decommission(Object object) throws Exception {
395: return;
396: }
397: }
398:
399: /**
400: * A class to provide SMTP handler configuration to the handlers
401: */
402: private class SMTPHandlerConfigurationDataImpl implements
403: SMTPHandlerConfigurationData {
404:
405: /**
406: * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#getHelloName()
407: */
408: public String getHelloName() {
409: return SMTPServer.this .helloName;
410: }
411:
412: /**
413: * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#getResetLength()
414: */
415: public int getResetLength() {
416: return SMTPServer.this .lengthReset;
417: }
418:
419: /**
420: * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#getMaxMessageSize()
421: */
422: public long getMaxMessageSize() {
423: return SMTPServer.this .maxMessageSize;
424: }
425:
426: /**
427: * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#isAuthRequired(String)
428: */
429: public boolean isRelayingAllowed(String remoteIP) {
430: boolean relayingAllowed = false;
431: if (authorizedNetworks != null) {
432: relayingAllowed = SMTPServer.this .authorizedNetworks
433: .matchInetNetwork(remoteIP);
434: }
435: return relayingAllowed;
436: }
437:
438: /**
439: * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#isAuthRequired(String)
440: */
441: public boolean isAuthRequired(String remoteIP) {
442: if (SMTPServer.this .authRequired == AUTH_ANNOUNCE)
443: return true;
444: boolean authRequired = SMTPServer.this .authRequired != AUTH_DISABLED;
445: if (authorizedNetworks != null) {
446: authRequired = authRequired
447: && !SMTPServer.this .authorizedNetworks
448: .matchInetNetwork(remoteIP);
449: }
450: return authRequired;
451: }
452:
453: /**
454: * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#isAuthRequired()
455: */
456: public boolean isAuthRequired() {
457: return SMTPServer.this .authRequired != AUTH_DISABLED;
458: }
459:
460: /**
461: * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#isVerifyIdentity()
462: */
463: public boolean isVerifyIdentity() {
464: return SMTPServer.this .verifyIdentity;
465: }
466:
467: /**
468: * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#getMailServer()
469: */
470: public MailServer getMailServer() {
471: return SMTPServer.this .mailServer;
472: }
473:
474: /**
475: * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#getUsersRepository()
476: */
477: public UsersRepository getUsersRepository() {
478: return SMTPServer.this .users;
479: }
480:
481: /**
482: * @see org.apache.james.smtpserver.SMTPHandlerConfigurationData#useHeloEnforcement()
483: */
484: public boolean useHeloEhloEnforcement() {
485: return SMTPServer.this.heloEhloEnforcement;
486: }
487:
488: }
489: }
|