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 }