Edit D:\app\Administrator\product\11.2.0\dbhome_1\owb\lib\int\HTTPClient\DefaultAuthHandler.java
/* * @(#)DefaultAuthHandler.java 0.3-3 06/05/2001 * * This file is part of the HTTPClient package * Copyright (C) 1996-2001 Ronald Tschal? * * 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). * * <P>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. * * <P><strong>Note:</strong> all methods except for * <var>setAuthorizationPrompter</var> 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 * <var>AuthorizationHandler</var> interface requires them to be). * * @version 0.3-3 06/05/2001 * @author Ronald Tschal? * @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<params.length; idx++) if (params[idx].getName().equalsIgnoreCase(name) && (val == null || params[idx].getValue().equalsIgnoreCase(val))) return true; return false; } /* * Here are all the Digest specific methods */ private static AuthorizationInfo digest_gen_auth_info(String host, int port, String realm, String user, String pass, Object context) { String A1 = user + ":" + realm + ":" + pass; String[] info = { MD5.hexDigest(A1), null, null }; AuthorizationInfo prev = AuthorizationInfo.getAuthorization(host, port, "Digest", realm, context); NVPair[] params; if (prev == null) { params = new NVPair[4]; params[0] = new NVPair("username", user); params[1] = new NVPair("uri", ""); params[2] = new NVPair("nonce", ""); params[3] = new NVPair("response", ""); } else { params = prev.getParams(); for (int idx=0; idx<params.length; idx++) { if (params[idx].getName().equalsIgnoreCase("username")) { params[idx] = new NVPair("username", user); break; } } } return new AuthorizationInfo(host, port, "Digest", realm, params, info); } /** * The fixup handler */ private static AuthorizationInfo digest_fixup(AuthorizationInfo info, RoRequest req, AuthorizationInfo challenge, RoResponse resp) throws AuthSchemeNotImplException { // get various parameters from challenge int ch_domain=-1, ch_nonce=-1, ch_alg=-1, ch_opaque=-1, ch_stale=-1, ch_dreq=-1, ch_qop=-1; NVPair[] ch_params = null; if (challenge != null) { ch_params = challenge.getParams(); for (int idx=0; idx<ch_params.length; idx++) { String name = ch_params[idx].getName().toLowerCase(); if (name.equals("domain")) ch_domain = idx; else if (name.equals("nonce")) ch_nonce = idx; else if (name.equals("opaque")) ch_opaque = idx; else if (name.equals("algorithm")) ch_alg = idx; else if (name.equals("stale")) ch_stale = idx; else if (name.equals("digest-required")) ch_dreq = idx; else if (name.equals("qop")) ch_qop = idx; } } // get various parameters from info int uri=-1, user=-1, alg=-1, response=-1, nonce=-1, cnonce=-1, nc=-1, opaque=-1, digest=-1, dreq=-1, qop=-1; NVPair[] params; String[] extra; synchronized (info) // we need to juggle nonce, nc, etc { params = info.getParams(); for (int idx=0; idx<params.length; idx++) { String name = params[idx].getName().toLowerCase(); if (name.equals("uri")) uri = idx; else if (name.equals("username")) user = idx; else if (name.equals("algorithm")) alg = idx; else if (name.equals("nonce")) nonce = idx; else if (name.equals("cnonce")) cnonce = idx; else if (name.equals("nc")) nc = idx; else if (name.equals("response")) response = idx; else if (name.equals("opaque")) opaque = idx; else if (name.equals("digest")) digest = idx; else if (name.equals("digest-required")) dreq = idx; else if (name.equals("qop")) qop = idx; } extra = (String[]) info.getExtraInfo(); // currently only MD5 hash (and "MD5-sess") is supported if (alg != -1 && !params[alg].getValue().equalsIgnoreCase("MD5") && !params[alg].getValue().equalsIgnoreCase("MD5-sess")) throw new AuthSchemeNotImplException("Digest auth scheme: " + "Algorithm " + params[alg].getValue() + " not implemented"); if (ch_alg != -1 && !ch_params[ch_alg].getValue().equalsIgnoreCase("MD5") && !ch_params[ch_alg].getValue().equalsIgnoreCase("MD5-sess")) throw new AuthSchemeNotImplException("Digest auth scheme: " + "Algorithm " + ch_params[ch_alg].getValue()+ " not implemented"); // fix up uri and nonce params[uri] = new NVPair("uri", URI.escape(req.getRequestURI(), URI.escpdPathChar, false)); String old_nonce = params[nonce].getValue(); if (ch_nonce != -1 && !old_nonce.equals(ch_params[ch_nonce].getValue())) params[nonce] = ch_params[ch_nonce]; // update or add optional attributes (opaque, algorithm, cnonce, // nonce-count, and qop if (ch_opaque != -1) { if (opaque == -1) { params = Util.resizeArray(params, params.length+1); opaque = params.length-1; } params[opaque] = ch_params[ch_opaque]; } if (ch_alg != -1) { if (alg == -1) { params = Util.resizeArray(params, params.length+1); alg = params.length-1; } params[alg] = ch_params[ch_alg]; } if (ch_qop != -1 || (ch_alg != -1 && ch_params[ch_alg].getValue().equalsIgnoreCase("MD5-sess"))) { if (cnonce == -1) { params = Util.resizeArray(params, params.length+1); cnonce = params.length-1; } if (digest_secret == null) digest_secret = gen_random_bytes(20); long l_time = System.currentTimeMillis(); byte[] time = new byte[8]; time[0] = (byte) (l_time & 0xFF); time[1] = (byte) ((l_time >> 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<qops.length; idx++) { if (qops[idx].equalsIgnoreCase("auth-int") && (req.getStream() == null || req.getConnection().ServProtVersKnown && req.getConnection().ServerProtocolVersion >= HTTP_1_1)) { p = "auth-int"; break; } if (qops[idx].equalsIgnoreCase("auth")) p = "auth"; } if (p == null) { for (int idx=0; idx<qops.length; idx++) if (qops[idx].equalsIgnoreCase("auth-int")) throw new AuthSchemeNotImplException( "Digest auth scheme: Can't comply with qop " + "option 'auth-int' because an HttpOutputStream " + "is being used and the server doesn't speak " + "HTTP/1.1"); throw new AuthSchemeNotImplException("Digest auth scheme: "+ "None of the available qop options '" + ch_params[ch_qop].getValue() + "' implemented"); } params[qop] = new NVPair("qop", p); } // increment nonce-count. if (qop != -1) { /* Note: we should actually be serializing all requests through * here so that the server sees the nonce-count in a * strictly increasing order. However, this would be a * *major* hassle to do, so we're just winging it. Most * of the time the requests will go over the wire in the * same order as they pass through here, but in MT apps * it's possible for one request to "overtake" another * between here and the synchronized block in * sendRequest(). */ if (nc == -1) { params = Util.resizeArray(params, params.length+1); nc = params.length-1; params[nc] = new NVPair("nc", "00000001"); } else if (old_nonce.equals(params[nonce].getValue())) { String c = Long.toHexString( Long.parseLong(params[nc].getValue(), 16) + 1); params[nc] = new NVPair("nc", "00000000".substring(c.length()) + c); } else params[nc] = new NVPair("nc", "00000001"); } // calc new session key if necessary if (challenge != null && (ch_stale == -1 || !ch_params[ch_stale].getValue().equalsIgnoreCase("true")) && alg != -1 && params[alg].getValue().equalsIgnoreCase("MD5-sess")) { extra[DI_A1S] = MD5.hexDigest(extra[DI_A1] + ":" + params[nonce].getValue() + ":" + params[cnonce].getValue()); } // update parameters for next auth cycle info.setParams(params); info.setExtraInfo(extra); } // calc "response" attribute String hash = null; if (qop != -1 && params[qop].getValue().equalsIgnoreCase("auth-int") && req.getStream() == null) { hash = MD5.hexDigest(req.getData() == null ? NUL : req.getData()); } if (req.getStream() == null) params[response] = new NVPair("response", calcResponseAttr(hash, extra, params, alg, uri, qop, nonce, nc, cnonce, req.getMethod())); // calc digest if necessary AuthorizationInfo new_info; boolean ch_dreq_val = false; if (ch_dreq != -1 && (ch_params[ch_dreq].getValue() == null || ch_params[ch_dreq].getValue().equalsIgnoreCase("true"))) ch_dreq_val = true; if ((ch_dreq_val || digest != -1) && req.getStream() == null) { NVPair[] d_params; if (digest == -1) { d_params = Util.resizeArray(params, params.length+1); digest = params.length; } else d_params = params; d_params[digest] = new NVPair("digest", calc_digest(req, extra[DI_A1], params[nonce].getValue())); if (dreq == -1) // if server requires digest, then so do we... { dreq = d_params.length; d_params = Util.resizeArray(d_params, d_params.length+1); d_params[dreq] = new NVPair("digest-required", "true"); } new_info = new AuthorizationInfo(info.getHost(), info.getPort(), info.getScheme(), info.getRealm(), d_params, extra); } else if (ch_dreq_val) new_info = null; else new_info = new AuthorizationInfo(info.getHost(), info.getPort(), info.getScheme(), info.getRealm(), params, extra); // add info for other domains, if listed boolean from_server = (challenge != null) && challenge.getHost().equalsIgnoreCase(req.getConnection().getHost()); if (ch_domain != -1) { URI base = null; try { base = new URI(req.getConnection().getProtocol(), req.getConnection().getHost(), req.getConnection().getPort(), req.getRequestURI()); } catch (ParseException pe) { } StringTokenizer tok = new StringTokenizer(ch_params[ch_domain].getValue()); while (tok.hasMoreTokens()) { URI Uri; try { Uri = new URI(base, tok.nextToken()); } catch (ParseException pe) { continue; } if (Uri.getHost() == null) continue; AuthorizationInfo tmp = AuthorizationInfo.getAuthorization(Uri.getHost(), Uri.getPort(), info.getScheme(), info.getRealm(), req.getConnection().getContext()); if (tmp == null) { params[uri] = new NVPair("uri", Uri.getPathAndQuery()); tmp = new AuthorizationInfo(Uri.getHost(), Uri.getPort(), info.getScheme(), info.getRealm(), params, extra); AuthorizationInfo.addAuthorization(tmp); } if (from_server) tmp.addPath(Uri.getPathAndQuery()); } } else if (from_server && challenge != null) { // Spec says that if no domain attribute is present then the // whole server should be considered being in the same space AuthorizationInfo tmp = AuthorizationInfo.getAuthorization(challenge.getHost(), challenge.getPort(), info.getScheme(), info.getRealm(), req.getConnection().getContext()); if (tmp != null) tmp.addPath("/"); } // now return the one to use return new_info; } /** * @return the fixed info is stale=true; null otherwise */ private static AuthorizationInfo digest_check_stale( AuthorizationInfo challenge, RoRequest req, RoResponse resp) throws AuthSchemeNotImplException, IOException { AuthorizationInfo cred = null; NVPair[] params = challenge.getParams(); for (int idx=0; idx<params.length; idx++) { String name = params[idx].getName(); if (name.equalsIgnoreCase("stale") && params[idx].getValue().equalsIgnoreCase("true")) { cred = AuthorizationInfo.getAuthorization(challenge, req, resp, false); if (cred != null) // should always be the case return digest_fixup(cred, req, challenge, resp); break; // should never be reached } } return cred; } /** * Handle nextnonce field. */ private static boolean handle_nextnonce(AuthorizationInfo prev, RoRequest req, HttpHeaderElement nextnonce) throws IOException { if (prev == null || nextnonce == null || nextnonce.getValue() == null) return false; AuthorizationInfo ai; try { ai = AuthorizationInfo.getAuthorization(prev, req, null, false); } catch (AuthSchemeNotImplException asnie) { ai = prev; /* shouldn't happen */ } synchronized (ai) { NVPair[] params = ai.getParams(); params = setValue(params, "nonce", nextnonce.getValue()); params = setValue(params, "nc", "00000000"); ai.setParams(params); } return true; } /** * Handle digest field of the Authentication-Info response header. */ private static boolean handle_digest(AuthorizationInfo prev, Response resp, RoRequest req, String hdr_name) throws IOException { if (prev == null) return false; NVPair[] params = prev.getParams(); VerifyDigest verifier = new VerifyDigest(((String[]) prev.getExtraInfo())[0], getValue(params, "nonce"), req.getMethod(), getValue(params, "uri"), hdr_name, resp); if (resp.hasEntity()) { Log.write(Log.AUTH, "Auth: pushing md5-check-stream to verify "+ "digest from " + hdr_name); resp.inp_stream = new MD5InputStream(resp.inp_stream, verifier); } else { Log.write(Log.AUTH, "Auth: verifying digest from " + hdr_name); verifier.verifyHash(MD5.digest(NUL), 0); } return true; } /** * Handle rspauth field of the Authentication-Info response header. */ private static boolean handle_rspauth(AuthorizationInfo prev, Response resp, RoRequest req, Vector auth_info, String hdr_name) throws IOException { if (prev == null) return false; // get the parameters we sent NVPair[] params = prev.getParams(); int uri=-1, alg=-1, nonce=-1, cnonce=-1, nc=-1; for (int idx=0; idx<params.length; idx++) { String name = params[idx].getName().toLowerCase(); if (name.equals("uri")) uri = idx; else if (name.equals("algorithm")) alg = idx; else if (name.equals("nonce")) nonce = idx; else if (name.equals("cnonce")) cnonce = idx; else if (name.equals("nc")) nc = idx; } // create hash verifier to verify rspauth VerifyRspAuth verifier = new VerifyRspAuth(params[uri].getValue(), ((String[]) prev.getExtraInfo())[0], (alg == -1 ? null : params[alg].getValue()), params[nonce].getValue(), (cnonce == -1 ? "" : params[cnonce].getValue()), (nc == -1 ? "" : params[nc].getValue()), hdr_name, resp); // if Authentication-Info in header and qop=auth then verify immediately HttpHeaderElement qop = null; if (auth_info != null && (qop = Util.getElement(auth_info, "qop")) != null && qop.getValue() != null && (qop.getValue().equalsIgnoreCase("auth") || !resp.hasEntity() && qop.getValue().equalsIgnoreCase("auth-int")) ) { Log.write(Log.AUTH, "Auth: verifying rspauth from " + hdr_name); verifier.verifyHash(MD5.digest(NUL), 0); } else { // else push md5 stream and verify after body Log.write(Log.AUTH, "Auth: pushing md5-check-stream to verify "+ "rspauth from " + hdr_name); resp.inp_stream = new MD5InputStream(resp.inp_stream, verifier); } return true; } /** * Calc "response" attribute for a request. */ private static String calcResponseAttr(String hash, String[] extra, NVPair[] params, int alg, int uri, int qop, int nonce, int nc, int cnonce, String method) { String A1, A2, resp_val; if (alg != -1 && params[alg].getValue().equalsIgnoreCase("MD5-sess")) A1 = extra[DI_A1S]; else A1 = extra[DI_A1]; A2 = method + ":" + params[uri].getValue(); if (qop != -1 && params[qop].getValue().equalsIgnoreCase("auth-int")) { A2 += ":" + hash; } A2 = MD5.hexDigest(A2); if (qop == -1) resp_val = MD5.hexDigest(A1 + ":" + params[nonce].getValue() + ":" + A2); else resp_val = MD5.hexDigest(A1 + ":" + params[nonce].getValue() + ":" + params[nc].getValue() + ":" + params[cnonce].getValue() + ":" + params[qop].getValue() + ":" + A2); return resp_val; } /** * Calculates the digest of the request body. This was in RFC-2069 * and draft-ietf-http-authentication-00.txt, but has subsequently * been removed. Here for backwards compatibility. */ private static String calc_digest(RoRequest req, String A1_hash, String nonce) { if (req.getStream() != null) return ""; int ct=-1, ce=-1, lm=-1, ex=-1, dt=-1; for (int idx=0; idx<req.getHeaders().length; idx++) { String name = req.getHeaders()[idx].getName(); if (name.equalsIgnoreCase("Content-type")) ct = idx; else if (name.equalsIgnoreCase("Content-Encoding")) ce = idx; else if (name.equalsIgnoreCase("Last-Modified")) lm = idx; else if (name.equalsIgnoreCase("Expires")) ex = idx; else if (name.equalsIgnoreCase("Date")) dt = idx; } NVPair[] hdrs = req.getHeaders(); byte[] entity_body = (req.getData() == null ? NUL : req.getData()); String entity_hash = MD5.hexDigest(entity_body); String entity_info = MD5.hexDigest(req.getRequestURI() + ":" + (ct == -1 ? "" : hdrs[ct].getValue()) + ":" + entity_body.length + ":" + (ce == -1 ? "" : hdrs[ce].getValue()) + ":" + (lm == -1 ? "" : hdrs[lm].getValue()) + ":" + (ex == -1 ? "" : hdrs[ex].getValue())); String entity_digest = A1_hash + ":" + nonce + ":" + req.getMethod() + ":" + (dt == -1 ? "" : hdrs[dt].getValue()) + ":" + entity_info + ":" + entity_hash; if (Log.isEnabled(Log.AUTH)) { Log.write(Log.AUTH, "Auth: Entity-Info: '" + req.getRequestURI() + ":" + (ct == -1 ? "" : hdrs[ct].getValue()) + ":" + entity_body.length + ":" + (ce == -1 ? "" : hdrs[ce].getValue()) + ":" + (lm == -1 ? "" : hdrs[lm].getValue()) + ":" + (ex == -1 ? "" : hdrs[ex].getValue()) +"'"); Log.write(Log.AUTH, "Auth: Entity-Body: '" + entity_hash + "'"); Log.write(Log.AUTH, "Auth: Entity-Digest: '" + entity_digest + "'"); } return MD5.hexDigest(entity_digest); } /** * Handle discard token */ private static boolean handle_discard(AuthorizationInfo prev, RoRequest req, HttpHeaderElement discard) { if (discard != null && prev != null) { AuthorizationInfo.removeAuthorization(prev, req.getConnection().getContext()); return true; } return false; } /** * Generate <var>num</var> 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<len; idx++) if (list[idx].getName().equalsIgnoreCase(key)) return list[idx].getValue(); return null; } /** * Return the index 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 index of the NVPair with that key, or -1 if not * found. */ private final static int getIndex(NVPair[] list, String key) { int len = list.length; for (int idx=0; idx<len; idx++) if (list[idx].getName().equalsIgnoreCase(key)) return idx; return -1; } /** * Sets the value of the NVPair with the name that matches the key * (case-insensitive). If no name matches, a new entry is created. * * @param list an array of NVPair's * @param key the name of the NVPair * @param val the value of the new NVPair * @return the (possibly) new list */ private final static NVPair[] setValue(NVPair[] list, String key, String val) { int idx = getIndex(list, key); if (idx == -1) { idx = list.length; list = Util.resizeArray(list, list.length+1); } list[idx] = new NVPair(key, val); return list; } /** * Split a list into an array of Strings, using sep as the * separator and removing whitespace around the separator. */ private static String[] splitList(String str, String sep) { if (str == null) return new String[0]; StringTokenizer tok = new StringTokenizer(str, sep); String[] list = new String[tok.countTokens()]; for (int idx=0; idx<list.length; idx++) list[idx] = tok.nextToken().trim(); return list; } /** * Produce a string of the form "A5:22:F1:0B:53" */ static String hex(byte[] buf) { StringBuffer str = new StringBuffer(buf.length*3); for (int idx=0; idx<buf.length; idx++) { str.append(Character.forDigit((buf[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<digest.length; idx++) { digest[idx] = (byte) (0xFF & Integer.parseInt( hex.substring(2*idx, 2*(idx+1)), 16)); } return digest; } /** * Set a new username/password prompter. * * @param prompt the AuthorizationPrompter to use whenever a username * and password are needed; if null, no querying will be * done * @return the previous prompter */ public static synchronized AuthorizationPrompter setAuthorizationPrompter( AuthorizationPrompter prompt) { AuthorizationPrompter prev = prompter; prompter = prompt; prompterSet = true; return prev; } /** * Set the default authorization prompter. It first tries to figure out * if the AWT is running, and if it is then the GUI popup prompter is used; * otherwise the command line prompter is used. */ private static void setDefaultPrompter() { // if the AWT is running use the popup box; else use the // the command line prompter. if (!SimpleAuthPrompt.canUseCLPrompt() || isAWTRunning()) prompter = new SimpleAuthPopup(); else prompter = new SimpleAuthPrompt(); } /** * Try and figure out if the AWT is running. This is done by searching all * threads and looking for one whose name starts with "AWT-". */ private static final boolean isAWTRunning() { // find top-level thread group ThreadGroup root = Thread.currentThread().getThreadGroup(); while (root.getParent() != null) root = root.getParent(); // search all threads Thread[] t_list = new Thread[root.activeCount() + 5]; int t_num = root.enumerate(t_list); for (int idx=0; idx<t_num; idx++) { if (t_list[idx].getName().startsWith("AWT-")) return true; } return false; } } /** * This verifies the "rspauth" from draft-ietf-http-authentication-03 */ class VerifyRspAuth implements HashVerifier, GlobalConstants { private String uri; private String HA1; private String alg; private String nonce; private String cnonce; private String nc; private String hdr; private RoResponse resp; public VerifyRspAuth(String uri, String HA1, String alg, String nonce, String cnonce, String nc, String hdr, RoResponse resp) { this.uri = uri; this.HA1 = HA1; this.alg = alg; this.nonce = nonce; this.cnonce = cnonce; this.nc = nc; this.hdr = hdr; this.resp = resp; } public void verifyHash(byte[] hash, long len) throws IOException { String auth_info = resp.getHeader(hdr); if (auth_info == null) auth_info = resp.getTrailer(hdr); if (auth_info == null) return; Vector pai; try { pai = Util.parseHeader(auth_info); } catch (ParseException pe) { throw new IOException(pe.toString()); } String qop; HttpHeaderElement elem = Util.getElement(pai, "qop"); if (elem == null || (qop = elem.getValue()) == null || (!qop.equalsIgnoreCase("auth") && !qop.equalsIgnoreCase("auth-int"))) return; elem = Util.getElement(pai, "rspauth"); if (elem == null || elem.getValue() == null) return; byte[] digest = DefaultAuthHandler.unHex(elem.getValue()); elem = Util.getElement(pai, "cnonce"); if (elem != null && elem.getValue() != null && !elem.getValue().equals(cnonce)) throw new IOException("Digest auth scheme: received wrong " + "client-nonce '" + elem.getValue() + "' - expected '" + cnonce + "'"); elem = Util.getElement(pai, "nc"); if (elem != null && elem.getValue() != null && !elem.getValue().equals(nc)) throw new IOException("Digest auth scheme: received wrong " + "nonce-count '" + elem.getValue() + "' - expected '" + nc + "'"); String A1, A2; if (alg != null && alg.equalsIgnoreCase("MD5-sess")) A1 = MD5.hexDigest(HA1 + ":" + nonce + ":" + cnonce); else A1 = HA1; // draft-01 was: A2 = resp.getStatusCode() + ":" + uri; A2 = ":" + uri; if (qop.equalsIgnoreCase("auth-int")) A2 += ":" + MD5.toHex(hash); A2 = MD5.hexDigest(A2); hash = MD5.digest(A1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + A2); for (int idx=0; idx<hash.length; idx++) { if (hash[idx] != digest[idx]) throw new IOException("MD5-Digest mismatch: expected " + DefaultAuthHandler.hex(digest) + " but calculated " + DefaultAuthHandler.hex(hash)); } Log.write(Log.AUTH, "Auth: rspauth from " + hdr + " successfully verified"); } } /** * This verifies the "digest" from rfc-2069 */ class VerifyDigest implements HashVerifier, GlobalConstants { private String HA1; private String nonce; private String method; private String uri; private String hdr; private RoResponse resp; public VerifyDigest(String HA1, String nonce, String method, String uri, String hdr, RoResponse resp) { this.HA1 = HA1; this.nonce = nonce; this.method = method; this.uri = uri; this.hdr = hdr; this.resp = resp; } public void verifyHash(byte[] hash, long len) throws IOException { String auth_info = resp.getHeader(hdr); if (auth_info == null) auth_info = resp.getTrailer(hdr); if (auth_info == null) return; Vector pai; try { pai = Util.parseHeader(auth_info); } catch (ParseException pe) { throw new IOException(pe.toString()); } HttpHeaderElement elem = Util.getElement(pai, "digest"); if (elem == null || elem.getValue() == null) return; byte[] digest = DefaultAuthHandler.unHex(elem.getValue()); String entity_info = MD5.hexDigest( uri + ":" + header_val("Content-Type", resp) + ":" + header_val("Content-Length", resp) + ":" + header_val("Content-Encoding", resp) + ":" + header_val("Last-Modified", resp) + ":" + header_val("Expires", resp)); hash = MD5.digest(HA1 + ":" + nonce + ":" + method + ":" + header_val("Date", resp) + ":" + entity_info + ":" + MD5.toHex(hash)); for (int idx=0; idx<hash.length; idx++) { if (hash[idx] != digest[idx]) throw new IOException("MD5-Digest mismatch: expected " + DefaultAuthHandler.hex(digest) + " but calculated " + DefaultAuthHandler.hex(hash)); } Log.write(Log.AUTH, "Auth: digest from " + hdr + " successfully verified"); } private static final String header_val(String hdr_name, RoResponse resp) throws IOException { String hdr = resp.getHeader(hdr_name); String tlr = resp.getTrailer(hdr_name); return (hdr != null ? hdr : (tlr != null ? tlr : "")); } } class SimpleAuthPopup implements AuthorizationPrompter { private static BasicAuthBox inp = null; /** * the method called by DefaultAuthHandler. * * @return the username/password pair */ public NVPair getUsernamePassword(AuthorizationInfo challenge, boolean forProxy) { String line1, line2, line3; if (challenge.getScheme().equalsIgnoreCase("SOCKS5")) { line1 = "Enter username and password for SOCKS server on host"; line2 = challenge.getHost(); line3 = "Authentication Method: username/password"; } else { line1 = "Enter username and password for realm `" + challenge.getRealm() + "'"; line2 = "on host " + challenge.getHost() + ":" + challenge.getPort(); line3 = "Authentication Scheme: " + challenge.getScheme(); } synchronized(getClass()) { if (inp == null) inp = new BasicAuthBox(); } return inp.getInput(line1, line2, line3, challenge.getScheme()); } /** * This class implements a simple popup that request username and password * used for the "basic" and "digest" authentication schemes. * * @version 0.3-3 06/05/2001 * @author Ronald Tschal? */ private static class BasicAuthBox extends Frame { private final static String title = "Authorization Request"; private Dimension screen; private Label line1, line2, line3; private TextField user, pass; private int done; private final static int OK = 1, CANCEL = 0; /** * Constructs the popup with two lines of text above the input fields */ BasicAuthBox() { super(title); screen = getToolkit().getScreenSize(); addNotify(); addWindowListener(new Close()); setLayout(new BorderLayout()); Panel p = new Panel(new GridLayout(3,1)); p.add(line1 = new Label()); p.add(line2 = new Label()); p.add(line3 = new Label()); add("North", p); p = new Panel(new GridLayout(2,1)); p.add(new Label("Username:")); p.add(new Label("Password:")); add("West", p); p = new Panel(new GridLayout(2,1)); p.add(user = new TextField(30)); p.add(pass = new TextField(30)); pass.addActionListener(new Ok()); pass.setEchoChar('*'); add("East", p); GridBagLayout gb = new GridBagLayout(); p = new Panel(gb); GridBagConstraints constr = new GridBagConstraints(); Panel pp = new Panel(); p.add(pp); constr.gridwidth = GridBagConstraints.REMAINDER; gb.setConstraints(pp, constr); constr.gridwidth = 1; constr.weightx = 1.0; Button b; p.add(b = new Button(" OK ")); b.addActionListener(new Ok()); constr.weightx = 1.0; gb.setConstraints(b, constr); p.add(b = new Button("Clear")); b.addActionListener(new Clear()); constr.weightx = 2.0; gb.setConstraints(b, constr); p.add(b = new Button("Cancel")); b.addActionListener(new Cancel()); constr.weightx = 1.0; gb.setConstraints(b, constr); add("South", p); pack(); } /** * our event handlers */ private class Ok implements ActionListener { public void actionPerformed(ActionEvent ae) { done = OK; synchronized (BasicAuthBox.this) { BasicAuthBox.this.notifyAll(); } } } private class Clear implements ActionListener { public void actionPerformed(ActionEvent ae) { user.setText(""); pass.setText(""); user.requestFocus(); } } private class Cancel implements ActionListener { public void actionPerformed(ActionEvent ae) { done = CANCEL; synchronized (BasicAuthBox.this) { BasicAuthBox.this.notifyAll(); } } } private class Close extends WindowAdapter { public void windowClosing(WindowEvent we) { new Cancel().actionPerformed(null); } } /** * the method called by SimpleAuthPopup. * * @return the username/password pair */ synchronized NVPair getInput(String l1, String l2, String l3, String scheme) { line1.setText(l1); line2.setText(l2); line3.setText(l3); line1.invalidate(); line2.invalidate(); line3.invalidate(); setResizable(true); pack(); setResizable(false); setLocation((screen.width-getPreferredSize().width)/2, (int) ((screen.height-getPreferredSize().height)/2*.7)); boolean user_focus = true; if (scheme.equalsIgnoreCase("NTLM")) { // prefill the user field with the username try { user.setText(System.getProperty("user.name", "")); user_focus = false; } catch (SecurityException se) { } } setVisible(true); if (user_focus) user.requestFocus(); else pass.requestFocus(); try { wait(); } catch (InterruptedException e) { } setVisible(false); NVPair result = new NVPair(user.getText(), pass.getText()); user.setText(""); pass.setText(""); if (done == CANCEL) return null; else return result; } } } /** * This class implements a simple command line prompter that request * username and password used for the "basic" and "digest" authentication * schemes. * * @version 0.3-3 06/05/2001 * @author Ronald Tschal? */ class SimpleAuthPrompt implements AuthorizationPrompter { /** * the method called by DefaultAuthHandler. * * @return the username/password pair */ public NVPair getUsernamePassword(AuthorizationInfo challenge, boolean forProxy) { String user, pass; if (challenge.getScheme().equalsIgnoreCase("SOCKS5")) { System.out.println("Enter username and password for SOCKS " + "server on host " + challenge.getHost()); System.out.println("Authentication Method: username/password"); } else { System.out.println("Enter username and password for realm `" + challenge.getRealm() + "' on host " + challenge.getHost() + ":" + challenge.getPort()); System.out.println("Authentication Scheme: " + challenge.getScheme()); } // get username BufferedReader inp = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Username: "); System.out.flush(); try { user = inp.readLine(); } catch (IOException ioe) { return null; } if (user == null || user.length() == 0) return null; // cancel'd // get password echo(false); System.out.print("Password: "); System.out.flush(); try { pass = inp.readLine(); } catch (IOException ioe) { return null; } System.out.println(); echo(true); if (pass == null) return null; // cancel'd // done return new NVPair(user, pass); } /* * Turn command-line echoing of typed characters on or off. */ private static void echo(boolean on) { String os = System.getProperty("os.name"); String[] cmd = null; if (os.equalsIgnoreCase("Windows 95") || os.equalsIgnoreCase("Windows NT")) // I don't think this works on M$ ... cmd = new String[] { "echo", on ? "on" : "off" }; else if (os.equalsIgnoreCase("Windows") || os.equalsIgnoreCase("16-bit Windows")) ; // ??? else if (os.equalsIgnoreCase("OS/2")) ; // ??? else if (os.equalsIgnoreCase("Mac OS") || os.equalsIgnoreCase("MacOS")) ; // ??? else if (os.equalsIgnoreCase("OpenVMS") || os.equalsIgnoreCase("VMS")) cmd = new String[] { "SET TERMINAL " + (on ? "/ECHO" : "/NOECHO") }; else // probably unix cmd = new String[] { "/bin/sh", "-c", "stty " + (on ? "echo" : "-echo") + " < /dev/tty" }; if (cmd != null) try { Runtime.getRuntime().exec(cmd).waitFor(); } catch (Exception e) { } } /** * @return true for Unix's and VMS */ static boolean canUseCLPrompt() { String os = System.getProperty("os.name"); return (os.indexOf("Linux") >= 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); } }
Ms-Dos/Windows
Unix
Write backup
jsp File Browser version 1.2 by
www.vonloesch.de