/* * @(#)DefaultAuthHandler.java 0.3-3 06/05/2001 * * This file is part of the HTTPClient package * Copyright (C) 1996-2001 Ronald Tschalär * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307, USA * * For questions, suggestions, bug-reports, enhancement-requests etc. * I may be contacted at: * * ronald@innovation.ch * * The HTTPClient's home page is located at: * * http://www.innovation.ch/java/HTTPClient/ * */ package HTTPClient; import java.io.IOException; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.DataInputStream; import java.io.InputStreamReader; import java.util.Vector; import java.util.StringTokenizer; import java.awt.Frame; import java.awt.Panel; import java.awt.Label; import java.awt.Button; import java.awt.Dimension; import java.awt.TextField; import java.awt.GridLayout; import java.awt.BorderLayout; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowEvent; import java.awt.event.WindowAdapter; /** * This class is the default authorization handler. It currently handles the * authentication schemes "Basic", "Digest", and "SOCKS5" (used for the * SocksClient and not part of HTTP per se). * *

By default, when a username and password is required, this handler throws * up a message box requesting the desired info. However, applications can * {@link #setAuthorizationPrompter(HTTPClient.AuthorizationPrompter) set their * own authorization prompter} if desired. * *

Note: all methods except for * setAuthorizationPrompter are meant to be invoked by the * AuthorizationModule only, i.e. should not be invoked by the application * (those methods are only public because implementing the * AuthorizationHandler interface requires them to be). * * @version 0.3-3 06/05/2001 * @author Ronald Tschalär * @since V0.2 */ public class DefaultAuthHandler implements AuthorizationHandler, GlobalConstants { private static final byte[] NUL = new byte[0]; private static final int DI_A1 = 0; private static final int DI_A1S = 1; private static final int DI_QOP = 2; private static byte[] digest_secret = null; private static AuthorizationPrompter prompter = null; private static boolean prompterSet = false; /** * For Digest authentication we need to set the uri, response and * opaque parameters. For "Basic" and "SOCKS5" nothing is done. */ public AuthorizationInfo fixupAuthInfo(AuthorizationInfo info, RoRequest req, AuthorizationInfo challenge, RoResponse resp) throws AuthSchemeNotImplException { // nothing to do for Basic and SOCKS5 schemes if (info.getScheme().equalsIgnoreCase("Basic") || info.getScheme().equalsIgnoreCase("SOCKS5")) return info; else if (!info.getScheme().equalsIgnoreCase("Digest")) throw new AuthSchemeNotImplException(info.getScheme()); if (Log.isEnabled(Log.AUTH)) Log.write(Log.AUTH, "Auth: fixing up Authorization for host " + info.getHost()+":"+info.getPort() + "; scheme: " + info.getScheme() + "; realm: " + info.getRealm()); return digest_fixup(info, req, challenge, resp); } /** * returns the requested authorization, or null if none was given. * * @param challenge the parsed challenge from the server. * @param req the request which solicited this response * @param resp the full response received * @return a structure containing the necessary authorization info, * or null * @exception AuthSchemeNotImplException if the authentication scheme * in the challenge cannot be handled. */ public AuthorizationInfo getAuthorization(AuthorizationInfo challenge, RoRequest req, RoResponse resp) throws AuthSchemeNotImplException, IOException { AuthorizationInfo cred; if (Log.isEnabled(Log.AUTH)) Log.write(Log.AUTH, "Auth: Requesting Authorization for host " + challenge.getHost()+":"+challenge.getPort() + "; scheme: " + challenge.getScheme() + "; realm: " + challenge.getRealm()); // we only handle Basic, Digest and SOCKS5 authentication if (!challenge.getScheme().equalsIgnoreCase("Basic") && !challenge.getScheme().equalsIgnoreCase("Digest") && !challenge.getScheme().equalsIgnoreCase("SOCKS5")) throw new AuthSchemeNotImplException(challenge.getScheme()); // For digest authentication, check if stale is set if (challenge.getScheme().equalsIgnoreCase("Digest")) { cred = digest_check_stale(challenge, req, resp); if (cred != null) return cred; } // Ask the user for username/password NVPair answer; synchronized (getClass()) { if (!req.allowUI() || prompterSet && prompter == null) return null; if (prompter == null) setDefaultPrompter(); answer = prompter.getUsernamePassword(challenge, resp.getStatusCode() == 407); } if (answer == null) return null; // Now process the username/password if (challenge.getScheme().equalsIgnoreCase("basic")) { cred = new AuthorizationInfo(challenge.getHost(), challenge.getPort(), challenge.getScheme(), challenge.getRealm(), Codecs.base64Encode( answer.getName() + ":" + answer.getValue())); } else if (challenge.getScheme().equalsIgnoreCase("Digest")) { cred = digest_gen_auth_info(challenge.getHost(), challenge.getPort(), challenge.getRealm(), answer.getName(), answer.getValue(), req.getConnection().getContext()); cred = digest_fixup(cred, req, challenge, null); } else // SOCKS5 { NVPair[] upwd = { answer }; cred = new AuthorizationInfo(challenge.getHost(), challenge.getPort(), challenge.getScheme(), challenge.getRealm(), upwd, null); } // try to get rid of any unencoded passwords in memory answer = null; System.gc(); // Done Log.write(Log.AUTH, "Auth: Got Authorization"); return cred; } /** * We handle the "Authentication-Info" and "Proxy-Authentication-Info" * headers here. */ public void handleAuthHeaders(Response resp, RoRequest req, AuthorizationInfo prev, AuthorizationInfo prxy) throws IOException { String auth_info = resp.getHeader("Authentication-Info"); String prxy_info = resp.getHeader("Proxy-Authentication-Info"); if (auth_info == null && prev != null && hasParam(prev.getParams(), "qop", "auth-int")) auth_info = ""; if (prxy_info == null && prxy != null && hasParam(prxy.getParams(), "qop", "auth-int")) prxy_info = ""; try { handleAuthInfo(auth_info, "Authentication-Info", prev, resp, req, true); handleAuthInfo(prxy_info, "Proxy-Authentication-Info", prxy, resp, req, true); } catch (ParseException pe) { throw new IOException(pe.toString()); } } /** * We handle the "Authentication-Info" and "Proxy-Authentication-Info" * trailers here. */ public void handleAuthTrailers(Response resp, RoRequest req, AuthorizationInfo prev, AuthorizationInfo prxy) throws IOException { String auth_info = resp.getTrailer("Authentication-Info"); String prxy_info = resp.getTrailer("Proxy-Authentication-Info"); try { handleAuthInfo(auth_info, "Authentication-Info", prev, resp, req, false); handleAuthInfo(prxy_info, "Proxy-Authentication-Info", prxy, resp, req, false); } catch (ParseException pe) { throw new IOException(pe.toString()); } } private static void handleAuthInfo(String auth_info, String hdr_name, AuthorizationInfo prev, Response resp, RoRequest req, boolean in_headers) throws ParseException, IOException { if (auth_info == null) return; Vector pai = Util.parseHeader(auth_info); HttpHeaderElement elem; if (handle_nextnonce(prev, req, elem = Util.getElement(pai, "nextnonce"))) pai.removeElement(elem); if (handle_discard(prev, req, elem = Util.getElement(pai, "discard"))) pai.removeElement(elem); if (in_headers) { HttpHeaderElement qop = null; if (pai != null && (qop = Util.getElement(pai, "qop")) != null && qop.getValue() != null) { handle_rspauth(prev, resp, req, pai, hdr_name); } else if (prev != null && (Util.hasToken(resp.getHeader("Trailer"), hdr_name) && hasParam(prev.getParams(), "qop", null) || hasParam(prev.getParams(), "qop", "auth-int"))) { handle_rspauth(prev, resp, req, null, hdr_name); } else if ((pai != null && qop == null && pai.contains(new HttpHeaderElement("digest"))) || (Util.hasToken(resp.getHeader("Trailer"), hdr_name) && prev != null && !hasParam(prev.getParams(), "qop", null))) { handle_digest(prev, resp, req, hdr_name); } } if (pai.size() > 0) resp.setHeader(hdr_name, Util.assembleHeader(pai)); else resp.deleteHeader(hdr_name); } private static final boolean hasParam(NVPair[] params, String name, String val) { for (int idx=0; idx> 8) & 0xFF); time[2] = (byte) ((l_time >> 16) & 0xFF); time[3] = (byte) ((l_time >> 24) & 0xFF); time[4] = (byte) ((l_time >> 32) & 0xFF); time[5] = (byte) ((l_time >> 40) & 0xFF); time[6] = (byte) ((l_time >> 48) & 0xFF); time[7] = (byte) ((l_time >> 56) & 0xFF); params[cnonce] = new NVPair("cnonce", MD5.hexDigest(digest_secret, time)); } // select qop option if (ch_qop != -1) { if (qop == -1) { params = Util.resizeArray(params, params.length+1); qop = params.length-1; } extra[DI_QOP] = ch_params[ch_qop].getValue(); // select qop option String[] qops = splitList(extra[DI_QOP], ","); String p = null; for (int idx=0; idx= HTTP_1_1)) { p = "auth-int"; break; } if (qops[idx].equalsIgnoreCase("auth")) p = "auth"; } if (p == null) { for (int idx=0; idxnum bytes of random data. * * @param num the number of bytes to generate * @return a byte array of random data */ private static byte[] gen_random_bytes(int num) { // first try /dev/random try { FileInputStream rnd = new FileInputStream("/dev/random"); DataInputStream din = new DataInputStream(rnd); byte[] data = new byte[num]; din.readFully(data); try { din.close(); } catch (IOException ioe) { } return data; } catch (Throwable t) { } /* This is probably a much better generator, but it can be awfully * slow (~ 6 secs / byte on my old LX) */ //return new java.security.SecureRandom().getSeed(num); /* this is faster, but needs to be done better... */ byte[] data = new byte[num]; try { long fm = Runtime.getRuntime().freeMemory(); data[0] = (byte) (fm & 0xFF); data[1] = (byte) ((fm >> 8) & 0xFF); int h = data.hashCode(); data[2] = (byte) (h & 0xFF); data[3] = (byte) ((h >> 8) & 0xFF); data[4] = (byte) ((h >> 16) & 0xFF); data[5] = (byte) ((h >> 24) & 0xFF); long time = System.currentTimeMillis(); data[6] = (byte) (time & 0xFF); data[7] = (byte) ((time >> 8) & 0xFF); } catch (ArrayIndexOutOfBoundsException aioobe) { } return data; } /** * Return the value of the first NVPair whose name matches the key * using a case-insensitive search. * * @param list an array of NVPair's * @param key the key to search for * @return the value of the NVPair with that key, or null if not * found. */ private final static String getValue(NVPair[] list, String key) { int len = list.length; for (int idx=0; idx> 4) & 15, 16)); str.append(Character.forDigit(buf[idx] & 15, 16)); str.append(':'); } str.setLength(str.length()-1); return str.toString(); } static final byte[] unHex(String hex) { byte[] digest = new byte[hex.length()/2]; for (int idx=0; idx= 0 || os.indexOf("SunOS") >= 0 || os.indexOf("Solaris") >= 0 || os.indexOf("BSD") >= 0 || os.indexOf("AIX") >= 0 || os.indexOf("HP-UX") >= 0 || os.indexOf("IRIX") >= 0 || os.indexOf("OSF") >= 0 || os.indexOf("A/UX") >= 0 || os.indexOf("VMS") >= 0); } }