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.container.config;
017
018 import java.io.File;
019 import java.io.IOException;
020 import java.net.InetAddress;
021 import java.net.UnknownHostException;
022 import java.text.DecimalFormat;
023 import java.util.Calendar;
024 import java.util.Date;
025 import java.util.GregorianCalendar;
026 import java.util.HashSet;
027 import java.util.Locale;
028 import java.util.Properties;
029 import java.util.Set;
030
031 import javax.net.ssl.SSLContext;
032
033 import org.apache.commons.io.FileUtils;
034 import org.apache.commons.io.FilenameUtils;
035 import org.apache.commons.lang.ArrayUtils;
036 import org.apache.commons.lang.StringUtils;
037 import org.apache.log4j.Logger;
038 import org.springframework.beans.factory.InitializingBean;
039
040 import com.hs.mail.container.server.SSLContextFactory;
041 import com.hs.mail.util.InetAddressMatcher;
042
043 /**
044 * Provides a number of properties to the server.
045 *
046 * @author Won Chul Doh
047 * @since Jun 3, 2010
048 */
049 public class Config implements InitializingBean {
050
051 static Logger console = Logger.getLogger("console");
052
053 public static final String ZIPFILE_EXTENSION = "zip";
054
055 private static final String DEF_CACHE_FIELDS = "Bcc,Cc,Date,From,In-Reply-To,Message-ID,Reply-To,Sender,Subject,To";
056
057 private static DecimalFormat formatter = new DecimalFormat("0000000000");
058
059 private static Properties properties;
060 private static File dataDirectory;
061 private static File tempDirectory;
062 private static File spoolDirectory;
063 private static String authScheme;
064 private static Set<String> defaultCacheFields;
065 private static long defaultQuota;
066 private static String[] domains;
067 private static String hostName;
068 private static String helloName;
069 private static InetAddressMatcher authorizedNetworks;
070 private static String postmaster;
071 private static long maxMessageSize;
072 private static int maxRcpt;
073 private static boolean saslAuthEnabled;
074 private static SSLContext context;
075 private static boolean initData = true;
076
077 public void setInitData(boolean initData) {
078 Config.initData = initData;
079 }
080
081 public static Properties getProperties() {
082 return properties;
083 }
084
085 public void setProperties(Properties properties) {
086 Config.properties = properties;
087 }
088
089 public static File getDataDirectory() {
090 return dataDirectory;
091 }
092
093 public static String getSubDirectory(Date date, long physmessageid) {
094 Calendar cal = Calendar.getInstance();
095 cal.setTime(date);
096 return new StringBuilder()
097 .append("mail")
098 .append(File.separator)
099 .append(cal.get(GregorianCalendar.YEAR))
100 .append(File.separator)
101 .append(cal.get(GregorianCalendar.MONTH) + 1)
102 .append(File.separator)
103 .append(cal.get(GregorianCalendar.DAY_OF_MONTH))
104 .append(File.separator)
105 .append(Integer.parseInt(
106 formatter.format(physmessageid)
107 .substring(5, 8)))
108 .toString();
109 }
110
111 public static File getDataFile(Date date, long physmessageid) throws IOException {
112 File directory = new File(dataDirectory, getSubDirectory(date, physmessageid));
113 FileUtils.forceMkdir(directory);
114 File zipped = new File(directory, Long.toString(physmessageid)
115 + FilenameUtils.EXTENSION_SEPARATOR_STR + ZIPFILE_EXTENSION);
116 return (zipped.exists()) ? zipped : new File(directory, Long
117 .toString(physmessageid));
118 }
119
120 public static File getTempDirectory() {
121 return tempDirectory;
122 }
123
124 public static File getSpoolDirectory() {
125 return spoolDirectory;
126 }
127
128 public static void setSpoolDirectory(File dir) {
129 spoolDirectory = dir;
130 }
131
132 public static File getSnapshotDirectory() {
133 return new File(spoolDirectory, "snapshot");
134 }
135
136 public static String getAuthScheme() {
137 return authScheme;
138 }
139
140 public static Set<String> getDefaultCacheFields() {
141 return defaultCacheFields;
142 }
143
144 public static long getDefaultQuota() {
145 return defaultQuota;
146 }
147
148 public static String[] getDomains() {
149 return domains;
150 }
151
152 public static String getDefaultDomain() {
153 return domains[0];
154 }
155
156 public static boolean isLocal(String domain) {
157 return ArrayUtils.contains(domains, domain);
158 }
159
160 public static String getHostName() {
161 return hostName;
162 }
163
164 public static String getHelloName() {
165 return helloName;
166 }
167
168 public static InetAddressMatcher getAuthorizedNetworks() {
169 return authorizedNetworks;
170 }
171
172 public static String getPostmaster() {
173 return postmaster;
174 }
175
176 public static long getMaxMessageSize() {
177 return maxMessageSize;
178 }
179
180 public static int getMaxRcptCount() {
181 return maxRcpt;
182 }
183
184 public static boolean isSaslAuthEnabled() {
185 return saslAuthEnabled;
186 }
187
188 public static SSLContext getSSLContext() {
189 return context;
190 }
191
192 public static String getProperty(String key, String defaultValue) {
193 return replaceByProperties(properties.getProperty(key, defaultValue));
194 }
195
196 public static File getFileProperty(String key, String file)
197 throws IOException {
198 return new File(getProperty(key, file));
199 }
200
201 public static long getNumberProperty(String key, long defaultValue) {
202 try {
203 String value = getProperty(key, Long.toString(defaultValue));
204 return Long.valueOf(value);
205 } catch (NumberFormatException e) {
206 return defaultValue;
207 }
208 }
209
210 public static boolean getBooleanProperty(String key, boolean defaultValue) {
211 String value = getProperty(key, Boolean.toString(defaultValue));
212 return "yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value);
213 }
214
215 public void afterPropertiesSet() throws Exception {
216 dataDirectory = getFileProperty("data_directory", "${app.home}"
217 + File.separator + "data");
218 console.info("Data directory is: " + dataDirectory.getCanonicalPath());
219
220 tempDirectory = getFileProperty("temp_directory", "${app.home}"
221 + File.separator + "temp");
222 console.info("Temp directory is: " + tempDirectory.getCanonicalPath());
223
224 spoolDirectory = getFileProperty("queue_directory", "${app.home}"
225 + File.separator + "spool");
226 console.info("Spool directory is: " + spoolDirectory.getCanonicalPath());
227
228 authScheme = getProperty("auth_scheme", null);
229 console.info("Authentication scheme is "
230 + ((authScheme != null) ? authScheme : "not specified"));
231
232 String fields = getProperty("default_cache_fields", DEF_CACHE_FIELDS);
233 defaultCacheFields = buildDefaultCacheFields(StringUtils.split(fields, ','));
234
235 long quota = getNumberProperty("default_quota", 0);
236 defaultQuota = quota * 1024 * 1024;
237 console.info("Default quota is: " + quota + "MB");
238
239 hostName = getProperty("myhostname", null);
240 if (null == hostName) {
241 try {
242 hostName = InetAddress.getLocalHost().getHostName();
243 } catch (UnknownHostException ex) {
244 hostName = "localhost";
245 }
246 }
247 console.info("Local host is: " + hostName);
248
249 String domain = getProperty("mydomain", null);
250 if (null == domain) {
251 domains = new String[] { StringUtils.substringAfter(hostName, ".") };
252 } else {
253 domains = StringUtils.split(domain, ",");
254 }
255 for (int i = 0; i < domains.length; i++) {
256 console.info("Handling mail for: " + domains[i]);
257 }
258
259 String networks = getProperty("mynetworks", "0.0.0.0/0.0.0.0");
260 authorizedNetworks = new InetAddressMatcher(networks);
261 console.info("SMTP relaying is allowded to: " + networks);
262
263 helloName = getProperty("smtp_helo_name", hostName);
264
265 postmaster = getProperty("postmaster", "postmaster");
266 if (postmaster.indexOf('@') < 0) {
267 String domainName = null;
268 for (int i = 0; i < domains.length; i++) {
269 String serverName = domains[i].toLowerCase(Locale.US);
270 if (!"localhost".equals(serverName)) {
271 domainName = serverName;
272 }
273 }
274 postmaster = postmaster + "@"
275 + (domainName != null ? domainName : hostName);
276 }
277 console.info("Postmaster address is: " + postmaster);
278
279 maxMessageSize = getNumberProperty("message_size_limit", 10240000);
280 console.info("Maximum message size is: " + maxMessageSize);
281
282 maxRcpt = (int) getNumberProperty("smtp_recipient_limit", 0);
283 console.info("Maximun recipients count is: " + maxRcpt);
284
285 saslAuthEnabled = getBooleanProperty("smtp_sasl_auth_enable", false);
286
287 if (initData) {
288 FileUtils.forceMkdir(dataDirectory);
289 FileUtils.forceMkdir(tempDirectory);
290 FileUtils.forceMkdir(spoolDirectory);
291 FileUtils.forceMkdir(getSnapshotDirectory());
292 buildSSLContext();
293 }
294 }
295
296 private Set<String> buildDefaultCacheFields(String[] fields) {
297 Set<String> result = new HashSet<String>();
298 for (int i = 0; i < fields.length; i++) {
299 result.add(fields[i]);
300 }
301 return result;
302 }
303
304 private void buildSSLContext() throws IOException {
305 if (context == null) {
306 String keyStore = getProperty("tls_keystore", null);
307 if (keyStore != null) {
308 String keyStorePassword = getProperty("tls_keypass", "");
309 String certificatePassword = getProperty("tls_storepass", "");
310 context = SSLContextFactory.createContext(keyStore,
311 keyStorePassword, certificatePassword);
312 }
313 }
314 }
315
316 public static String replaceByProperties(String source) {
317 if (null == source)
318 return null;
319 int end;
320 int start = source.indexOf("${");
321 while (-1 != start) {
322 end = source.indexOf("}", start + 2);
323 if (end > 0) {
324 String propName = source.substring(start + 2, end);
325 String propValue = System.getProperty(propName);
326 if (null != propValue) {
327 int propValueLen = propValue.length();
328 source = new StringBuffer(source.length())
329 .append(source.substring(0, start))
330 .append(propValue)
331 .append(source.substring(end + 1)).toString();
332 start = source.indexOf("${", start + propValueLen);
333 } else {
334 start = source.indexOf("${", end + 1);
335 }
336 } else {
337 break;
338 }
339 }
340 return source;
341 }
342
343 }