001    /**
002     *
003     * Copyright 2003-2004 The Apache Software Foundation
004     *
005     *  Licensed under the Apache License, Version 2.0 (the "License");
006     *  you may not use this file except in compliance with the License.
007     *  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.security.realm.providers;
018    
019    import java.io.File;
020    import java.io.FileOutputStream;
021    import java.io.IOException;
022    import java.io.PrintWriter;
023    import java.nio.channels.FileChannel;
024    import java.nio.channels.FileLock;
025    import java.text.DateFormat;
026    import java.text.SimpleDateFormat;
027    import java.util.Date;
028    import java.util.Map;
029    import javax.security.auth.Subject;
030    import javax.security.auth.callback.Callback;
031    import javax.security.auth.callback.CallbackHandler;
032    import javax.security.auth.callback.NameCallback;
033    import javax.security.auth.login.LoginException;
034    import javax.security.auth.spi.LoginModule;
035    
036    import org.apache.geronimo.security.jaas.JaasLoginModuleUse;
037    import org.apache.geronimo.system.serverinfo.ServerInfo;
038    
039    /**
040     * Writes audit records to a file for all authentication activity.  Currently
041     * doesn't perform too well; perhaps the file management should be centralized
042     * and the IO objects kept open across many requests.  It would also be nice
043     * to write in a more convenient XML format.
044     *
045     * This module does not write any Principals into the Subject.
046     *
047     * To enable this login module, set your primary login module to REQUIRED or
048     * OPTIONAL, and list this module after it (with any setting).
049     *
050     * @version $Rev: 355877 $ $Date: 2005-12-10 18:48:27 -0800 (Sat, 10 Dec 2005) $
051     */
052    public class FileAuditLoginModule implements LoginModule {
053        public static final String LOG_FILE_OPTION = "file";
054        private final static DateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
055        private File logFile;
056        private CallbackHandler handler;
057        private String username;
058    
059        public void initialize(Subject subject, CallbackHandler callbackHandler,
060                               Map sharedState, Map options) {
061            String name = (String) options.get(LOG_FILE_OPTION);
062            ServerInfo info = (ServerInfo) options.get(JaasLoginModuleUse.SERVERINFO_LM_OPTION);
063            logFile = info.resolve(name);
064            handler = callbackHandler;
065        }
066    
067        public boolean login() throws LoginException {
068            NameCallback user = new NameCallback("User name:");
069            Callback[] callbacks = new Callback[]{user};
070            try {
071                handler.handle(callbacks);
072            } catch (Exception e) {
073                throw new LoginException("Unable to process callback: "+e);
074            }
075            if(callbacks.length != 1) {
076                throw new IllegalStateException("Number of callbacks changed by server!");
077            }
078            user = (NameCallback) callbacks[0];
079            username = user.getName();
080            writeToFile("Authentication attempt");
081    
082            return true;
083        }
084    
085        private synchronized void writeToFile(String action) {
086            Date date = new Date();
087            try {
088                FileOutputStream out = new FileOutputStream(logFile, true);
089                FileChannel channel = out.getChannel();
090                FileLock lock = channel.lock(0, Long.MAX_VALUE, false);
091                PrintWriter writer = new PrintWriter(out, false);
092                writer.println(DATE_FORMAT.format(date)+" - "+action+" - "+username);
093                writer.flush();
094                writer.close();
095                if(lock.isValid()) {
096                    lock.release();
097                }
098            } catch (IOException e) {
099                throw new RuntimeException("Unable to write to authentication log file", e);
100            }
101        }
102    
103        public boolean commit() throws LoginException {
104            writeToFile("Authentication succeeded");
105            return true;
106        }
107    
108        public boolean abort() throws LoginException {
109            if(username != null) { //work around initial "fake" login
110                writeToFile("Authentication failed");
111                username = null;
112            }
113            return true;
114        }
115    
116        public boolean logout() throws LoginException {
117            writeToFile("Explicit logout");
118            username = null;
119            return true;
120        }
121    }