001    package org.esupportail.cas.server;
002    
003    import java.util.Iterator;
004    import java.util.LinkedList;
005    import java.util.List;
006    import java.util.Date;
007    
008    import java.io.File;
009    import java.io.UnsupportedEncodingException;
010    import java.lang.reflect.Constructor;
011    import java.lang.reflect.InvocationTargetException;
012    import java.net.InetAddress;
013    import java.net.URLDecoder;
014    import java.net.UnknownHostException;
015    
016    import javax.servlet.ServletRequest;
017    
018    import org.dom4j.Document;
019    import org.dom4j.DocumentException;
020    import org.dom4j.DocumentHelper;
021    import org.dom4j.Element;
022    import org.dom4j.io.SAXReader;
023    import org.dom4j.XPath;
024    import org.esupportail.cas.server.util.BasicHandler;
025    import org.esupportail.cas.server.util.MisconfiguredHandlerException;
026    import org.esupportail.cas.server.util.RedundantHandler;
027    import org.esupportail.cas.server.util.Server;
028    import org.esupportail.cas.server.util.log.Debug;
029    import org.esupportail.cas.server.util.log.Log;
030    
031    import edu.yale.its.tp.cas.auth.provider.WatchfulPasswordHandler;
032    
033    
034    //---------------------------------------------------------
035    
036    /**
037     * This class permits via a Xml configuration file to call different specific handler
038     *
039     * @author Pascal Aubry <pascal.aubry at univ-rennes1.fr>
040     * @author Jean-Baptiste Daniel <danielj at sourceforge.net>
041     */
042    
043    public final class GenericHandler extends WatchfulPasswordHandler {    
044            
045            /**
046             * the package release number.
047             */
048            private static final String CASGENERICHANDLER_RELEASE = "2.1.2-1";
049            
050            /**
051             * Debugging mode.
052             */
053            private static boolean debug = false;
054            
055            /**
056             * the list of all the specific handlers to try for authentication.
057             */
058            private static List handlers = null;
059            
060            /**
061             * the name of the configuration file.
062             */
063            private static String configFilename = null;
064            
065            /**
066             * a boolean to store wheter empty passwords can be accepted or not.
067             */
068            private static boolean acceptEmptyPasswords;
069            
070            /**
071             * Retrieve the name of the file storing the configuration.
072             *
073             * @return a string.
074             */
075            private static String getConfigFileName() {
076                    java.net.URL resourceURL;
077    
078                    if (configFilename == null) {
079                            resourceURL = GenericHandler.class.getResource("/../genericHandler.xml");
080                            if (resourceURL == null) {
081                                    Log.warn("Configuration file genericHandler.xml is missing!");
082                            }
083                            // if configuration file is missing an exception is thrown and the cas server crashes
084                            configFilename = resourceURL.getFile();
085    
086                            // decode string to allow paths with spaces (since version 2.0.4)
087                            // J-Louis.RENAUD at univ-bpclermont.fr
088                try {
089                    configFilename = URLDecoder.decode(configFilename, "UTF-8");
090                } catch (UnsupportedEncodingException e) {
091                                    Log.warn("Configuration filename could not be decoded!");
092                }
093                    }
094                    return configFilename;
095            }
096            
097            /**
098             * the last time the configuration file was modified
099             */
100            private static long configDate = 0;
101            
102            /**
103             * tell if the configuration file was modified.
104             *
105             * @return true if the file was modified since last configuration reading.
106             */
107            private static boolean configFileModified() {
108                    return new File(getConfigFileName()).lastModified() > configDate;
109            }
110            
111            /**
112             * Scans an XML configuration file and builds the structure needed 
113             * for authentication (a list of specific handlers).
114             *
115             * @throws MisconfiguredHandlerException MisconfiguredHandlerException
116             */
117            private void readConfigFile() throws MisconfiguredHandlerException {
118                    
119                    // create an empty list of handlers
120                    List newHandlers = new LinkedList();
121                    
122                    Document config;
123                    XPath xPathSelector;
124                    Element authenticationElement;
125                    
126                    // create a SAXReader component
127                    SAXReader saxReader = new SAXReader(false);
128                    
129                    // parse the configuration file
130                    try {
131                            config = saxReader.read(getConfigFileName());
132                    } catch (DocumentException e) {
133                            throw new MisconfiguredHandlerException("Configuration file is ill-formed: " + e.getMessage());
134                    }
135                    
136                    // check the root node
137                    xPathSelector = DocumentHelper.createXPath("/authentication");
138                    try {
139                            authenticationElement = (Element) xPathSelector.selectNodes(config).get(0);
140                    } catch (IndexOutOfBoundsException e) {
141                            throw new MisconfiguredHandlerException("Root <authentication> tag not found.");
142                    }
143                    boolean configDebug = Debug.elementDebugValue(authenticationElement, false);
144                    if (configDebug) {
145                            Log.debug("Debugging mode set to \"on\".");
146                    }
147                    boolean newAcceptEmptyPasswords = authenticationElement.attributeValue(
148                                    "empty_password_accepted", 
149                                    "off").equals("on");
150                    if (configDebug) {
151                            if (newAcceptEmptyPasswords) {
152                                    Log.debug("Empty passwords will be accepted.");
153                            } else {
154                                    Log.debug("Empty passwords will not be accepted.");
155                            }
156                    }
157                    // get the list of all the handlers             
158                    xPathSelector = DocumentHelper.createXPath("/authentication/*");
159                    List handlerElements = xPathSelector.selectNodes(config);
160                    
161                    // check that at least one handler is declared 
162                    if (handlerElements.isEmpty()) {
163                            throw new MisconfiguredHandlerException("No \"handler\" element found.");
164                    }
165    
166                    for (Iterator i = handlerElements.iterator(); i.hasNext();) {
167                            Element handlerElement = (Element) i.next();
168                            
169                            // check that all the sub-elements are handlers
170                            if (!handlerElement.getName().equals("handler")) {
171                                    throw new MisconfiguredHandlerException(
172                                                    "Unknown \"" 
173                                                    + handlerElement.getName() 
174                                                    + "\" XML element found.");
175                            }
176                            if (configDebug) { Log.debug("Found a handler."); }
177                            
178                            // extract the classname
179                            Element handlerClassnameElement = handlerElement.element("classname");
180                            if (handlerClassnameElement == null) {
181                                    throw new MisconfiguredHandlerException(
182                                                    "No \"classname\" element found for a handler.");
183                            }
184                            String handlerClassname = handlerClassnameElement.getTextTrim();
185                            if (handlerClassname.equals("")) {
186                                    throw new MisconfiguredHandlerException(
187                                                    "Empty \"classname\" element found for a handler.");
188                            }
189                            if (configDebug) { Log.debug("This is a \"" + handlerClassname + "\" handler."); }
190    
191                            // try to load the class
192                            Class handlerClass;
193                            try {
194                                    handlerClass = Class.forName(handlerClassname);
195                            } catch (ClassNotFoundException e) {
196                                    throw new MisconfiguredHandlerException(
197                                                    "Class \"" + handlerClassname + "\" could not be loaded.");
198                            }
199    
200                            // get the constructor
201                            Class[] handlerConstructorArgumentTypes = {Element.class, Boolean.class};
202                            Constructor handlerConstructor;
203                            try {
204                                    handlerConstructor = handlerClass.getConstructor(handlerConstructorArgumentTypes);
205                            } catch (Exception e) {
206                                    throw new MisconfiguredHandlerException(
207                                                    "The constructor of Class \"" 
208                                                    + handlerClassname 
209                                                    + "\" could not be loaded because a \"" 
210                                                    + e.getClass().getName() 
211                                                    + "\" exception was raised: " 
212                                                    + e.getMessage());
213                            }
214    
215                            // create an instance
216                            Object[] handlerConstructorArguments = { 
217                                            handlerElement, 
218                                            new Boolean(configDebug) };
219                            BasicHandler handler;
220                            try {
221                                    handler = (BasicHandler) handlerConstructor.newInstance(handlerConstructorArguments);
222                            } catch (InvocationTargetException e) {
223                                    throw new MisconfiguredHandlerException(e.getCause().getMessage());
224                            } catch (Exception e) {
225                                    throw new MisconfiguredHandlerException(
226                                                    "Class \"" 
227                                                    + handlerClassname 
228                                                    + "\" could not be instanciated because a \"" 
229                                                    + e.getClass().getName() 
230                                                    + "\" exception was raised: " 
231                                                    + e.getMessage());
232                            }
233    
234                            newHandlers.add(handler);
235                    }
236                    
237                    handlers = newHandlers;
238                    debug = configDebug;
239                    acceptEmptyPasswords = newAcceptEmptyPasswords;
240                    
241                    // while we created handlers and servers, we set the debugging mode
242                    // on each object depending on the confifuration. Now we set debugging mode
243                    // to on for redundant handlers having servers with debugging mode set
244                    // to on, and then the main handler if one specific handler (at least) has
245                    // debugging mode set to on.
246                    for (Iterator i = handlers.iterator(); i.hasNext();) {
247                            BasicHandler handler = (BasicHandler) i.next();
248                            if (handler instanceof RedundantHandler) {
249                                    for (Iterator j = ((RedundantHandler) handler).getServers().iterator(); j.hasNext();) {
250                                            Server server = (Server) j.next();
251                                            if (server.isDebug()) {
252                                                    handler.setDebug(true);
253                                            }
254                                    }
255                            }
256                            if (handler.isDebug()) {
257                                    debug = true;
258                            }
259                    }
260                    
261                    // configuration read without any error
262                    configDate = new Date().getTime();
263            }    
264    
265            /**
266             * Try to authenticate a user by calling all the handlers found in
267             * the configuration file.
268             *
269             * @param request the current request
270             * @param username the username provided by the user
271             * @param password the password provided by the user
272             *
273             * @return true on success, false otherwise.
274             */
275            public synchronized boolean authenticate(
276                            final ServletRequest request, 
277                            final String username, 
278                            final String password) {
279                    if (!super.authenticate(request, username, password)) {
280                            Log.warn("Authentication process was blocked by WatchfullPasswordHandler.");
281                            return false;
282                    }
283                    
284                    if (configFileModified()) {
285                            Log.info(new String(
286                                            "ESUP-Portail Generic Handler " 
287                                            + CASGENERICHANDLER_RELEASE 
288                                            + ", reading configuration file..."));                  
289                            try {
290                                    readConfigFile();
291                                    Log.info("Configuration file read without any error.");
292                            } catch (MisconfiguredHandlerException e) {
293                                    Log.error("An error occured while reading the configuration file:");
294                                    Log.error(e.getMessage());
295                                    if (handlers == null) {
296                                            Log.warn("No previous configuration, authentication will always fail.");
297                                    } else {
298                                            Log.warn("Keeping the previous configuration.");
299                                    }
300                            }
301                    }       
302    
303                    // as the debug flag is set by readConfigFile(), a trace can be written only at this point
304                    if (debug) { Log.traceBegin(); }
305                    
306                    if (handlers == null) {
307                            Log.warn("No authentication handler found.");
308                    }
309                    
310                    if (username.equals("")) {
311                            if (debug) { Log.debug("Empty username, handlers will not be called."); }
312                    } else if (password.equals("") && !acceptEmptyPasswords) {
313                            if (debug) { Log.debug("Empty password, handlers will not be called."); }
314                    } else if (handlers != null) {
315                            for (Iterator i = handlers.iterator(); i.hasNext();) {
316                                    BasicHandler handler = (BasicHandler) i.next();
317                                    switch (handler.authenticate(username, password)) {
318                                            case BasicHandler.SUCCEEDED:
319                                                    String hostAddress = null;
320                                                    String hostName = null;
321                                                    String remoteAddr = request.getRemoteAddr();
322                                                    if (remoteAddr != null) {
323                                                            try {
324                                                                    InetAddress address = InetAddress.getByName(remoteAddr);
325                                                                    hostName = address.getHostName();
326                                                                    hostAddress = address.getHostAddress();
327                                                            } catch (UnknownHostException e) {
328                                                                    // resolution failed
329                                                            }
330                                                    }
331                                                    String msg = "Authentication succeeded for user `" + username + "'";
332                                                    if (hostAddress != null && hostName != null) {
333                                                            msg = msg + " from '" + hostAddress + "' (" + hostName + ")";
334                                                    } else if (hostAddress != null) {
335                                                            msg = msg + " from '" + hostAddress + "'";
336                                                    } else if (hostName != null) {
337                                                            msg = msg + " from '" + hostName + "'";
338                                                    }
339                                                    msg = msg + ".";
340                                                    Log.info(msg);
341                                                    if (debug) { Log.traceEnd(String.valueOf(true)); }
342                                                    return true;
343                                            case BasicHandler.FAILED_STOP:
344                                                    Log.info("Authentication failed for user `" + username + "'.");
345                                                    if (debug) { Log.traceEnd(String.valueOf(false)); }
346                                                    return false;
347                                            default: // BasicHandler.FAILED_CONTINUE
348                                    }
349                            }
350                    }
351                    Log.info("Authentication failed for user `" + username + "'.");
352                    if (debug) { Log.traceEnd(String.valueOf(false)); }
353                    return false;
354            }
355            
356            
357    }