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    }