001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package org.apache.hadoop.security; 020 021 import java.security.Provider; 022 import java.util.Map; 023 024 import javax.security.auth.callback.*; 025 import javax.security.sasl.AuthorizeCallback; 026 import javax.security.sasl.Sasl; 027 import javax.security.sasl.SaslException; 028 import javax.security.sasl.SaslServer; 029 import javax.security.sasl.SaslServerFactory; 030 031 import org.apache.hadoop.classification.InterfaceAudience; 032 import org.apache.hadoop.classification.InterfaceStability; 033 034 @InterfaceAudience.Private 035 @InterfaceStability.Evolving 036 public class SaslPlainServer implements SaslServer { 037 @SuppressWarnings("serial") 038 public static class SecurityProvider extends Provider { 039 public SecurityProvider() { 040 super("SaslPlainServer", 1.0, "SASL PLAIN Authentication Server"); 041 put("SaslServerFactory.PLAIN", 042 SaslPlainServerFactory.class.getName()); 043 } 044 } 045 046 public static class SaslPlainServerFactory implements SaslServerFactory { 047 @Override 048 public SaslServer createSaslServer(String mechanism, String protocol, 049 String serverName, Map<String,?> props, CallbackHandler cbh) 050 throws SaslException { 051 return "PLAIN".equals(mechanism) ? new SaslPlainServer(cbh) : null; 052 } 053 @Override 054 public String[] getMechanismNames(Map<String,?> props){ 055 return (props == null) || "false".equals(props.get(Sasl.POLICY_NOPLAINTEXT)) 056 ? new String[]{"PLAIN"} 057 : new String[0]; 058 } 059 } 060 061 private CallbackHandler cbh; 062 private boolean completed; 063 private String authz; 064 065 SaslPlainServer(CallbackHandler callback) { 066 this.cbh = callback; 067 } 068 069 @Override 070 public String getMechanismName() { 071 return "PLAIN"; 072 } 073 074 @Override 075 public byte[] evaluateResponse(byte[] response) throws SaslException { 076 if (completed) { 077 throw new IllegalStateException("PLAIN authentication has completed"); 078 } 079 if (response == null) { 080 throw new IllegalArgumentException("Received null response"); 081 } 082 try { 083 String payload; 084 try { 085 payload = new String(response, "UTF-8"); 086 } catch (Exception e) { 087 throw new IllegalArgumentException("Received corrupt response", e); 088 } 089 // [ authz, authn, password ] 090 String[] parts = payload.split("\u0000", 3); 091 if (parts.length != 3) { 092 throw new IllegalArgumentException("Received corrupt response"); 093 } 094 if (parts[0].isEmpty()) { // authz = authn 095 parts[0] = parts[1]; 096 } 097 098 NameCallback nc = new NameCallback("SASL PLAIN"); 099 nc.setName(parts[1]); 100 PasswordCallback pc = new PasswordCallback("SASL PLAIN", false); 101 pc.setPassword(parts[2].toCharArray()); 102 AuthorizeCallback ac = new AuthorizeCallback(parts[1], parts[0]); 103 cbh.handle(new Callback[]{nc, pc, ac}); 104 if (ac.isAuthorized()) { 105 authz = ac.getAuthorizedID(); 106 } 107 } catch (Exception e) { 108 throw new SaslException("PLAIN auth failed: " + e.getMessage()); 109 } finally { 110 completed = true; 111 } 112 return null; 113 } 114 115 private void throwIfNotComplete() { 116 if (!completed) { 117 throw new IllegalStateException("PLAIN authentication not completed"); 118 } 119 } 120 121 @Override 122 public boolean isComplete() { 123 return completed; 124 } 125 126 @Override 127 public String getAuthorizationID() { 128 throwIfNotComplete(); 129 return authz; 130 } 131 132 @Override 133 public Object getNegotiatedProperty(String propName) { 134 throwIfNotComplete(); 135 return Sasl.QOP.equals(propName) ? "auth" : null; 136 } 137 138 @Override 139 public byte[] wrap(byte[] outgoing, int offset, int len) 140 throws SaslException { 141 throwIfNotComplete(); 142 throw new IllegalStateException( 143 "PLAIN supports neither integrity nor privacy"); 144 } 145 146 @Override 147 public byte[] unwrap(byte[] incoming, int offset, int len) 148 throws SaslException { 149 throwIfNotComplete(); 150 throw new IllegalStateException( 151 "PLAIN supports neither integrity nor privacy"); 152 } 153 154 @Override 155 public void dispose() throws SaslException { 156 cbh = null; 157 authz = null; 158 } 159 }