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
019package org.apache.hadoop.security;
020
021import java.security.Provider;
022import java.util.Map;
023
024import javax.security.auth.callback.*;
025import javax.security.sasl.AuthorizeCallback;
026import javax.security.sasl.Sasl;
027import javax.security.sasl.SaslException;
028import javax.security.sasl.SaslServer;
029import javax.security.sasl.SaslServerFactory;
030
031import org.apache.hadoop.classification.InterfaceAudience;
032import org.apache.hadoop.classification.InterfaceStability;
033
034@InterfaceAudience.Private
035@InterfaceStability.Evolving
036public 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.toString(), e);
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}