1 /*
2  * Copyright (c) 1999, 2006, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.security.sasl;
27 
28 import javax.security.sasl.*;
29 import com.sun.security.sasl.util.PolicyUtils;
30 
31 import java.util.Map;
32 import java.io.IOException;
33 import javax.security.auth.callback.Callback;
34 import javax.security.auth.callback.CallbackHandler;
35 import javax.security.auth.callback.NameCallback;
36 import javax.security.auth.callback.PasswordCallback;
37 import javax.security.auth.callback.UnsupportedCallbackException;
38 
39 /**
40   * Client factory for EXTERNAL, CRAM-MD5, PLAIN.
41   *
42   * Requires the following callbacks to be satisfied by callback handler
43   * when using CRAM-MD5 or PLAIN.
44   * - NameCallback (to get username)
45   * - PasswordCallback (to get password)
46   *
47   * @author Rosanna Lee
48   */
49 final public class ClientFactoryImpl implements SaslClientFactory {
50     private static final String[] myMechs = {
51         "EXTERNAL",
52         "CRAM-MD5",
53         "PLAIN",
54     };
55 
56     private static final int[] mechPolicies = {
57         // %%% RL: Policies should actually depend on the external channel
58         PolicyUtils.NOPLAINTEXT|PolicyUtils.NOACTIVE|PolicyUtils.NODICTIONARY,
59         PolicyUtils.NOPLAINTEXT|PolicyUtils.NOANONYMOUS,    // CRAM-MD5
60         PolicyUtils.NOANONYMOUS,                            // PLAIN
61     };
62 
63     private static final int EXTERNAL = 0;
64     private static final int CRAMMD5 = 1;
65     private static final int PLAIN = 2;
66 
ClientFactoryImpl()67     public ClientFactoryImpl() {
68     }
69 
createSaslClient(String[] mechs, String authorizationId, String protocol, String serverName, Map<String,?> props, CallbackHandler cbh)70     public SaslClient createSaslClient(String[] mechs,
71         String authorizationId,
72         String protocol,
73         String serverName,
74         Map<String,?> props,
75         CallbackHandler cbh) throws SaslException {
76 
77             for (int i = 0; i < mechs.length; i++) {
78                 if (mechs[i].equals(myMechs[EXTERNAL])
79                     && PolicyUtils.checkPolicy(mechPolicies[EXTERNAL], props)) {
80                     return new ExternalClient(authorizationId);
81 
82                 } else if (mechs[i].equals(myMechs[CRAMMD5])
83                     && PolicyUtils.checkPolicy(mechPolicies[CRAMMD5], props)) {
84 
85                     Object[] uinfo = getUserInfo("CRAM-MD5", authorizationId, cbh);
86 
87                     // Callee responsible for clearing bytepw
88                     return new CramMD5Client((String) uinfo[0],
89                         (byte []) uinfo[1]);
90 
91                 } else if (mechs[i].equals(myMechs[PLAIN])
92                     && PolicyUtils.checkPolicy(mechPolicies[PLAIN], props)) {
93 
94                     Object[] uinfo = getUserInfo("PLAIN", authorizationId, cbh);
95 
96                     // Callee responsible for clearing bytepw
97                     return new PlainClient(authorizationId,
98                         (String) uinfo[0], (byte []) uinfo[1]);
99                 }
100             }
101             return null;
102     };
103 
getMechanismNames(Map<String,?> props)104     public String[] getMechanismNames(Map<String,?> props) {
105         return PolicyUtils.filterMechs(myMechs, mechPolicies, props);
106     }
107 
108     /**
109      * Gets the authentication id and password. The
110      * password is converted to bytes using UTF-8 and stored in bytepw.
111      * The authentication id is stored in authId.
112      *
113      * @param prefix The non-null prefix to use for the prompt (e.g., mechanism
114      *  name)
115      * @param authorizationId The possibly null authorization id. This is used
116      * as a default for the NameCallback. If null, it is not used in prompt.
117      * @param cbh The non-null callback handler to use.
118      * @return an {authid, passwd} pair
119      */
getUserInfo(String prefix, String authorizationId, CallbackHandler cbh)120     private Object[] getUserInfo(String prefix, String authorizationId,
121         CallbackHandler cbh) throws SaslException {
122         if (cbh == null) {
123             throw new SaslException(
124                 "Callback handler to get username/password required");
125         }
126         try {
127             String userPrompt = prefix + " authentication id: ";
128             String passwdPrompt = prefix + " password: ";
129 
130             NameCallback ncb = authorizationId == null?
131                 new NameCallback(userPrompt) :
132                 new NameCallback(userPrompt, authorizationId);
133 
134             PasswordCallback pcb = new PasswordCallback(passwdPrompt, false);
135 
136             cbh.handle(new Callback[]{ncb,pcb});
137 
138             char[] pw = pcb.getPassword();
139 
140             byte[] bytepw;
141             String authId;
142 
143             if (pw != null) {
144                 bytepw = new String(pw).getBytes("UTF8");
145                 pcb.clearPassword();
146             } else {
147                 bytepw = null;
148             }
149 
150             authId = ncb.getName();
151 
152             return new Object[]{authId, bytepw};
153 
154         } catch (IOException e) {
155             throw new SaslException("Cannot get password", e);
156         } catch (UnsupportedCallbackException e) {
157             throw new SaslException("Cannot get userid/password", e);
158         }
159     }
160 }
161