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,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 package org.apache.directory.server.ldap.handlers.bind.ntlm;
021
022
023 import org.apache.directory.server.core.CoreSession;
024 import org.apache.directory.server.core.authn.LdapPrincipal;
025 import org.apache.directory.server.core.interceptor.context.BindOperationContext;
026 import org.apache.directory.server.ldap.LdapSession;
027 import org.apache.directory.server.ldap.handlers.bind.AbstractSaslServer;
028 import org.apache.directory.server.ldap.handlers.bind.SaslConstants;
029 import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
030 import org.apache.directory.shared.ldap.constants.SupportedSaslMechanisms;
031 import org.apache.directory.shared.ldap.message.InternalBindRequest;
032 import org.apache.directory.shared.ldap.name.LdapDN;
033 import org.apache.directory.shared.ldap.util.StringTools;
034
035 import javax.naming.Context;
036 import javax.naming.InvalidNameException;
037 import javax.security.sasl.SaslException;
038
039
040 /**
041 * A SaslServer implementation for NTLM based SASL mechanism. This is
042 * required unfortunately because the JDK's SASL provider does not support
043 * this mechanism.
044 *
045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046 * @version $$Rev$$
047 */
048 public class NtlmSaslServer extends AbstractSaslServer
049 {
050 /** The different states during a NTLM negotiation */
051 enum NegotiationState { INITIALIZED, TYPE_1_RECEIVED, TYPE_2_SENT, TYPE_3_RECEIVED, COMPLETED }
052
053 /** The current state */
054 private NegotiationState state = NegotiationState.INITIALIZED;
055 private final NtlmProvider provider;
056
057
058 public NtlmSaslServer( NtlmProvider provider, InternalBindRequest bindRequest, LdapSession ldapSession )
059 {
060 super( ldapSession, null, bindRequest );
061 this.provider = provider;
062 }
063
064
065 /**
066 * {@inheritDoc}
067 */
068 public String getMechanismName()
069 {
070 return SupportedSaslMechanisms.NTLM;
071 }
072
073
074 protected void responseRecieved()
075 {
076 switch ( state )
077 {
078 case INITIALIZED:
079 state = NegotiationState.TYPE_1_RECEIVED;
080 break;
081
082 case TYPE_1_RECEIVED:
083 throw new IllegalStateException( "Cannot receive NTLM message before sending Type 2 challenge." );
084
085 case TYPE_2_SENT:
086 state = NegotiationState.TYPE_3_RECEIVED;
087 break;
088
089 case TYPE_3_RECEIVED:
090 throw new IllegalStateException( "Cannot receive NTLM message after Type 3 has been received." );
091
092 case COMPLETED:
093 throw new IllegalStateException( "Sasl challenge response already completed." );
094 }
095 }
096
097
098 protected void responseSent()
099 {
100 switch ( state )
101 {
102 case INITIALIZED:
103 throw new IllegalStateException( "Cannot send Type 2 challenge before Type 1 response." );
104
105 case TYPE_1_RECEIVED:
106 state = NegotiationState.TYPE_2_SENT;
107 break;
108
109 case TYPE_2_SENT:
110 throw new IllegalStateException( "Cannot send Type 2 after it's already sent." );
111
112 case TYPE_3_RECEIVED:
113 state = NegotiationState.COMPLETED;
114 break;
115
116 case COMPLETED:
117 throw new IllegalStateException( "Sasl challenge response already completed." );
118 }
119 }
120
121
122 /**
123 * {@inheritDoc}
124 */
125 public byte[] evaluateResponse( byte[] response ) throws SaslException
126 {
127 if ( response == null )
128 {
129 throw new NullPointerException( "response was null" );
130 }
131
132 if ( response.length == 0 )
133 {
134 throw new IllegalArgumentException( "response with zero bytes" );
135 }
136
137 responseRecieved();
138 byte[] retval = null;
139
140 switch ( state )
141 {
142 case TYPE_1_RECEIVED:
143 try
144 {
145 retval = provider.generateChallenge( getLdapSession().getIoSession(), response );
146 }
147 catch ( Exception e )
148 {
149 throw new SaslException( "There was a failure during NTLM Type 1 message handling.", e );
150 }
151
152 break;
153
154 case TYPE_3_RECEIVED:
155 boolean result;
156 try
157 {
158 result = provider.authenticate( getLdapSession().getIoSession(), response );
159 LdapDN dn = getBindRequest().getName();
160 dn.normalize( getLdapSession().getLdapServer().getDirectoryService().getRegistries().getAttributeTypeRegistry().getNormalizerMapping() );
161 LdapPrincipal ldapPrincipal = new LdapPrincipal( dn, AuthenticationLevel.STRONG );
162 getLdapSession().putSaslProperty( SaslConstants.SASL_AUTHENT_USER, ldapPrincipal );
163 getLdapSession().putSaslProperty( Context.SECURITY_PRINCIPAL, getBindRequest().getName().toString() );
164 }
165 catch ( Exception e )
166 {
167 throw new SaslException( "There was a failure during NTLM Type 3 message handling.", e );
168 }
169
170 if ( ! result )
171 {
172 throw new SaslException( "Authentication occurred but the credentials were invalid." );
173 }
174
175 break;
176 }
177
178 responseSent();
179 return retval;
180 }
181
182
183 /**
184 * Try to authenticate the usr against the underlying LDAP server.
185 */
186 private CoreSession authenticate( String user, String password ) throws InvalidNameException, Exception
187 {
188 BindOperationContext bindContext = new BindOperationContext( getLdapSession().getCoreSession() );
189 bindContext.setDn( new LdapDN( user ) );
190 bindContext.setCredentials( StringTools.getBytesUtf8( password ) );
191
192 getAdminSession().getDirectoryService().getOperationManager().bind( bindContext );
193
194 return bindContext.getSession();
195 }
196
197
198 /**
199 * {@inheritDoc}
200 */
201 public boolean isComplete()
202 {
203 return state == NegotiationState.COMPLETED;
204 }
205 }