1/*
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements.  See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership.  The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License.  You may obtain a copy of the License at
10 *
11 *   http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied.  See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 */
21package org.apache.qpid.management.common.sasl;
22
23import org.apache.harmony.javax.security.auth.callback.Callback;
24import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
25import org.apache.harmony.javax.security.auth.callback.NameCallback;
26import org.apache.harmony.javax.security.auth.callback.PasswordCallback;
27import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException;
28import de.measite.smack.Sasl;
29import org.apache.harmony.javax.security.sasl.SaslClient;
30import org.apache.harmony.javax.security.sasl.SaslException;
31import java.io.IOException;
32import java.io.UnsupportedEncodingException;
33
34public class PlainSaslClient implements SaslClient
35{
36
37    private boolean completed;
38    private CallbackHandler cbh;
39    private String authorizationID;
40    private String authenticationID;
41    private byte password[];
42    private static byte SEPARATOR = 0;
43
44    public PlainSaslClient(String authorizationID, CallbackHandler cbh) throws SaslException
45    {
46        completed = false;
47        this.cbh = cbh;
48        Object[] userInfo = getUserInfo();
49        this.authorizationID = authorizationID;
50        this.authenticationID = (String) userInfo[0];
51        this.password = (byte[]) userInfo[1];
52        if (authenticationID == null || password == null)
53        {
54            throw new SaslException("PLAIN: authenticationID and password must be specified");
55        }
56    }
57
58    public byte[] evaluateChallenge(byte[] challenge) throws SaslException
59    {
60        if (completed)
61        {
62            throw new IllegalStateException("PLAIN: authentication already " +
63            "completed");
64        }
65        completed = true;
66        try
67        {
68            byte authzid[] =
69                authorizationID == null ? null : authorizationID.getBytes("UTF8");
70            byte authnid[] = authenticationID.getBytes("UTF8");
71            byte response[] =
72                new byte[
73                         password.length +
74                         authnid.length +
75                         2 + // SEPARATOR
76                         (authzid != null ? authzid.length : 0)
77                         ];
78            int size = 0;
79            if (authzid != null) {
80                System.arraycopy(authzid, 0, response, 0, authzid.length);
81                size = authzid.length;
82            }
83            response[size++] = SEPARATOR;
84            System.arraycopy(authnid, 0, response, size, authnid.length);
85            size += authnid.length;
86            response[size++] = SEPARATOR;
87            System.arraycopy(password, 0, response, size, password.length);
88            clearPassword();
89            return response;
90        } catch (UnsupportedEncodingException e) {
91            throw new SaslException("PLAIN: Cannot get UTF-8 encoding of ids",
92                    e);
93        }
94    }
95
96    public String getMechanismName()
97    {
98        return "PLAIN";
99    }
100
101    public boolean hasInitialResponse()
102    {
103        return true;
104    }
105
106    public boolean isComplete()
107    {
108        return completed;
109    }
110
111    public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
112    {
113        if (completed) {
114            throw new IllegalStateException("PLAIN: this mechanism supports " +
115            "neither integrity nor privacy");
116        } else {
117            throw new IllegalStateException("PLAIN: authentication not " +
118            "completed");
119        }
120    }
121
122    public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
123    {
124        if (completed)
125        {
126            throw new IllegalStateException("PLAIN: this mechanism supports " +
127            "neither integrity nor privacy");
128        }
129        else
130        {
131            throw new IllegalStateException("PLAIN: authentication not " +
132            "completed");
133        }
134    }
135
136    public Object getNegotiatedProperty(String propName)
137    {
138        if (completed)
139        {
140            if (propName.equals(Sasl.QOP))
141            {
142                return "auth";
143            }
144            else
145            {
146                return null;
147            }
148        }
149        else
150        {
151            throw new IllegalStateException("PLAIN: authentication not " +
152            "completed");
153        }
154    }
155
156    private void clearPassword()
157    {
158        if (password != null)
159        {
160            for (int i = 0 ; i < password.length ; i++)
161            {
162                password[i] = 0;
163            }
164            password = null;
165        }
166    }
167
168    public void dispose() throws SaslException
169    {
170        clearPassword();
171    }
172
173    protected void finalize()
174    {
175        clearPassword();
176    }
177
178    private Object[] getUserInfo() throws SaslException
179    {
180        try
181        {
182            final String userPrompt = "PLAIN authentication id: ";
183            final String pwPrompt = "PLAIN password: ";
184            NameCallback nameCb = new NameCallback(userPrompt);
185            PasswordCallback passwordCb = new PasswordCallback(pwPrompt, false);
186            cbh.handle(new Callback[] { nameCb, passwordCb });
187            String userid = nameCb.getName();
188            char pwchars[] = passwordCb.getPassword();
189            byte pwbytes[];
190            if (pwchars != null)
191            {
192                pwbytes = (new String(pwchars)).getBytes("UTF8");
193                passwordCb.clearPassword();
194            }
195            else
196            {
197                pwbytes = null;
198            }
199            return (new Object[] { userid, pwbytes });
200        }
201        catch (IOException e)
202        {
203            throw new SaslException("Cannot get password", e);
204        }
205        catch (UnsupportedCallbackException e)
206        {
207            throw new SaslException("Cannot get userid/password", e);
208        }
209    }
210}
211