001 /*
002 * Copyright 2010 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package com.hs.mail.imap.server;
017
018 import java.io.FileNotFoundException;
019 import java.io.FileOutputStream;
020 import java.io.PrintStream;
021 import java.net.InetAddress;
022 import java.net.InetSocketAddress;
023 import java.util.concurrent.Executors;
024
025 import javax.net.ssl.SSLEngine;
026
027 import org.apache.commons.lang.StringUtils;
028 import org.apache.log4j.Logger;
029 import org.jboss.netty.bootstrap.ServerBootstrap;
030 import org.jboss.netty.channel.ChannelPipeline;
031 import org.jboss.netty.channel.ChannelPipelineFactory;
032 import org.jboss.netty.channel.Channels;
033 import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
034 import org.jboss.netty.handler.ssl.SslHandler;
035 import org.jboss.netty.handler.timeout.ReadTimeoutHandler;
036 import org.jboss.netty.util.HashedWheelTimer;
037 import org.jboss.netty.util.Timer;
038 import org.springframework.beans.factory.InitializingBean;
039
040 import com.hs.mail.container.config.Config;
041 import com.hs.mail.imap.server.codec.ImapMessageEncoder;
042 import com.hs.mail.imap.server.codec.ImapRequestDecoder;
043
044 /**
045 * NIO IMAP Server which use Netty
046 *
047 * @author Won Chul Doh
048 * @since Jan 12, 2010
049 */
050 public class ImapServer implements InitializingBean {
051
052 /**
053 * The host name of network interface to which the service will bind. If not
054 * set, the server binds to all available interfaces.
055 */
056 private String bind = null;
057
058 /**
059 * The port on which this server will be made available.
060 */
061 private int port = 143;
062
063 /**
064 * Whether TLS is enabled for this server's socket?
065 */
066 private boolean useTLS = false;
067
068 public String getBind() {
069 return bind;
070 }
071
072 public void setBind(String bind) {
073 this.bind = bind;
074 }
075
076 public void setPort(int port) {
077 this.port = port;
078 }
079
080 public int getPort() {
081 return port;
082 }
083
084 public void setUseTLS(boolean useTLS) {
085 this.useTLS = useTLS;
086 }
087
088 public boolean isUseTLS() {
089 return useTLS;
090 }
091
092 /**
093 * This method returns the type of service provided by this server.
094 * This should be invariant over the life of the class.
095 *
096 * Subclasses may override this implementation. This implementation
097 * parses the complete class name and returns the undecorated class
098 * name.
099 *
100 * @return description of this server
101 */
102 public String getServiceType() {
103 String name = getClass().getName();
104 int i = name.lastIndexOf(".");
105 if (i > 0 && i < name.length() - 2) {
106 name = name.substring(i + 1);
107 }
108 return name;
109 }
110
111 public void afterPropertiesSet() throws Exception {
112 // Configure the server.
113 ServerBootstrap bootstrap = new ServerBootstrap(
114 new NioServerSocketChannelFactory(Executors
115 .newCachedThreadPool(), Executors.newCachedThreadPool()));
116
117 bootstrap.setPipelineFactory(createPipelineFactory());
118
119 // Bind and start to accept incoming connections.
120 InetSocketAddress endpoint = null;
121 if (!StringUtils.isEmpty(bind) && !"*".equals(bind)) {
122 InetAddress bindTo = InetAddress.getByName(bind);
123 endpoint = new InetSocketAddress(bindTo, getPort());
124 } else {
125 endpoint = new InetSocketAddress(getPort());
126 }
127 bootstrap.bind(endpoint);
128
129 StringBuilder logBuffer = new StringBuilder(64)
130 .append(getServiceType())
131 .append(" started on port ")
132 .append(getPort());
133 Logger.getLogger("console").info(logBuffer.toString());
134 }
135
136 private ChannelPipelineFactory createPipelineFactory() {
137 return new ChannelPipelineFactory() {
138
139 public ChannelPipeline getPipeline() throws Exception {
140 // Create a default pipeline implementation.
141 ChannelPipeline pipeline = Channels.pipeline();
142
143 if (isUseTLS()) {
144 SSLEngine engine = Config.getSSLContext().createSSLEngine();
145 engine.setUseClientMode(false);
146 pipeline.addFirst("ssl", new SslHandler(engine));
147 }
148 if ("true".equals(Config.getProperty("imap_trace_protocol", "false"))) {
149 pipeline.addLast("debug", createDebuggingHandler());
150 }
151 int timeout = (int) Config.getNumberProperty("imap_timeout", 1800);
152 Timer timer = new HashedWheelTimer();
153 // Set 30-minute read timeout.
154 pipeline.addLast("timeout", new ReadTimeoutHandler(timer, timeout));
155 int maxLineLength = (int) Config.getNumberProperty("imap_line_limit", 8192);
156 pipeline.addLast("decoder", new ImapRequestDecoder(maxLineLength));
157 pipeline.addLast("encoder", new ImapMessageEncoder());
158
159 // and then business logic.
160 ImapServerHandler handler = new ImapServerHandler();
161 pipeline.addLast("handler", handler);
162
163 return pipeline;
164 }
165
166 };
167 }
168
169 private DebuggingHandler createDebuggingHandler() {
170 DebuggingHandler handler = new DebuggingHandler();
171 String path = Config.getProperty("imap_protocol_log", null);
172 if (path != null) {
173 try {
174 FileOutputStream fos = new FileOutputStream(path);
175 handler.setDebugOut(new PrintStream(fos));
176 } catch (FileNotFoundException e) {
177 // Ignore this exception
178 }
179 }
180 return handler;
181 }
182
183 }