1 package com.sslexplorer.unixauth; 2 3 import java.io.BufferedReader ; 4 import java.io.File ; 5 import java.io.FileInputStream ; 6 import java.io.IOException ; 7 import java.io.InputStream ; 8 import java.io.InputStreamReader ; 9 import java.io.OutputStream ; 10 import java.io.PrintWriter ; 11 import java.util.ArrayList ; 12 import java.util.Collections ; 13 import java.util.Date ; 14 import java.util.HashMap ; 15 import java.util.Iterator ; 16 import java.util.List ; 17 import java.util.Properties ; 18 import java.util.regex.Matcher ; 19 import java.util.regex.Pattern ; 20 21 import org.apache.commons.logging.Log; 22 import org.apache.commons.logging.LogFactory; 23 24 import com.sslexplorer.boot.ContextHolder; 25 import com.sslexplorer.boot.PropertyList; 26 import com.sslexplorer.boot.Util; 27 import com.sslexplorer.core.CoreServlet; 28 import com.sslexplorer.core.CoreUtil; 29 import com.sslexplorer.properties.Property; 30 import com.sslexplorer.properties.impl.realms.RealmKey; 31 import com.sslexplorer.realms.Realm; 32 import com.sslexplorer.security.AccountLockedException; 33 import com.sslexplorer.security.DefaultUserDatabase; 34 import com.sslexplorer.security.InvalidLoginCredentialsException; 35 import com.sslexplorer.security.Role; 36 import com.sslexplorer.security.User; 37 import com.sslexplorer.security.UserDatabaseException; 38 import com.sslexplorer.security.UserNotFoundException; 39 40 public class UNIXUserDatabase extends DefaultUserDatabase { 41 42 final static Log log = LogFactory.getLog(UNIXUserDatabase.class); 43 44 final static File GROUP_FILE = new File ("/etc/group"); 45 final static File PASSWD_FILE = new File ("/etc/passwd"); 46 final static File SHADOW_FILE = new File ("/etc/shadow"); 47 final static File USER_EMAIL_MAP_FILE = new File (ContextHolder.getContext().getConfDirectory(), "userEmailMap.properties"); 48 49 private UNIXRole[] roles; 50 private UNIXUser[] users; 51 private HashMap shadowPasswords; 52 private Date lastGroupFileChange, lastPasswdFileChange, lastShadowFileChange; 53 private PropertyList administrators; 54 private Properties userEmailMap = new Properties (); 55 private long userEmailMapLastModified = -1; 56 57 60 public static final String DATABASE_TYPE = "unixAuth"; 61 62 public UNIXUserDatabase() { 63 super("Unix", false, false); 64 } 65 66 71 public void open(CoreServlet controllingServlet, Realm realm) throws Exception { 72 administrators = Property.getPropertyList(new RealmKey("security.administrators", realm.getResourceId())); 73 String osName = System.getProperty("os.name", "").toLowerCase(); 74 if (!osName.startsWith("linux") && !osName.startsWith("solaris")) { 75 log.warn("The UNIXAuth plugin will only be likely to work on Linux based systems, Solaris or other operating systems " 76 + "that use /etc/passwd, /etc/group and /etc/shadow. OpenBSD and FreeBSD will definately *not* work."); 77 } 78 open = true; 79 if (System.getProperty("sslexplorer.unix.passwordChange", "false").equals("true")) { 80 if (new File ("/usr/sbin/chpasswd").exists()) { 81 if (log.isInfoEnabled()) 82 log.info("Found chpasswd, enabling experimental password change support."); 83 supportsPasswordChange = true; 84 } 85 } 86 this.realm = realm; 87 } 88 89 92 public User logon(String username, String password) throws UserDatabaseException, InvalidLoginCredentialsException, 93 AccountLockedException { 94 if (!checkPassword(username, password)) { 95 throw new InvalidLoginCredentialsException(); 96 } 97 try { 98 return getAccount(username); 99 } catch (Exception e) { 100 throw new UserDatabaseException("Failed to get user account.", e); 101 } 102 } 103 104 107 public boolean checkPassword(String username, String password) throws UserDatabaseException, InvalidLoginCredentialsException { 108 UNIXUser user = null; 110 try { 111 user = (UNIXUser) getAccount(username); 112 } catch (Exception e) { 113 throw new UserDatabaseException("Could not get user account", e); 114 } 115 116 if (user == null) { 118 throw new InvalidLoginCredentialsException(); 119 } 120 121 String pw = new String (user.getPassword()); 123 try { 124 if (pw.startsWith("$1$")) { 125 return pw.substring(12).equals(MD5Crypt.crypt(password, pw.substring(3, 11)).substring(12)); 127 128 } else { 129 return DESCrypt.crypt(pw.substring(0, 2), password).equals(pw.substring(2)); 131 } 132 } catch (Exception e) { 133 throw new UserDatabaseException("Invalid password format.", e); 134 } 135 } 136 137 public void logout(User user) { 138 } 139 140 public User[] listAllUsers(String filter) throws Exception { 141 checkPasswdFile(); 142 if (!filter.equals("*")) { 143 List l = new ArrayList (); 144 String wildCard = "^" + CoreUtil.replaceAllTokens(filter, "*", ".*") + "$"; 145 Pattern p = Pattern.compile(wildCard, Pattern.CASE_INSENSITIVE); 146 for (int i = 0; i < users.length; i++) { 147 Matcher matcher = p.matcher(users[i].getPrincipalName()); 148 if (matcher.matches()) { 149 l.add(users[i]); 150 } 151 } 152 User[] u = new User[l.size()]; 153 l.toArray(u); 154 return u; 155 } else { 156 return users; 157 } 158 } 159 160 public com.sslexplorer.policyframework.Principal[] listAvailablePrincipals() throws Exception { 161 return listAllUsers("*"); 162 } 163 164 public com.sslexplorer.policyframework.Principal getPrincipal(String principalName) throws Exception { 165 return getAccount(principalName); 166 } 167 168 public User getAccount(String username) throws UserNotFoundException, Exception { 169 try { 170 checkPasswdFile(); 171 for (int i = 0; i < users.length; i++) { 172 if (users[i].getPrincipalName().equals(username)) { 173 return users[i]; 174 } 175 } 176 throw new UserNotFoundException("Could not find user " + username); 177 } catch (Exception e) { 178 e.printStackTrace(); 179 throw e; 180 } 181 } 182 183 public boolean isDefaultAdministrator(com.sslexplorer.policyframework.Principal principal) throws Exception { 184 boolean found = false; 185 if (principal.getPrincipalName().equals("root")) { 186 found = true; 187 } else { 188 for (Iterator j = administrators.iterator(); !found && j.hasNext();) { 189 found = principal.getPrincipalName().matches((String ) j.next()); 190 } 191 } 192 return found; 193 } 194 195 public Role getRole(String rolename) throws Exception { 196 checkGroupFile(); 197 for (int i = 0; i < roles.length; i++) { 198 if (roles[i].getPrincipalName().equals(rolename)) { 199 return roles[i]; 200 } 201 } 202 return null; 203 } 204 205 public Role[] listAllRoles(String filter) throws Exception { 206 checkGroupFile(); 207 if (!filter.equals("*")) { 208 List l = new ArrayList (); 209 String wildCard = "^" + CoreUtil.replaceAllTokens(filter, "*", ".*") + "$"; 210 Pattern p = Pattern.compile(wildCard, Pattern.CASE_INSENSITIVE); 211 for (int i = 0; i < roles.length; i++) { 212 Matcher matcher = p.matcher(roles[i].getPrincipalName()); 213 if (matcher.matches()) { 214 l.add(roles[i]); 215 } 216 } 217 return (Role[]) l.toArray(new Role[l.size()]); 218 } else { 219 return roles; 220 } 221 } 222 223 private void checkGroupFile() throws Exception { 224 Date current = null; 225 if (GROUP_FILE.exists()) { 226 current = new Date (GROUP_FILE.lastModified()); 227 if (lastGroupFileChange == null || !lastGroupFileChange.equals(current)) { 228 lastGroupFileChange = current; 229 String line = null; 230 FileInputStream fin = new FileInputStream (GROUP_FILE); 231 List rolesList = new ArrayList (); 232 try { 233 BufferedReader r = new BufferedReader (new InputStreamReader (fin)); 234 while ((line = r.readLine()) != null) { 235 try { 236 rolesList.add(new UNIXRole(getRealm(), line)); 237 } 238 catch(IllegalArgumentException iae) { 239 } 240 } 241 } finally { 242 Util.closeStream(fin); 243 } 244 Collections.sort(rolesList); 245 roles = new UNIXRole[rolesList.size()]; 246 rolesList.toArray(roles); 247 } 248 } else { 249 throw new IOException ("Could not locate " + GROUP_FILE.getAbsolutePath()); 250 } 251 } 252 253 private void checkPasswdFile() throws Exception { 254 Date current = null; 255 if (PASSWD_FILE.exists()) { 256 if (checkShadowFile()) { 257 lastPasswdFileChange = null; 258 } 259 if (checkUserEmailMapFile()) { 260 lastPasswdFileChange = null; 261 } 262 current = new Date (PASSWD_FILE.lastModified()); 263 if (lastPasswdFileChange == null || !lastPasswdFileChange.equals(current)) { 264 lastPasswdFileChange = current; 265 String line = null; 266 FileInputStream fin = new FileInputStream (PASSWD_FILE); 267 List userList = new ArrayList (); 268 try { 269 BufferedReader r = new BufferedReader (new InputStreamReader (fin)); 270 while ((line = r.readLine()) != null) { 271 String [] elements = line.split(":"); 272 String username = elements[0]; 273 if(elements.length > 5) { 274 String password = elements[1]; 275 int uid = Integer.parseInt(elements[2]); 276 int gid = Integer.parseInt(elements[3]); 277 String fullname = elements[4]; 278 String home = elements[5]; 279 String shell = ""; 280 if (elements.length > 6) { 281 shell = elements[6]; 282 } 283 List userRolesList = new ArrayList (); 284 Role primaryRole = getRoleByGID(gid); 285 if (primaryRole == null) { 286 log.warn("No primary group for user " + username); 287 } else { 288 userRolesList.add(primaryRole); 289 } 290 for (int i = 0; i < roles.length; i++) { 291 if (roles[i].containsMember(username) 292 && !(primaryRole != null && roles[i].getPrincipalName().equals( 293 primaryRole.getPrincipalName()))) { 294 userRolesList.add(roles[i]); 295 } 296 } 297 Role[] userRoles = new Role[userRolesList.size()]; 298 userRolesList.toArray(userRoles); 299 char[] pw = null; 300 if (password.equals("x")) { 301 pw = (char[]) shadowPasswords.get(username); 302 } else { 303 pw = password.toCharArray(); 304 } 305 UNIXUser user = new UNIXUser(username,userEmailMap == null ? "" : userEmailMap.getProperty(username, ""), pw, uid, gid, fullname, home, shell, userRoles, this.getRealm()); 306 userList.add(user); 307 } 308 } 309 } finally { 310 Util.closeStream(fin); 311 } 312 Collections.sort(userList); 313 users = new UNIXUser[userList.size()]; 314 userList.toArray(users); 315 } 316 } else { 317 throw new IOException ("Could not locate " + PASSWD_FILE.getAbsolutePath()); 318 } 319 } 320 321 private synchronized boolean checkShadowFile() throws Exception { 322 Date current = null; 323 if (SHADOW_FILE.exists()) { 324 current = new Date (SHADOW_FILE.lastModified()); 325 if (lastShadowFileChange == null || !lastShadowFileChange.equals(current)) { 326 lastShadowFileChange = current; 327 String line = null; 328 FileInputStream fin = new FileInputStream (SHADOW_FILE); 329 shadowPasswords = new HashMap (); 330 try { 331 BufferedReader r = new BufferedReader (new InputStreamReader (fin)); 332 while ((line = r.readLine()) != null) { 333 String [] elements = line.split(":"); 334 String username = elements[0]; 335 if (elements.length > 1 && !username.equals("+")) { 336 char[] password = elements[1].toCharArray(); 337 shadowPasswords.put(username, password); 338 } 339 } 340 } finally { 341 Util.closeStream(fin); 342 } 343 return true; 344 } 345 } else { 346 throw new IOException ("Could not locate " + PASSWD_FILE.getAbsolutePath()); 347 } 348 return false; 349 } 350 351 private synchronized boolean checkUserEmailMapFile() throws Exception { 352 if (!USER_EMAIL_MAP_FILE.exists()) { 353 if (userEmailMap != null) { 354 userEmailMap = null; 355 userEmailMapLastModified = -1; 356 return true; 357 } 358 } else if (userEmailMap == null) { 359 userEmailMap = new Properties (); 360 } 361 if (userEmailMap != null 362 && (userEmailMapLastModified == -1 || userEmailMapLastModified != USER_EMAIL_MAP_FILE.lastModified())) { 363 FileInputStream fin = null; 364 try { 365 fin = new FileInputStream (USER_EMAIL_MAP_FILE); 366 userEmailMap.load(fin); 367 } catch (IOException ioe) { 368 log.error("Failed to load user email map."); 369 } finally { 370 Util.closeStream(fin); 371 } 372 userEmailMapLastModified = USER_EMAIL_MAP_FILE.lastModified(); 373 return true; 374 } 375 return false; 376 } 377 378 382 private Role getRoleByGID(int gid) throws Exception { 383 checkGroupFile(); 384 for (int i = 0; i < roles.length; i++) { 385 if (roles[i].getGid() == gid) { 386 return roles[i]; 387 } 388 } 389 return null; 390 } 391 392 public void cleanup() throws Exception { 393 } 394 395 public int getInstallationPropertyCategory() { 396 return -1; 397 } 398 399 public boolean isOpen() { 400 return open; 401 } 402 403 public User[] getUsersInRole(Role role) throws Exception { 404 return CoreUtil.getUsersInRole(role, this); 405 } 406 407 public void changePassword(String username, String oldPassword, String password, boolean forcePasswordChangeAtLogon) throws UserDatabaseException, 408 InvalidLoginCredentialsException { 409 if (!supportsPasswordChange()) { 410 throw new InvalidLoginCredentialsException("Database doesn't support password change."); 411 } 412 if (forcePasswordChangeAtLogon) { 413 log.warn("Password change function of UNIX user database does not support forcePassswordChangeAtLogon."); 414 } 415 Process p = null; 416 try { 417 p = Runtime.getRuntime().exec( 418 "true".equals(System.getProperty("sslexplorer.useDevConfig", "false")) ? "sudo /usr/sbin/chpasswd" 419 : "/usr/sbin/chpasswd"); 420 new StreamReaderThread(p.getInputStream()); 421 new StreamReaderThread(p.getErrorStream()); 422 OutputStream out = p.getOutputStream(); 423 PrintWriter pw = new PrintWriter (out); 424 pw.println(username + ":" + password); 425 pw.flush(); 426 out.close(); 427 try { 428 p.waitFor(); 429 } catch (InterruptedException ie) { 430 431 } 432 int ret = p.exitValue(); 433 if (ret != 0) { 434 throw new UserDatabaseException("Failed to change password. chpasswd returned exit code " + ret + "."); 435 } 436 437 } catch (IOException e) { 438 throw new UserDatabaseException("Failed to change password.", e); 439 } finally { 440 if (p != null) { 441 Util.closeStream(p.getOutputStream()); 442 Util.closeStream(p.getInputStream()); 443 Util.closeStream(p.getErrorStream()); 444 } 445 } 446 } 447 448 class StreamReaderThread extends Thread { 449 InputStream in; 450 451 StreamReaderThread(InputStream in) { 452 this.in = in; 453 } 454 455 public void run() { 456 try { 457 BufferedReader reader = new BufferedReader (new InputStreamReader (in)); 458 String line = null; 459 while ((line = reader.readLine()) != null) { 460 if (log.isInfoEnabled()) 461 log.info("Output from chpasswd: '" + line + "'"); 462 } 463 } catch (IOException ioe) { 464 } 465 } 466 } 467 468 } | Popular Tags |