001: /*
002: * Copyright (c) 2001 - 2005 ivata limited.
003: * All rights reserved.
004: * ---------------------------------------------------------
005: * ivata groupware may be redistributed under the GNU General Public
006: * License as published by the Free Software Foundation;
007: * version 2 of the License.
008: *
009: * These programs are free software; you can redistribute them and/or
010: * modify them under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; version 2 of the License.
012: *
013: * These programs are distributed in the hope that they will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: *
017: * See the GNU General Public License in the file LICENSE.txt for more
018: * details.
019: *
020: * If you would like a copy of the GNU General Public License write to
021: *
022: * Free Software Foundation, Inc.
023: * 59 Temple Place - Suite 330
024: * Boston, MA 02111-1307, USA.
025: *
026: *
027: * To arrange commercial support and licensing, contact ivata at
028: * http://www.ivata.com/contact.jsp
029: * ---------------------------------------------------------
030: * $Log: MailSetupAction.java,v $
031: * Revision 1.9 2005/10/12 18:35:48 colinmacleod
032: * Standardized format of Logger declaration - to make it easier to find instances
033: * which are not both static and final.
034: *
035: * Revision 1.8 2005/10/11 18:56:03 colinmacleod
036: * Fixed some checkstyle and javadoc issues.
037: *
038: * Revision 1.7 2005/10/10 16:06:39 colinmacleod
039: * Merged in changes from releases 0.11.2 and 0.11.3.
040: *
041: * Revision 1.6 2005/10/09 09:39:27 colinmacleod
042: * Merged changes from ivata groupware v0.11.2 back into main trunk.
043: *
044: * Revision 1.5 2005/10/03 10:21:16 colinmacleod
045: * Fixed some style and javadoc issues.
046: *
047: * Revision 1.4 2005/10/02 14:08:59 colinmacleod
048: * Added/improved log4j logging.
049: *
050: * Revision 1.3.2.1 2005/10/08 17:35:43 colinmacleod
051: * Extended for hMailServer v4.x
052: * Now uses JDBC to set settings (rather than attempting to re-initializse).
053: *
054: * Revision 1.3 2005/04/27 15:19:24 colinmacleod
055: * Fixed error strings for IMAP and SMTP messages.
056: * Corrected SITE_ID for UNIX.
057: *
058: * Revision 1.2 2005/04/22 10:59:55 colinmacleod
059: * Added extra factory reset to apply
060: * settings changes.
061: *
062: * Revision 1.1 2005/04/11 10:03:43 colinmacleod
063: * Added setup feature.
064: *
065: * ---------------------------------------------------------
066: */
067: package com.ivata.groupware.business.mail.struts;
068:
069: import java.io.IOException;
070: import java.io.InputStream;
071: import java.net.Socket;
072: import java.net.UnknownHostException;
073: import java.sql.Connection;
074: import java.sql.DriverManager;
075: import java.sql.PreparedStatement;
076: import java.sql.SQLException;
077: import java.sql.Statement;
078: import java.util.List;
079:
080: import javax.servlet.http.HttpServletRequest;
081: import javax.servlet.http.HttpServletResponse;
082: import javax.servlet.http.HttpSession;
083:
084: import org.apache.log4j.Logger;
085: import org.apache.struts.action.ActionForm;
086: import org.apache.struts.action.ActionMapping;
087: import org.apache.struts.action.ActionMessage;
088: import org.apache.struts.action.ActionMessages;
089: import org.sourceforge.clientsession.ClientSession;
090:
091: import com.ivata.groupware.admin.security.Security;
092: import com.ivata.groupware.admin.security.server.SecuritySession;
093: import com.ivata.groupware.admin.setting.Settings;
094: import com.ivata.groupware.admin.struts.HibernateSetupAction;
095: import com.ivata.groupware.admin.struts.HibernateSetupForm;
096: import com.ivata.mask.MaskFactory;
097: import com.ivata.mask.util.SystemException;
098: import com.ivata.mask.web.struts.MaskAuthenticator;
099:
100: /**
101: * Setup the application to use a database and mail server.
102: *
103: * @since ivata groupware 0.11 (2005-03-25)
104: * @author Colin MacLeod
105: * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
106: * @version $Revision: 1.9 $
107: */
108: public class MailSetupAction extends HibernateSetupAction {
109: /**
110: * Logger for this class.
111: */
112: private static final Logger logger = Logger
113: .getLogger(MailSetupAction.class);
114: /**
115: * Number of bytes to read from the socket in each cycle.
116: */
117: private static final int SOCKET_READ_BUFFER = 1024;
118: /**
119: * The text of the prepared statement query used to change settings.
120: */
121: private static final String SETTINGS_STATEMENT = "UPDATE SETTING SET value = ? WHERE name = ? AND person_user IS NULL";
122: /**
123: * On <strong>Courier IMAP</strong> and <strong>Cyrus IMAP</strong>,
124: * all mail folder names are prefixed by the value 'INBOX.' - this
125: * setting stores that prefix.
126: */
127: private String mailFolderNamespace = "";
128:
129: /**
130: * Constructor.
131: *
132: * @param securityParam System security object.
133: * @param settingsParam System settings object.
134: * @param maskFactoryParam Creates input and list masks.
135: * @param authenticatorParam Used to check user is auhtorized to see this
136: * page.
137: */
138: public MailSetupAction(final Security securityParam,
139: final Settings settingsParam,
140: final MaskFactory maskFactoryParam,
141: final MaskAuthenticator authenticatorParam) {
142: super (securityParam, settingsParam, maskFactoryParam,
143: authenticatorParam);
144: }
145:
146: /**
147: * Check a socket on a given host is valid. This is used to check the
148: * SMTP and IMAP connections.
149: *
150: * @param host host we are checking.
151: * @param port port we are checking.
152: * @param errorMessages if there are any errors, they are appended to this
153: * collection.
154: * @param errorString the error message to show the user if all is not well.
155: * @return a string buffer containing all of the output read from the port.
156: */
157: private StringBuffer checkSocket(final String host, final int port,
158: final ActionMessages errorMessages, final String errorString) {
159: if (logger.isDebugEnabled()) {
160: logger.debug("checkSocket(String host = " + host
161: + ", int port = " + port
162: + ", ActionMessages errorMessages = "
163: + errorMessages + ", String errorString = "
164: + errorString + ") - start");
165: }
166:
167: Socket socket = null;
168: StringBuffer socketOutput = new StringBuffer();
169: try {
170: socket = new Socket(host, port);
171: logger.info("Connected with server "
172: + socket.getInetAddress() + ":" + socket.getPort());
173: InputStream socketInput = socket.getInputStream();
174: byte[] buffer = new byte[SOCKET_READ_BUFFER];
175: int bytesRead = 0;
176: do {
177: // wait a total of SOCKET_WAIT_NUMBER times, each time for
178: // SOCKET_WAIT_INTERVAL milliseconds
179: synchronized (this ) {
180: for (int waitCount = 0; (waitCount < MailSetupConstants.SOCKET_WAIT_NUMBER)
181: && (socketInput.available() == 0); ++waitCount) {
182: try {
183: wait(MailSetupConstants.SOCKET_WAIT_INTERVAL);
184: } catch (InterruptedException e1) {
185: logger.warn("Socket wait interrupted.", e1);
186: break;
187: }
188: }
189: }
190: socketOutput.append(new String(buffer, 0, bytesRead));
191: } while ((socketInput.available() != 0)
192: && ((bytesRead = socketInput.read(buffer, 0,
193: buffer.length)) != -1));
194: if (logger.isDebugEnabled()) {
195: logger.debug("Read the following string from "
196: + socket.getInetAddress() + ":"
197: + socket.getPort());
198: logger.debug(socketOutput.toString());
199: }
200: } catch (UnknownHostException e) {
201: logger.error("UnknownHostException connecting to host '"
202: + host + ":" + port + "'.", e);
203: errorMessages.add(null, new ActionMessage(errorString, e
204: .getClass().getName(), host, e.getMessage()));
205: } catch (IOException e) {
206: logger.error("IOException connecting to host '" + host
207: + ":" + port + "'.", e);
208: errorMessages.add(null, new ActionMessage(errorString, e
209: .getClass().getName(), host, e.getMessage()));
210: } finally {
211: if ((socket != null) && !socket.isClosed()) {
212: try {
213: socket.close();
214: } catch (IOException e) {
215: logger.error(e.getClass().getName()
216: + ": closing socket: " + e.getMessage(), e);
217: }
218: }
219: }
220:
221: if (logger.isDebugEnabled()) {
222: logger.debug("checkSocket() - end - return value = "
223: + socketOutput);
224: }
225: return socketOutput;
226: }
227:
228: /**
229: * Overridden to check that there are mail domains defined on windows.
230: *
231: * @param mappingParam {@inheritDoc}
232: * @param formParam {@inheritDoc}
233: * @param requestParam {@inheritDoc}
234: * @param responseParam {@inheritDoc}
235: * @param sessionParam {@inheritDoc}
236: * @param clientSession {@inheritDoc}
237: * @return {@inheritDoc}
238: * @throws SystemException {@inheritDoc}
239: */
240: public String execute(final ActionMapping mappingParam,
241: final ActionForm formParam,
242: final HttpServletRequest requestParam,
243: final HttpServletResponse responseParam,
244: final HttpSession sessionParam,
245: final ClientSession clientSession) throws SystemException {
246: if (logger.isDebugEnabled()) {
247: logger.debug("execute(ActionMapping mappingParam = "
248: + mappingParam + ", ActionForm formParam = "
249: + formParam
250: + ", HttpServletRequest requestParam = "
251: + requestParam
252: + ", HttpServletResponse responseParam = "
253: + responseParam + ", HttpSession sessionParam = "
254: + sessionParam + ", ClientSession clientSession = "
255: + clientSession + ") - start");
256: }
257:
258: MailSetupForm setupForm = (MailSetupForm) formParam;
259: List mailDomains = setupForm.getMailDomains();
260: if (setupForm.isWindows()
261: && ((mailDomains == null) || mailDomains.isEmpty())) {
262: ActionMessages errors = getErrors(requestParam);
263: errors.add(null, new ActionMessage(
264: "errors.setup.noMailDomains"));
265: saveErrors(requestParam, errors);
266: saveToken(requestParam);
267: }
268: String returnString = super .execute(mappingParam, formParam,
269: requestParam, responseParam, sessionParam,
270: clientSession);
271: if (logger.isDebugEnabled()) {
272: logger.debug("execute() - end - return value = "
273: + returnString);
274: }
275: return returnString;
276: }
277:
278: /**
279: * Called when the user clicks the 'OK' button to continue with the chosen
280: * setup options. If you chose hibernate settings, this will write them
281: * out.
282: *
283: * @param mappingParam {@inheritDoc}
284: * @param formParam {@inheritDoc}
285: * @param request {@inheritDoc}
286: * @param responseParam {@inheritDoc}
287: * @param session {@inheritDoc}
288: * @param clientSession {@inheritDoc}
289: * @param defaultForwardParam {@inheritDoc}
290: * @return Always returns <code>defaultForwardParam</code>.
291: * @throws SystemException {@inheritDoc}
292: */
293: public String onConfirm(final ActionMapping mappingParam,
294: final ActionForm formParam,
295: final HttpServletRequest request,
296: final HttpServletResponse responseParam,
297: final HttpSession session,
298: final ClientSession clientSession,
299: final String defaultForwardParam) throws SystemException {
300: if (logger.isDebugEnabled()) {
301: logger.debug("onConfirm(ActionMapping mappingParam = "
302: + mappingParam + ", ActionForm formParam = "
303: + formParam + ", HttpServletRequest request = "
304: + request
305: + ", HttpServletResponse responseParam = "
306: + responseParam + ", HttpSession session = "
307: + session + ", ClientSession clientSession = "
308: + clientSession + ", String defaultForwardParam = "
309: + defaultForwardParam + ") - start");
310: }
311:
312: MailSetupForm setupForm = (MailSetupForm) formParam;
313:
314: // check the mail hosts work as expected
315: String mailHostIMAP = setupForm.getMailHostIMAP();
316: String mailHostSMTP = setupForm.getMailHostSMTP();
317:
318: ActionMessages errorMessages = new ActionMessages();
319: StringBuffer socketOutput = checkSocket(mailHostIMAP,
320: MailSetupConstants.IMAP_PORT, errorMessages,
321: "errors.setup.hostIMAP");
322: // look for courier or cyrus - that means we need to set the folder
323: // namespace
324: if (socketOutput.indexOf(MailSetupConstants.CYRUS_SIGNATURE) != -1) {
325: logger
326: .info("IMAP server identified as Cyrus - setting folder "
327: + "namespace to '"
328: + MailSetupConstants.CYRUS_FOLDER_NAMESPACE
329: + "'.");
330: mailFolderNamespace = MailSetupConstants.CYRUS_FOLDER_NAMESPACE;
331: } else if (socketOutput
332: .indexOf(MailSetupConstants.COURIER_SIGNATURE) != -1) {
333: logger
334: .info("IMAP server identified as Courier - setting folder "
335: + "namespace to '"
336: + MailSetupConstants.COURIER_FOLDER_NAMESPACE
337: + "'.");
338: mailFolderNamespace = MailSetupConstants.COURIER_FOLDER_NAMESPACE;
339: } else {
340: logger
341: .info("IMAP server neither Cyrus nor Courier - using "
342: + "default folder namespace.");
343: mailFolderNamespace = "";
344: }
345:
346: checkSocket(mailHostSMTP, MailSetupConstants.SMTP_PORT,
347: errorMessages, "errors.setup.hostSMTP");
348:
349: String super Return = super .onConfirm(mappingParam, formParam,
350: request, responseParam, session, clientSession,
351: defaultForwardParam);
352: // did we get error messages? if so, return to the input page
353: if (!errorMessages.isEmpty()) {
354: saveErrors(request, errorMessages);
355: saveToken(request);
356:
357: if (logger.isDebugEnabled()) {
358: logger.debug("onConfirm() - end - return value = "
359: + null);
360: }
361: return null;
362: }
363:
364: // if the superclass told us to go somewhere, we'd better comply!
365: if (super Return != null) {
366: if (logger.isDebugEnabled()) {
367: logger.debug("onConfirm() - end - return value = "
368: + super Return);
369: }
370: return super Return;
371: }
372: // otherwise go to the default place
373:
374: if (logger.isDebugEnabled()) {
375: logger.debug("onConfirm() - end - return value = "
376: + defaultForwardParam);
377: }
378: return defaultForwardParam;
379: }
380:
381: /**
382: * Helper. Uses JDBC to set a single setting in the new settings table.
383: *
384: * @param preparedStatement Statement matching {@link SETTINGS_STATEMENT}.
385: * @param settingName The name of the setting to be amended.
386: * @param settingValue New setting value as a string.
387: * @throws SystemException if the setting cannot be set, or more than 1
388: * row is returned.
389: */
390: private void setOneSystemSetting(
391: final PreparedStatement preparedStatement,
392: final String settingName, final String settingValue)
393: throws SystemException {
394: if (logger.isDebugEnabled()) {
395: logger.debug("setOneSystemSetting("
396: + "PreparedStatement preparedStatement = "
397: + preparedStatement + ", String settingName = "
398: + settingName + ", String settingValue = "
399: + settingValue + ") - start");
400: }
401: try {
402: preparedStatement.setString(1, settingValue);
403: } catch (SQLException e) {
404: logger.error(
405: "setOneSystemSetting - Setting setting value to '"
406: + settingValue + "'", e);
407: throw new SystemException(e);
408: }
409: try {
410: preparedStatement.setString(2, settingName);
411: } catch (SQLException e) {
412: logger.error(
413: "setOneSystemSetting - Setting setting name to '"
414: + settingName + "'", e);
415: throw new SystemException(e);
416: }
417: int numberOfRows;
418: try {
419: numberOfRows = preparedStatement.executeUpdate();
420: } catch (SQLException e) {
421: logger.error("setOneSystemSetting - Executing statement '"
422: + SETTINGS_STATEMENT + "'", e);
423: throw new SystemException(e);
424: }
425: if (logger.isDebugEnabled()) {
426: logger.debug("setOneSystemSetting -"
427: + "Number of rows returned: " + numberOfRows);
428: }
429: if (numberOfRows != 1) {
430: String message = "setOneSystemSetting - More than 1 row returned "
431: + "for system setting '" + settingName + "'";
432: logger.error(message);
433: throw new SystemException(message);
434: }
435: if (logger.isDebugEnabled()) {
436: logger.debug("setOneSystemSetting - end");
437: }
438: }
439:
440: /**
441: * Reset the pico container factory and update all settings in the new
442: * factory.
443: * @param hibernateSetupForm the form from the current request.
444: * @param securitySession guest security session, used to access settings.
445: * @param session current HTTP session.
446: * @throws SystemException if any setting cannot be set, or more than
447: * one row is returned when setting.
448: */
449: protected void resetFactoryUpdateSettings(
450: final HibernateSetupForm hibernateSetupForm,
451: final SecuritySession securitySession,
452: final HttpSession session) throws SystemException {
453: if (logger.isDebugEnabled()) {
454: logger.debug("resetFactoryUpdateSettings("
455: + "HibernateSetupForm hibernateSetupForm = "
456: + hibernateSetupForm
457: + ", SecuritySession securitySession = "
458: + securitySession + ", HttpSession session = "
459: + session + ") - start");
460: }
461:
462: MailSetupForm setupForm = (MailSetupForm) hibernateSetupForm;
463: String uRL = hibernateSetupForm.getDatabaseURL();
464: String userName = hibernateSetupForm.getDatabaseUserName();
465: String password = hibernateSetupForm.getDatabasePassword();
466:
467: if (logger.isDebugEnabled()) {
468: logger.debug("resetFactoryUpdateSettings - "
469: + "Connecting to new DB with parameters: "
470: + "uRL = " + uRL + ", " + "userName = " + userName
471: + ", " + "password = ******)");
472: }
473: Connection connection;
474: try {
475: connection = DriverManager.getConnection(uRL, userName,
476: password);
477: } catch (SQLException e) {
478: logger.error("resetFactoryUpdateSettings - "
479: + "Connecting to new DB with paramteters: "
480: + "uRL = " + uRL + ", " + "userName = " + userName
481: + ", " + "password = ******)", e);
482: throw new SystemException(e);
483: }
484: PreparedStatement preparedStatement;
485: try {
486: preparedStatement = connection
487: .prepareStatement(SETTINGS_STATEMENT);
488: } catch (SQLException e) {
489: logger.error(
490: "resetFactoryUpdateSettings - Preparing statement '"
491: + SETTINGS_STATEMENT + "'", e);
492: throw new SystemException(e);
493: }
494: try {
495: setOneSystemSetting(preparedStatement, "emailAddressHost",
496: setupForm.getMailDomain());
497: setOneSystemSetting(preparedStatement,
498: "emailFolderNamespace", mailFolderNamespace);
499: setOneSystemSetting(preparedStatement, "emailHost",
500: setupForm.getMailHostIMAP());
501: setOneSystemSetting(preparedStatement, "emailHostSmtp",
502: setupForm.getMailHostSMTP());
503: setOneSystemSetting(preparedStatement,
504: "emailScriptServerEnvironment",
505: "SITE_ID=www\nSUDO_USER=root\nSUDO_PATH="
506: + setupForm.getScriptsPath()
507: + MailSetupConstants.SCRIPT_PATH_EXIM);
508: setOneSystemSetting(preparedStatement,
509: "pathScriptMailServer", setupForm.getScriptsPath()
510: + MailSetupConstants.SCRIPT_PATH_SUDO);
511: // on windows, we use the COM interface to hMailServer
512: if (setupForm.isWindows()) {
513: setOneSystemSetting(preparedStatement,
514: "securitySessionServer",
515: "com.ivata.groupware.business.mail.server.HMailServer");
516: // hMailServer needs the folder to be called 'INBOX', all caps
517: setOneSystemSetting(preparedStatement,
518: "emailFolderInbox", "INBOX");
519: } else {
520: // other platforms make do with sudo scripts
521: setOneSystemSetting(preparedStatement,
522: "securitySessionServer",
523: "com.ivata.groupware.business.mail.server."
524: + "ScriptMailServer");
525: }
526: } catch (Exception e) {
527: logger.error(
528: "resetFactoryUpdateSettings - Exception, so rolling "
529: + "back transation", e);
530: try {
531: preparedStatement.close();
532: } catch (SQLException e2) {
533: logger.error(
534: "resetFactoryUpdateSettings - Errror rolling "
535: + "back statement.", e2);
536: }
537: throw new SystemException(e);
538:
539: } finally {
540: try {
541: preparedStatement.close();
542: } catch (SQLException e) {
543: logger.error(
544: "resetFactoryUpdateSettings - Error closing "
545: + "prepared statement.", e);
546: throw new SystemException(e);
547: }
548: // HSQLDB requires us to explicitly shutdown
549: if ((connection != null)
550: && (uRL.indexOf("hsqldb:file") != -1)) {
551: logger
552: .info("resetFactoryUpdateSettings - shutting down "
553: + "HSQLDB file.");
554: Statement statement = null;
555: try {
556: statement = connection.createStatement();
557: boolean success = statement.execute("SHUTDOWN");
558: if (!success) {
559: logger
560: .error("resetFactoryUpdateSettings - shutting "
561: + "down HSQLDB file returned FALSE.");
562: }
563: } catch (SQLException e) {
564: logger.error(
565: "resetFactoryUpdateSettings - Error closing "
566: + " shutdown statement.", e);
567: throw new SystemException(e);
568: } finally {
569: if (statement != null) {
570: try {
571: statement.close();
572: } catch (SQLException e) {
573: logger
574: .error(
575: "resetFactoryUpdateSettings - Error "
576: + "closing shutdown statement.",
577: e);
578: throw new SystemException(e);
579: }
580: }
581: }
582: }
583: }
584: if (logger.isDebugEnabled()) {
585: logger.debug("resetFactoryUpdateSettings() - end");
586: }
587: }
588: }
|