Java tutorial
/* * 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 } } } }