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    }