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 }