/*==========================================================================*\ | $Id: PopAuthenticator.java,v 1.2 2011/03/07 18:44:37 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2011 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.core; import com.webobjects.eoaccess.*; import com.webobjects.foundation.*; import java.io.*; import java.net.*; import javax.net.*; import javax.net.ssl.*; import org.webcat.core.AuthenticationDomain; import org.webcat.core.PopAuthenticator; import org.webcat.core.User; import org.webcat.core.UserAuthenticator; import org.webcat.core.WCProperties; import org.apache.log4j.Logger; // ------------------------------------------------------------------------- /** * A concrete implementation of <code>UserAuthenticator</code> that * tests user ids/passwords against a POP3 server. * This implementation uses the application properties * <code>authenticator.POPserver.name</code> and * <code>authenticator.POPserver.port</code> to determine the * POP server to use for authentication. * * @author Stephen Edwards * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.2 $, $Date: 2011/03/07 18:44:37 $ */ public class PopAuthenticator implements UserAuthenticator { //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Create a new PopAuthenticator object. */ public PopAuthenticator() { // Private data are initialized in their declarations } //~ Methods ............................................................... // ---------------------------------------------------------- /** * Initialize and configure the authenticator, reading subclass-specific * settings from properties. The authenticator should read any * instance-specific settings from properties named * "baseName.<property>". This operation should only be called once, * before any authenticate requests. * * @param baseName The base property name for this authenticator object * @param properties The property collection from which the object * should read its configuration settings * @return true If configuration was successful and authenticator is * ready for service */ public boolean configure( String baseName, WCProperties properties ) { boolean result = true; server = properties.getProperty( baseName + ".POPserver.name" ); if ( server == null || server.equals("") ) { log.error( "a required property is not set: " + baseName + ".POPserver.name" ); result = false; } port = properties.intForKey( baseName + ".POPserver.port" ); if ( port == 0 ) { log.error( "a required property is not set: " + baseName + ".POPserver.port\n" ); result = false; } boolean secure = properties.booleanForKey( baseName + ".POPserver.useSSL" ); socketFactory = secure ? SSLSocketFactory.getDefault() : SocketFactory.getDefault(); addIfNotFound = properties.booleanForKeyWithDefault( baseName + ".addIfNotFound", true ); return result; } // ---------------------------------------------------------- /** * Validate the user `username' with the password `password'. * Should not be called until the authenticator has been configured. * * @param userName The user id to validate * @param password The password to check * @param domain The authentication domain associated with this check * @param ec The editing context to use * @return The current user object, or null if invalid login */ public User authenticate( String userName, String password, AuthenticationDomain domain, com.webobjects.eocontrol.EOEditingContext ec ) { User user = null; if ( validatePid( userName, password ) == rPidPswdGood ) { log.debug( "user " + userName + " validated" ); try { user = (User)EOUtilities.objectMatchingValues( ec, User.ENTITY_NAME, new NSDictionary<String, Object>( new Object[]{ userName , domain }, new String[]{ User.USER_NAME_KEY, User.AUTHENTICATION_DOMAIN_KEY } ) ); if ( user.authenticationDomain() != domain ) { if ( user.authenticationDomain() == null ) { user.setAuthenticationDomainRelationship( domain ); } else { log.warn( "user " + userName + " successfully validated in '" + domain.displayableName() + "' but bound to '" + user.authenticationDomain().displayableName() + "'" ); user = null; } } } catch ( EOObjectNotAvailableException e ) { if ( addIfNotFound ) { user = User.createUser( userName, null, // DO NOT MIRROR PASSWORD IN DATABASE // for security reasons domain, User.STUDENT_PRIVILEGES, ec ); log.info( "new user '" + userName + "' (" + domain.displayableName() + ") created" ); } else { log.info( "user " + userName + " successfully validated in '" + domain.displayableName() + "' but does not exist (not created)" ); } } catch ( EOUtilities.MoreThanOneException e ) { log.error( "user '" + userName + "' (" + domain.displayableName() + "):", e ); } } else { log.info( "user " + userName + "(" + domain.displayableName() + "): login validation failed" ); } return user; } // ---------------------------------------------------------- /** * Connects to POP3 server via a socket and tries to validate the * username/password pair. * <pre> * protocol used : telnet * server type : mail server * security : none -- clear text transmissions * * RETURN CODES: * -------------------------------------------+------------ * Event Exit Code * -------------------------------------------+------------ * Abnormal Termination of function | -1 * PID,PSWD OK | 1 * PID bad | 2 * PSWD bad | 3 * PSWD expired | 4 * incorrect number of command line parameters| 5 * auth server is down | 6 * -------------------------------------------+------------ * </pre> * * @param pid The user name to use * @param password The password to check * @return Result code, see table above */ public int validatePid( String pid, String password ) { // exit code int result = rAbnormalTermination; if ( server == null ) return result; Socket socket = null; try { // create socket connection to pop server socket = socketFactory.createSocket( server, port ); // stream which comes from server BufferedReader serverIn = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); //stream which goes out to server PrintStream meOut = new PrintStream( socket.getOutputStream() ); // converse with server... doing authentication // first -- read server's greeting line and check to see // if we made a connection String serverSaid = serverIn.readLine(); if ( serverSaid.indexOf( "+OK" ) != -1 ) { // send the user name meOut.println( "user " + pid ); // ignore the server's message to send password serverIn.readLine(); // send the password meOut.println( "pass " + password ); // read the server's response serverSaid = serverIn.readLine(); // close the connection meOut.println( "quit" ); result = rPswdBad; if ( serverSaid.indexOf( "+OK" ) != -1 ) { result = rPidPswdGood; } if ( serverSaid.indexOf( "Can't find your" ) != -1 ) { result = rPidBad; } } serverIn.close(); //close connection to server } catch ( IOException ioe ) { log.error( "Error talking to pop server", ioe ); result = rServerError; } finally { if ( socket != null ) { try { socket.close(); } catch ( IOException ioe ) { log.info( "Error closing pop server socket", ioe ); } } } return result; } // ---------------------------------------------------------- /** * Check whether users validated with this authenticator can * change their password. For authentication mechanisms using * external databases or servers where no changes are allowed, the * authenticator should return false. * * @return True if users associated with this authenticator can * change their password */ public boolean canChangePassword() { return false; } // ---------------------------------------------------------- /** * Change the user's password. For authentication mechanisms using * external databases or servers where no changes are allowed, an * authenticator may simply return false for all requests. * * @param user The user * @param newPassword The password to change to * @return True if the password change was successful */ public boolean changePassword( User user, String newPassword ) { return false; } // ---------------------------------------------------------- /** * Change the user's password to a new random password, and e-mail's * the user their new password. For authentication mechanisms using * external databases or servers where no changes are allowed, an * authenticator may simply return false for all requests. * * @param user The user * @return True if the password change was successful */ public boolean newRandomPassword( User user ) { return false; } //~ Instance/static variables ............................................. static Logger log = Logger.getLogger( PopAuthenticator.class ); private int port; private String server; private SocketFactory socketFactory; private boolean addIfNotFound; // return codes static final int rAbnormalTermination = -1; static final int rPidPswdGood = 1; static final int rPidBad = 2; static final int rPswdBad = 3; static final int rPswdExpired = 4; static final int rInvalidCmdLineParam = 5; static final int rServerError = 6; static final int rCantUnderstandServer = 7; }