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