com.adito.community.unix.UNIXUserDatabase.java Source code

Java tutorial

Introduction

Here is the source code for com.adito.community.unix.UNIXUserDatabase.java

Source

/*
*  Adito
*
*  Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU General Public License
*  as published by the Free Software Foundation; either version 2 of
*  the License, or (at your option) any later version.
*  This program 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 General Public
*  License along with this program; if not, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

package com.adito.community.unix;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.adito.boot.ContextHolder;
import com.adito.boot.SystemProperties;
import com.adito.boot.Util;
import com.adito.core.CoreServlet;
import com.adito.realms.Realm;
import com.adito.security.AccountLockedException;
import com.adito.security.DefaultUserDatabase;
import com.adito.security.InvalidLoginCredentialsException;
import com.adito.security.Role;
import com.adito.security.User;
import com.adito.security.UserDatabaseException;
import com.adito.security.UserNotFoundException;
import com.adito.unixauth.BCrypt;
import com.adito.unixauth.DESCrypt;
import com.adito.unixauth.MD5Crypt;
import com.adito.unixauth.UNIXRole;
import com.adito.unixauth.UNIXUser;

public class UNIXUserDatabase extends DefaultUserDatabase {
    private static final Log LOG = LogFactory.getLog(UNIXUserDatabase.class);
    private static final File GROUP_FILE = new File("/etc/group");
    private static final File PASSWD_FILE = new File("/etc/passwd");
    private static final File SHADOW_FILE = new File("/etc/shadow");
    private static final File USER_EMAIL_MAP_FILE = new File(ContextHolder.getContext().getConfDirectory(),
            "userEmailMap.properties");

    private UNIXRole[] roles;
    private UNIXUser[] users;
    private Map<String, char[]> shadowPasswords;
    private Date lastGroupFileChange;
    private Date lastPasswdFileChange;
    private Date lastShadowFileChange;
    private Properties userEmailMap = new Properties();
    private long userEmailMapLastModified = -1;

    /**
     * Constant for the database type.
     */
    public static final String DATABASE_TYPE = "unixAuth";

    public UNIXUserDatabase() {
        super("Unix", false, false);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.adito.core.Database#open(com.adito.core.CoreServlet)
     */
    public void open(CoreServlet controllingServlet, Realm realm) throws Exception {
        String osName = SystemProperties.get("os.name", "").toLowerCase();
        if (!osName.startsWith("linux") && !osName.startsWith("solaris")) {
            LOG.warn(
                    "The UNIXAuth plugin will only be likely to work on Linux based systems, Solaris or other operating systems "
                            + "that use /etc/passwd, /etc/group and /etc/shadow. OpenBSD and FreeBSD will definately *not* work.");
        }
        open = true;
        if (SystemProperties.get("adito.unix.passwordChange", "false").equals("true")) {
            if (new File("/usr/sbin/chpasswd").exists()) {
                if (LOG.isInfoEnabled()) {
                    LOG.info("Found chpasswd, enabling experimental password change support.");
                }
                supportsPasswordChange = true;
            }
        }
        this.realm = realm;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.adito.security.UserDatabase#logon(java.lang.String,
     *      java.lang.String)
     */
    public User logon(String username, String password)
            throws UserDatabaseException, InvalidLoginCredentialsException, AccountLockedException {
        if (!checkPassword(username, password)) {
            throw new InvalidLoginCredentialsException();
        }
        try {
            return getAccount(username);
        } catch (Exception e) {
            throw new UserDatabaseException("Failed to get user account.", e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.adito.security.UserDatabase#checkPassword(java.lang.String,
     *      java.lang.String, int)
     */
    public boolean checkPassword(String username, String password)
            throws UserDatabaseException, InvalidLoginCredentialsException {
        // Get the user account
        UNIXUser user = null;
        try {
            user = (UNIXUser) getAccount(username);
        } catch (Exception e) {
            throw new UserDatabaseException("Could not get user account", e);
        }

        // Make sure the user exists
        if (user == null) {
            throw new InvalidLoginCredentialsException();
        }

        // Determine the password type
        String pw = new String(user.getPassword());
        try {
            if (pw.startsWith("$1$")) {
                // MD5
                return pw.substring(12).equals(MD5Crypt.crypt(password, pw.substring(3, 11)).substring(12));
            } else if (pw.startsWith("$2a$")) {
                // Blowfish
                return BCrypt.checkpw(password, pw);
            } else {
                // DES
                return DESCrypt.crypt(pw.substring(0, 2), password).equals(pw.substring(2));
            }
        } catch (Exception e) {
            throw new UserDatabaseException("Invalid password format.", e);
        }
    }

    public void logout(User user) {
    }

    @SuppressWarnings("unchecked")
    public Iterable<User> allUsers() throws UserDatabaseException {
        try {
            checkPasswdFile();
        } catch (Exception e) {
            throw new UserDatabaseException("failed to list all users", e);
        }
        return (Iterable<User>) (List<? extends User>) Arrays.asList(users);
    }

    public User getAccount(String username) throws UserNotFoundException, Exception {
        try {
            checkPasswdFile();
            for (int i = 0; i < users.length; i++) {
                if (users[i].getPrincipalName().equals(username)) {
                    return users[i];
                }
            }
            throw new UserNotFoundException("Could not find user " + username);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    public UNIXRole getRole(String rolename) throws Exception {
        checkGroupFile();
        for (int i = 0; i < roles.length; i++) {
            if (roles[i].getPrincipalName().equals(rolename)) {
                return roles[i];
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public Iterable<Role> allRoles() throws UserDatabaseException {
        try {
            checkGroupFile();
        } catch (Exception e) {
            throw new UserDatabaseException("failed to list all roles", e);
        }
        return (Iterable<Role>) (List<? extends Role>) Arrays.asList(roles);
    }

    private void checkGroupFile() throws Exception {
        Date current = null;
        if (GROUP_FILE.exists()) {
            current = new Date(GROUP_FILE.lastModified());
            if (lastGroupFileChange == null || !lastGroupFileChange.equals(current)) {
                lastGroupFileChange = current;
                String line = null;
                FileInputStream fin = new FileInputStream(GROUP_FILE);
                List<UNIXRole> rolesList = new ArrayList<UNIXRole>();
                try {
                    BufferedReader r = new BufferedReader(new InputStreamReader(fin));
                    while ((line = r.readLine()) != null) {
                        try {
                            rolesList.add(new UNIXRole(getRealm(), line));
                        } catch (IllegalArgumentException iae) {
                        }
                    }
                } finally {
                    Util.closeStream(fin);
                }
                Collections.sort(rolesList);
                roles = new UNIXRole[rolesList.size()];
                rolesList.toArray(roles);
            }
        } else {
            throw new IOException("Could not locate " + GROUP_FILE.getAbsolutePath());
        }
    }

    private void checkPasswdFile() throws Exception {
        Date current = null;
        if (PASSWD_FILE.exists()) {
            if (checkShadowFile()) {
                lastPasswdFileChange = null;
            }
            if (checkUserEmailMapFile()) {
                lastPasswdFileChange = null;
            }
            current = new Date(PASSWD_FILE.lastModified());
            if (lastPasswdFileChange == null || !lastPasswdFileChange.equals(current)) {
                lastPasswdFileChange = current;
                String line = null;
                FileInputStream fin = new FileInputStream(PASSWD_FILE);
                List<User> userList = new ArrayList<User>();
                try {
                    BufferedReader r = new BufferedReader(new InputStreamReader(fin));
                    while ((line = r.readLine()) != null) {
                        String[] elements = line.split(":");
                        String username = elements[0];
                        if (elements.length > 5) {
                            String password = elements[1];
                            int uid = Integer.parseInt(elements[2]);
                            int gid = Integer.parseInt(elements[3]);
                            String fullname = elements[4];
                            String home = elements[5];
                            String shell = "";
                            if (elements.length > 6) {
                                shell = elements[6];
                            }
                            List<UNIXRole> userRolesList = new ArrayList<UNIXRole>();
                            UNIXRole primaryRole = getRoleByGID(gid);
                            if (primaryRole == null) {
                                LOG.warn("No primary group for user " + username);
                            } else {
                                userRolesList.add(primaryRole);
                            }
                            for (int i = 0; i < roles.length; i++) {
                                if (roles[i].containsMember(username) && !(primaryRole != null
                                        && roles[i].getPrincipalName().equals(primaryRole.getPrincipalName()))) {
                                    userRolesList.add(roles[i]);
                                }
                            }
                            UNIXRole[] userRoles = new UNIXRole[userRolesList.size()];
                            userRolesList.toArray(userRoles);
                            char[] pw = null;
                            if (password.equals("x")) {
                                pw = (char[]) shadowPasswords.get(username);
                                if (pw == null) {
                                    // No shadow password, continue to the next
                                    // user
                                    LOG.warn("User " + username
                                            + " has 'x' as password indicating a shadow password. However, "
                                            + "either the shadow file does not exist or an entry for this user "
                                            + "does not exist. User has been omitted");
                                    continue;
                                }
                            } else {
                                pw = password.toCharArray();
                            }
                            UNIXUser user = new UNIXUser(username,
                                    userEmailMap == null ? "" : userEmailMap.getProperty(username, ""), pw, uid,
                                    gid, fullname, home, shell, userRoles, this.getRealm());
                            userList.add(user);
                        }
                    }
                } finally {
                    Util.closeStream(fin);
                }
                Collections.sort(userList);
                users = new UNIXUser[userList.size()];
                userList.toArray(users);
            }
        } else {
            throw new IOException("Could not locate " + PASSWD_FILE.getAbsolutePath());
        }
    }

    private synchronized boolean checkShadowFile() throws Exception {
        Date current = null;
        shadowPasswords = new HashMap<String, char[]>();
        if (SHADOW_FILE.exists()) {
            current = new Date(SHADOW_FILE.lastModified());
            if (lastShadowFileChange == null || !lastShadowFileChange.equals(current)) {
                lastShadowFileChange = current;
                String line = null;
                FileInputStream fin = new FileInputStream(SHADOW_FILE);
                try {
                    BufferedReader r = new BufferedReader(new InputStreamReader(fin));
                    while ((line = r.readLine()) != null) {
                        String[] elements = line.split(":");
                        String username = elements[0];
                        if (elements.length > 1 && !username.equals("+")) {
                            char[] password = elements[1].toCharArray();
                            shadowPasswords.put(username, password);
                        }
                    }
                } finally {
                    Util.closeStream(fin);
                }
                return true;
            }
        } else {
            // The shadow did exist but does now not - unlikely to happen!
            if (lastShadowFileChange != null) {
                return true;
            }
        }
        return false;
    }

    private synchronized boolean checkUserEmailMapFile() throws Exception {
        if (!USER_EMAIL_MAP_FILE.exists()) {
            if (userEmailMap != null) {
                userEmailMap = null;
                userEmailMapLastModified = -1;
                return true;
            }
        } else if (userEmailMap == null) {
            userEmailMap = new Properties();
        }
        if (userEmailMap != null && (userEmailMapLastModified == -1
                || userEmailMapLastModified != USER_EMAIL_MAP_FILE.lastModified())) {
            FileInputStream fin = null;
            try {
                fin = new FileInputStream(USER_EMAIL_MAP_FILE);
                userEmailMap.load(fin);
            } catch (IOException ioe) {
                LOG.error("Failed to load user email map.");
            } finally {
                Util.closeStream(fin);
            }
            userEmailMapLastModified = USER_EMAIL_MAP_FILE.lastModified();
            return true;
        }
        return false;
    }

    /**
     * @param gid
     * @return
     */
    private UNIXRole getRoleByGID(int gid) throws Exception {
        checkGroupFile();
        for (int i = 0; i < roles.length; i++) {
            if (roles[i].getGid() == gid) {
                return roles[i];
            }
        }
        return null;
    }

    public void cleanup() throws Exception {
    }

    public boolean isOpen() {
        return open;
    }

    public void changePassword(String username, String oldPassword, String password,
            boolean forcePasswordChangeAtLogon) throws UserDatabaseException, InvalidLoginCredentialsException {
        if (!supportsPasswordChange()) {
            throw new InvalidLoginCredentialsException("Database doesn't support password change.");
        }
        if (forcePasswordChangeAtLogon) {
            LOG.warn(
                    "Password change function of UNIX user database does not support forcePassswordChangeAtLogon.");
        }
        Process p = null;
        try {
            p = Runtime.getRuntime()
                    .exec("true".equals(SystemProperties.get("adito.useDevConfig", "false"))
                            ? "sudo /usr/sbin/chpasswd"
                            : "/usr/sbin/chpasswd");
            new StreamReaderThread(p.getInputStream());
            new StreamReaderThread(p.getErrorStream());
            OutputStream out = p.getOutputStream();
            PrintWriter pw = new PrintWriter(out);
            pw.println(username + ":" + password);
            pw.flush();
            out.close();
            try {
                p.waitFor();
            } catch (InterruptedException ie) {

            }
            int ret = p.exitValue();
            if (ret != 0) {
                throw new UserDatabaseException(
                        "Failed to change password. chpasswd returned exit code " + ret + ".");
            }

        } catch (IOException e) {
            throw new UserDatabaseException("Failed to change password.", e);
        } finally {
            if (p != null) {
                Util.closeStream(p.getOutputStream());
                Util.closeStream(p.getInputStream());
                Util.closeStream(p.getErrorStream());
            }
        }
    }

    private static final class StreamReaderThread extends Thread {
        private final InputStream in;

        private StreamReaderThread(InputStream in) {
            this.in = in;
        }

        public void run() {
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                String line = null;
                while ((line = reader.readLine()) != null) {
                    if (LOG.isInfoEnabled()) {
                        LOG.info("Output from chpasswd: '" + line + "'");
                    }
                }
            } catch (IOException ioe) {
                // nothing to do
            }
        }
    }
}