001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: /*
038: * @(#)IMAPSaslAuthenticator.java 1.10 07/05/04
039: */
040:
041: package com.sun.mail.imap.protocol;
042:
043: import java.io.*;
044: import java.util.*;
045: import javax.security.sasl.*;
046: import javax.security.auth.callback.*;
047:
048: import com.sun.mail.iap.*;
049: import com.sun.mail.imap.*;
050: import com.sun.mail.util.*;
051:
052: /**
053: * This class contains a single method that does authentication using
054: * SASL. This is in a separate class so that it can be compiled with
055: * J2SE 1.5. Eventually it should be merged into IMAPProtocol.java.
056: */
057:
058: public class IMAPSaslAuthenticator implements SaslAuthenticator {
059:
060: private IMAPProtocol pr;
061: private String name;
062: private Properties props;
063: private boolean debug;
064: private PrintStream out;
065: private String host;
066:
067: public IMAPSaslAuthenticator(IMAPProtocol pr, String name,
068: Properties props, boolean debug, PrintStream out,
069: String host) {
070: this .pr = pr;
071: this .name = name;
072: this .props = props;
073: this .debug = debug;
074: this .out = out;
075: this .host = host;
076: }
077:
078: public boolean authenticate(String[] mechs, String realm,
079: String authzid, String u, String p)
080: throws ProtocolException {
081:
082: synchronized (pr) { // authenticate method should be synchronized
083: Vector v = new Vector();
084: String tag = null;
085: Response r = null;
086: boolean done = false;
087: if (debug) {
088: out.print("IMAP SASL DEBUG: Mechanisms:");
089: for (int i = 0; i < mechs.length; i++)
090: out.print(" " + mechs[i]);
091: out.println();
092: }
093:
094: SaslClient sc;
095: final String r0 = realm;
096: final String u0 = u;
097: final String p0 = p;
098: CallbackHandler cbh = new CallbackHandler() {
099: public void handle(Callback[] callbacks) {
100: if (debug)
101: out
102: .println("IMAP SASL DEBUG: callback length: "
103: + callbacks.length);
104: for (int i = 0; i < callbacks.length; i++) {
105: if (debug)
106: out.println("IMAP SASL DEBUG: callback "
107: + i + ": " + callbacks[i]);
108: if (callbacks[i] instanceof NameCallback) {
109: NameCallback ncb = (NameCallback) callbacks[i];
110: ncb.setName(u0);
111: } else if (callbacks[i] instanceof PasswordCallback) {
112: PasswordCallback pcb = (PasswordCallback) callbacks[i];
113: pcb.setPassword(p0.toCharArray());
114: } else if (callbacks[i] instanceof RealmCallback) {
115: RealmCallback rcb = (RealmCallback) callbacks[i];
116: rcb.setText(r0 != null ? r0 : rcb
117: .getDefaultText());
118: } else if (callbacks[i] instanceof RealmChoiceCallback) {
119: RealmChoiceCallback rcb = (RealmChoiceCallback) callbacks[i];
120: if (r0 == null)
121: rcb.setSelectedIndex(rcb
122: .getDefaultChoice());
123: else {
124: // need to find specified realm in list
125: String[] choices = rcb.getChoices();
126: for (int k = 0; k < choices.length; k++) {
127: if (choices[k].equals(r0)) {
128: rcb.setSelectedIndex(k);
129: break;
130: }
131: }
132: }
133: }
134: }
135: }
136: };
137:
138: try {
139: sc = Sasl.createSaslClient(mechs, authzid, name, host,
140: (Map) props, cbh);
141: } catch (SaslException sex) {
142: if (debug)
143: out
144: .println("IMAP SASL DEBUG: Failed to create SASL client: "
145: + sex);
146: return false;
147: }
148: if (sc == null) {
149: if (debug)
150: out.println("IMAP SASL DEBUG: No SASL support");
151: return false;
152: }
153: if (debug)
154: out.println("IMAP SASL DEBUG: SASL client "
155: + sc.getMechanismName());
156:
157: try {
158: tag = pr.writeCommand("AUTHENTICATE "
159: + sc.getMechanismName(), null);
160: } catch (Exception ex) {
161: if (debug)
162: out
163: .println("IMAP SASL DEBUG: AUTHENTICATE Exception: "
164: + ex);
165: return false;
166: }
167:
168: OutputStream os = pr.getIMAPOutputStream(); // stream to IMAP server
169:
170: /*
171: * Wrap a BASE64Encoder around a ByteArrayOutputstream
172: * to craft b64 encoded username and password strings
173: *
174: * Note that the encoded bytes should be sent "as-is" to the
175: * server, *not* as literals or quoted-strings.
176: *
177: * Also note that unlike the B64 definition in MIME, CRLFs
178: * should *not* be inserted during the encoding process. So, I
179: * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine,
180: * which should be sufficiently large !
181: */
182:
183: ByteArrayOutputStream bos = new ByteArrayOutputStream();
184: byte[] CRLF = { (byte) '\r', (byte) '\n' };
185:
186: // Hack for Novell GroupWise XGWTRUSTEDAPP authentication mechanism
187: boolean isXGWTRUSTEDAPP = sc.getMechanismName().equals(
188: "XGWTRUSTEDAPP");
189: while (!done) { // loop till we are done
190: try {
191: r = pr.readResponse();
192: if (r.isContinuation()) {
193: byte[] ba = null;
194: if (!sc.isComplete()) {
195: ba = r.readByteArray().getNewBytes();
196: if (ba.length > 0)
197: ba = BASE64DecoderStream.decode(ba);
198: if (debug)
199: out
200: .println("IMAP SASL DEBUG: challenge: "
201: + ASCIIUtility
202: .toString(
203: ba,
204: 0,
205: ba.length)
206: + " :");
207: ba = sc.evaluateChallenge(ba);
208: }
209: if (ba == null) {
210: if (debug)
211: out
212: .println("IMAP SASL DEBUG: no response");
213: os.write(CRLF); // write out empty line
214: os.flush(); // flush the stream
215: bos.reset(); // reset buffer
216: } else {
217: if (debug)
218: out
219: .println("IMAP SASL DEBUG: response: "
220: + ASCIIUtility
221: .toString(
222: ba,
223: 0,
224: ba.length)
225: + " :");
226: ba = BASE64EncoderStream.encode(ba);
227: if (isXGWTRUSTEDAPP)
228: bos.write("XGWTRUSTEDAPP ".getBytes());
229: bos.write(ba);
230:
231: bos.write(CRLF); // CRLF termination
232: os.write(bos.toByteArray()); // write out line
233: os.flush(); // flush the stream
234: bos.reset(); // reset buffer
235: }
236: } else if (r.isTagged() && r.getTag().equals(tag))
237: // Ah, our tagged response
238: done = true;
239: else if (r.isBYE()) // outta here
240: done = true;
241: else
242: // hmm .. unsolicited response here ?!
243: v.addElement(r);
244: } catch (Exception ioex) {
245: if (debug)
246: ioex.printStackTrace();
247: // convert this into a BYE response
248: r = Response.byeResponse(ioex);
249: done = true;
250: // XXX - ultimately return true???
251: }
252: }
253:
254: if (sc.isComplete() /*&& res.status == SUCCESS*/) {
255: String qop = (String) sc
256: .getNegotiatedProperty(Sasl.QOP);
257: if (qop != null
258: && (qop.equalsIgnoreCase("auth-int") || qop
259: .equalsIgnoreCase("auth-conf"))) {
260: // XXX - NOT SUPPORTED!!!
261: if (debug)
262: out
263: .println("IMAP SASL DEBUG: "
264: + "Mechanism requires integrity or confidentiality");
265: return false;
266: }
267: }
268:
269: /* Dispatch untagged responses.
270: * NOTE: in our current upper level IMAP classes, we add the
271: * responseHandler to the Protocol object only *after* the
272: * connection has been authenticated. So, for now, the below
273: * code really ends up being just a no-op.
274: */
275: Response[] responses = new Response[v.size()];
276: v.copyInto(responses);
277: pr.notifyResponseHandlers(responses);
278:
279: // Handle the final OK, NO, BAD or BYE response
280: pr.handleResult(r);
281: pr.setCapabilities(r);
282: return true;
283: }
284: }
285: }
|