1 /* 2 * Copyright (C) 2004-2008 Jive Software. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.jivesoftware.openfire.sasl; 18 19 import org.jivesoftware.openfire.session.LocalClientSession; 20 import org.jivesoftware.openfire.session.LocalIncomingServerSession; 21 import org.jivesoftware.openfire.session.LocalSession; 22 import org.slf4j.Logger; 23 import org.slf4j.LoggerFactory; 24 25 import java.util.Arrays; 26 import java.util.HashSet; 27 import java.util.Map; 28 import java.util.Set; 29 30 import javax.security.auth.callback.CallbackHandler; 31 import javax.security.sasl.Sasl; 32 import javax.security.sasl.SaslException; 33 import javax.security.sasl.SaslServer; 34 import javax.security.sasl.SaslServerFactory; 35 36 /** 37 * Server Factory for supported mechanisms. 38 * 39 * @author Jay Kline 40 */ 41 42 public class SaslServerFactoryImpl implements SaslServerFactory 43 { 44 private final static Logger Log = LoggerFactory.getLogger( SaslServerFactoryImpl.class ); 45 46 /** 47 * All mechanisms provided by this factory. 48 */ 49 private final Set<Mechanism> allMechanisms; 50 SaslServerFactoryImpl()51 public SaslServerFactoryImpl() 52 { 53 allMechanisms = new HashSet<>(); 54 allMechanisms.add( new Mechanism( "ANONYMOUS", true, true ) ); 55 allMechanisms.add( new Mechanism( "PLAIN", false, true ) ); 56 allMechanisms.add( new Mechanism( "SCRAM-SHA-1", false, false ) ); 57 allMechanisms.add( new Mechanism( "JIVE-SHAREDSECRET", true, false ) ); 58 allMechanisms.add( new Mechanism( "EXTERNAL", false, false ) ); 59 } 60 61 @Override createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, CallbackHandler cbh)62 public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, CallbackHandler cbh) throws SaslException 63 { 64 if ( !Arrays.asList( getMechanismNames( props )).contains( mechanism ) ) 65 { 66 Log.debug( "This implementation is unable to create a SaslServer instance for the {} mechanism using the provided properties.", mechanism ); 67 return null; 68 } 69 70 switch ( mechanism.toUpperCase() ) 71 { 72 case "PLAIN": 73 if ( cbh == null ) 74 { 75 Log.debug( "Unable to instantiate {} SaslServer: A callbackHandler with support for Password, Name, and AuthorizeCallback required.", mechanism ); 76 return null; 77 } 78 return new SaslServerPlainImpl( protocol, serverName, props, cbh ); 79 80 case "SCRAM-SHA-1": 81 return new ScramSha1SaslServer(); 82 83 case "ANONYMOUS": 84 if ( !props.containsKey( LocalSession.class.getCanonicalName() ) ) 85 { 86 Log.debug( "Unable to instantiate {} SaslServer: Provided properties do not contain a LocalSession instance.", mechanism ); 87 return null; 88 } 89 else 90 { 91 final LocalSession session = (LocalSession) props.get( LocalSession.class.getCanonicalName() ); 92 return new AnonymousSaslServer( session ); 93 } 94 95 case "EXTERNAL": 96 if ( !props.containsKey( LocalSession.class.getCanonicalName() ) ) 97 { 98 Log.debug( "Unable to instantiate {} SaslServer: Provided properties do not contain a LocalSession instance.", mechanism ); 99 return null; 100 } 101 else 102 { 103 final Object session = props.get( LocalSession.class.getCanonicalName() ); 104 if ( session instanceof LocalClientSession ) 105 { 106 return new ExternalClientSaslServer( (LocalClientSession) session ); 107 } 108 if ( session instanceof LocalIncomingServerSession ) 109 { 110 return new ExternalServerSaslServer( (LocalIncomingServerSession) session ); 111 } 112 113 Log.debug( "Unable to instantiate {} Sasl Server: Provided properties contains neither LocalClientSession nor LocalIncomingServerSession instance.", mechanism ); 114 return null; 115 } 116 117 case JiveSharedSecretSaslServer.NAME: 118 return new JiveSharedSecretSaslServer(); 119 120 default: 121 throw new IllegalStateException(); // Fail fast - this should not be possible, as the first check in this method already verifies wether the mechanism is supported. 122 } 123 } 124 125 @Override getMechanismNames( Map<String, ?> props )126 public String[] getMechanismNames( Map<String, ?> props ) 127 { 128 final Set<String> result = new HashSet<>(); 129 130 for ( final Mechanism mechanism : allMechanisms ) 131 { 132 if ( props != null ) 133 { 134 if ( mechanism.allowsAnonymous && props.containsKey( Sasl.POLICY_NOANONYMOUS ) && Boolean.parseBoolean( (String) props.get( Sasl.POLICY_NOANONYMOUS ) ) ) 135 { 136 // Do not include a mechanism that allows anonymous authentication when the 'no anonymous' policy is set. 137 continue; 138 } 139 140 if ( mechanism.isPlaintext && props.containsKey( Sasl.POLICY_NOPLAINTEXT ) && Boolean.parseBoolean( (String) props.get( Sasl.POLICY_NOPLAINTEXT ) ) ) 141 { 142 // Do not include a mechanism that is susceptible to simple plain passive attacks when the 'no plaintext' policy is set. 143 continue; 144 } 145 } 146 147 // Mechanism passed all filters. It should be part of the result. 148 result.add( mechanism.name ); 149 } 150 151 return result.toArray( new String[ result.size() ] ); 152 } 153 154 private static class Mechanism 155 { 156 final String name; 157 final boolean allowsAnonymous; 158 final boolean isPlaintext; 159 Mechanism( String name, boolean allowsAnonymous, boolean isPlaintext )160 private Mechanism( String name, boolean allowsAnonymous, boolean isPlaintext ) 161 { 162 this.name = name; 163 this.allowsAnonymous = allowsAnonymous; 164 this.isPlaintext = isPlaintext; 165 } 166 } 167 } 168