001: /******************************************************************************
002: * $Source: /cvsroot/sshwebproxy/src/java/com/ericdaugherty/sshwebproxy/SshAdminServlet.java,v $
003: * $Revision: 1.2 $
004: * $Author: edaugherty $
005: * $Date: 2003/11/23 00:18:10 $
006: ******************************************************************************
007: * Copyright (c) 2003, Eric Daugherty (http://www.ericdaugherty.com)
008: * All rights reserved.
009: *
010: * Redistribution and use in source and binary forms, with or without
011: * modification, are permitted provided that the following conditions are met:
012: *
013: * * Redistributions of source code must retain the above copyright notice,
014: * this list of conditions and the following disclaimer.
015: * * Redistributions in binary form must reproduce the above copyright
016: * notice, this list of conditions and the following disclaimer in the
017: * documentation and/or other materials provided with the distribution.
018: * * Neither the name of the Eric Daugherty nor the names of its
019: * contributors may be used to endorse or promote products derived
020: * from this software without specific prior written permission.
021: *
022: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
023: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
024: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
025: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
026: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
027: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
028: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
029: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
030: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
031: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
032: * THE POSSIBILITY OF SUCH DAMAGE.
033: * *****************************************************************************
034: * For current versions and more information, please visit:
035: * http://www.ericdaugherty.com/dev/sshwebproxy
036: *
037: * or contact the author at:
038: * web@ericdaugherty.com
039: *****************************************************************************/package com.ericdaugherty.sshwebproxy;
040:
041: import org.apache.commons.logging.Log;
042: import org.apache.commons.logging.LogFactory;
043:
044: import javax.servlet.http.HttpServletRequest;
045: import javax.servlet.http.HttpServletResponse;
046: import javax.servlet.http.HttpServlet;
047: import javax.servlet.ServletException;
048: import javax.servlet.ServletConfig;
049: import java.io.IOException;
050: import java.io.File;
051: import java.io.InputStream;
052: import java.io.FileInputStream;
053: import java.io.OutputStream;
054: import java.io.FileOutputStream;
055: import java.util.Properties;
056: import java.util.Enumeration;
057: import java.security.NoSuchAlgorithmException;
058: import java.security.MessageDigest;
059:
060: /**
061: * Handles all the actions that are not
062: * releated to actual SSH communciation.
063: *
064: * @author Eric Daugherty
065: */
066: public class SshAdminServlet extends HttpServlet implements
067: SshConstants {
068:
069: //***************************************************************
070: // Variables
071: //***************************************************************
072:
073: /** Reference to the file that contains the user login information */
074: private File propertiesFile;
075:
076: /** The last time the propertiesFile was changed */
077: private long propertiesFileLastModified;
078:
079: /** The loaded user login information */
080: private Properties properties;
081:
082: /** Logger */
083: private static final Log log = LogFactory
084: .getLog(SshAdminServlet.class);
085:
086: //***************************************************************
087: // HTTPServlet Methods
088: //***************************************************************
089:
090: /**
091: * Called when the application is deployed. Loads the properties file
092: *
093: * @param servletConfig
094: * @throws javax.servlet.ServletException
095: */
096: public void init(ServletConfig servletConfig)
097: throws ServletException {
098: super .init(servletConfig);
099:
100: // Load the properties.
101: initializeProperties();
102:
103: // Start the watchdog thread.
104: new ConfigurationFileWatcher().start();
105:
106: log.info("SSHWebProxy Initialized using properties file: "
107: + propertiesFile.getAbsolutePath());
108: }
109:
110: protected void doGet(HttpServletRequest request,
111: HttpServletResponse response) throws ServletException,
112: IOException {
113: log.warn("doGet called, but is not implemented.");
114: response.sendRedirect(PAGE_HOME);
115: }
116:
117: /**
118: * Handles requests from the SHH client JSP page. All requests from
119: * that page should be via POST.
120: *
121: * @param request
122: * @param response
123: * @throws ServletException
124: * @throws IOException
125: */
126: protected void doPost(HttpServletRequest request,
127: HttpServletResponse response) throws ServletException,
128: IOException {
129: String action = request.getParameter(PARAMETER_ACTION);
130:
131: // Verify we received an action to perform.
132: if (action == null || action.trim().length() == 0) {
133: log
134: .warn("POST Request received without an action parameter.");
135: response.sendRedirect(PAGE_HOME);
136: }
137:
138: action = action.trim();
139: if (ACTION_LOGIN.equals(action)) {
140: login(request, response);
141: } else {
142: log
143: .warn("POST Request received with an invalid action parameter: "
144: + action);
145: response.sendRedirect(PAGE_HOME);
146: }
147: }
148:
149: //***************************************************************
150: // Private Action Handlers
151: //***************************************************************
152:
153: private void login(HttpServletRequest request,
154: HttpServletResponse response) throws IOException {
155: log.debug("Login request received.");
156:
157: SshSession sshSession = new SshSession(request);
158:
159: String username = request.getParameter(PARAMETER_USERNAME);
160: String password = request.getParameter(PARAMETER_PASSWORD);
161: String redirectPage = PAGE_LOGIN;
162:
163: if (username == null || username.length() == 0) {
164: sshSession
165: .setErrorMessage("Please specify a vaild username.");
166: } else if (password == null || password.length() == 0) {
167: sshSession
168: .setErrorMessage("Please specify a valid password.");
169: } else {
170: username = username.trim();
171: password = password.trim();
172:
173: String correctPassword = properties.getProperty(username);
174: if (correctPassword == null) {
175: sshSession.setErrorMessage("Unknown User.");
176: } else {
177: String encryptedPassword = encryptPassword(password);
178: if (correctPassword.equals(encryptedPassword)) {
179: sshSession.setUser(username);
180: redirectPage = PAGE_HOME;
181: if (log.isInfoEnabled())
182: log.info("User: " + username
183: + " logged in successfully.");
184: } else {
185: sshSession.setErrorMessage("Incorrect Password.");
186: }
187: }
188: }
189:
190: response.sendRedirect(redirectPage);
191: }
192:
193: //***************************************************************
194: // Private Util Methods
195: //***************************************************************
196:
197: /**
198: * Loads the properties file. If the user specified the system
199: * parameter sshwebproxy.properties, the file will be loaded from there.
200: * Otherwise the file will be loaded from the default location. If it
201: * does not exist in the default location, a default file will be copied
202: * to the default location.
203: */
204: private void initializeProperties() throws ServletException {
205: properties = new Properties();
206:
207: String fileName = System.getProperty(PROPERTIES_FILENAME);
208: if (fileName != null && !fileName.equals("")) {
209: // The user specified file path for the properties file. Load it from there.
210: propertiesFile = new File(fileName);
211: if (propertiesFile.exists() && propertiesFile.isFile()) {
212: // Load the properties file from the specified location.
213: try {
214: loadProperties();
215: } catch (IOException ioException) {
216: // If we got here, the file exists and is a file, but there
217: // was some error loading it. Not much we can do, so just
218: // error out. This should never happen.
219: throw new ServletException(
220: "Unable to load the properties file from the specified location: "
221: + propertiesFile.getAbsolutePath()
222: + " due to an IOException: "
223: + ioException);
224: }
225: } else {
226: throw new ServletException(
227: "The specified properties file: "
228: + propertiesFile.getAbsolutePath()
229: + " does not exist.");
230: }
231: } else {
232: // If the sshwebproxy.properties location was not specified as a
233: // system property, attempt to load it from the default
234: // location.
235:
236: log
237: .info("The system property \""
238: + PROPERTIES_FILENAME
239: + "\" was not specified. Using the default location.");
240:
241: propertiesFile = new File(PROPERTIES_FILENAME);
242: if (propertiesFile.exists() && propertiesFile.isFile()) {
243: // Load the properties file from the default location.
244: try {
245: loadProperties();
246: } catch (IOException ioException) {
247: // If we got here, the file exists and is a file, but there
248: // was some error loading it. Not much we can do, so just
249: // error out. This should never happen.
250: throw new ServletException(
251: "Unable to load the properties file from the default location: "
252: + propertiesFile.getAbsolutePath()
253: + " due to an IOException: "
254: + ioException);
255: }
256: } else {
257: // The default properties file does not exist, so attempt
258: // to copy the properties file from the war to the default
259: // file location.
260:
261: InputStream propertiesStream = getServletContext()
262: .getResourceAsStream(
263: "/WEB-INF/" + PROPERTIES_FILENAME);
264: if (propertiesStream != null) {
265: try {
266: // Load from the WAR
267: properties.load(propertiesStream);
268: // Save to the default location.
269: saveProperties();
270: // Load from the new location.
271: loadProperties();
272: } catch (IOException ioException) {
273: throw new ServletException(
274: "Error copying the properties file from the WAR to the default location: "
275: + propertiesFile
276: .getAbsolutePath()
277: + " due to an IOException: "
278: + ioException);
279: }
280: } else {
281: throw new ServletException(
282: "Unable to load "
283: + PROPERTIES_FILENAME
284: + " from WAR. SSHWebProxy will not function correctly!");
285: }
286: }
287: }
288: }
289:
290: /**
291: * Loads the properties from the propertiesFile
292: * location.
293: */
294: private void loadProperties() throws IOException {
295: InputStream propertiesStream = null;
296: try {
297: propertiesStream = new FileInputStream(propertiesFile);
298: properties.load(propertiesStream);
299: encryptProperties();
300: propertiesFileLastModified = propertiesFile.lastModified();
301: } finally {
302: if (propertiesStream != null) {
303: propertiesStream.close();
304: }
305: }
306: }
307:
308: /**
309: * Persists the properties to disk. This should be called
310: * after any changes to the configuration made by the user.
311: */
312: private void saveProperties() throws IOException {
313:
314: OutputStream propertiesStream = null;
315: try {
316: propertiesStream = new FileOutputStream(propertiesFile);
317: properties.store(propertiesStream, PROPERTIES_HEADER);
318: } finally {
319: if (propertiesStream != null) {
320: propertiesStream.close();
321: }
322: }
323: }
324:
325: /**
326: * Checks to see if the passwords are encrypted or not. If not, it
327: * encrypts them and updates and saves the properties file.
328: *
329: * @throws IOException thrown if an error occurs saving the updated properties.
330: */
331: private void encryptProperties() throws IOException {
332: boolean changed = false;
333:
334: Enumeration usernames = properties.keys();
335: while (usernames.hasMoreElements()) {
336: String username = (String) usernames.nextElement();
337: String password = properties.getProperty(username);
338: // If the password is not hashed, hash it now.
339: if (password.length() != 60) {
340: password = encryptPassword(password);
341: if (password == null) {
342: log
343: .error("Error encrypting plaintext password from user.conf for user "
344: + username);
345: throw new RuntimeException(
346: "Error encrypting password for user: "
347: + username);
348: }
349: properties.setProperty(username, password);
350: changed = true;
351: }
352: }
353:
354: // Save the changes.
355: if (changed)
356: saveProperties();
357: }
358:
359: /**
360: * Creates a one-way has of the specified password. This allows passwords to be
361: * safely stored without an easy way to retrieve the original value.
362: *
363: * @param password the string to encrypt.
364: *
365: * @return the encrypted password, or null if encryption failed.
366: */
367: private String encryptPassword(String password) {
368:
369: try {
370: MessageDigest md = MessageDigest.getInstance("SHA");
371:
372: //Create the encrypted Byte[]
373: md.update(password.getBytes());
374: byte[] hash = md.digest();
375:
376: //Convert the byte array into a String
377:
378: StringBuffer hashStringBuf = new StringBuffer();
379: String byteString;
380: int byteLength;
381:
382: for (int index = 0; index < hash.length; index++) {
383:
384: byteString = String.valueOf(hash[index] + 128);
385:
386: //Pad string to 3. Otherwise hash may not be unique.
387: byteLength = byteString.length();
388: switch (byteLength) {
389: case 1:
390: byteString = "00" + byteString;
391: break;
392: case 2:
393: byteString = "0" + byteString;
394: break;
395: }
396: hashStringBuf.append(byteString);
397: }
398:
399: return hashStringBuf.toString();
400: } catch (NoSuchAlgorithmException nsae) {
401: log.error("Error getting password hash - "
402: + nsae.getMessage());
403: return null;
404: }
405: }
406:
407: //***************************************************************
408: // Watchdog Inner Class
409: //***************************************************************
410:
411: /**
412: * Checks the user configuration file and reloads it if it is new.
413: */
414: class ConfigurationFileWatcher extends Thread {
415:
416: /**
417: * Initialize the thread.
418: */
419: public ConfigurationFileWatcher() {
420: super ("SSHWebProxy Configuration Watchdog");
421: setDaemon(true);
422: }
423:
424: /**
425: * Check the timestamp on the file to see
426: * if it has been updated.
427: */
428: public void run() {
429: long sleepTime = 10 * 1000;
430: while (true) {
431: try {
432: Thread.sleep(sleepTime);
433: if (propertiesFile.lastModified() > propertiesFileLastModified) {
434: log
435: .info("Configuration File Changed, reloading...");
436: loadProperties();
437: }
438: } catch (Throwable throwable) {
439: log.error(
440: "Error in ConfigurationWatcher thread. Thread will continue to execute. "
441: + throwable, throwable);
442: }
443: }
444: }
445: }
446: }
|