DefaultAuthHandler.java
Upload User: demmber
Upload Date: 2007-12-22
Package Size: 717k
Code Size: 47k
Category:

Java Develop

Development Platform:

Java

  1. /*
  2.  * @(#)DefaultAuthHandler.java 0.3-3 06/05/2001
  3.  *
  4.  *  This file is part of the HTTPClient package
  5.  *  Copyright (C) 1996-2001 Ronald Tschal鋜
  6.  *
  7.  *  This library is free software; you can redistribute it and/or
  8.  *  modify it under the terms of the GNU Lesser General Public
  9.  *  License as published by the Free Software Foundation; either
  10.  *  version 2 of the License, or (at your option) any later version.
  11.  *
  12.  *  This library is distributed in the hope that it will be useful,
  13.  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  *  Lesser General Public License for more details.
  16.  *
  17.  *  You should have received a copy of the GNU Lesser General Public
  18.  *  License along with this library; if not, write to the Free
  19.  *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  20.  *  MA 02111-1307, USA
  21.  *
  22.  *  For questions, suggestions, bug-reports, enhancement-requests etc.
  23.  *  I may be contacted at:
  24.  *
  25.  *  ronald@innovation.ch
  26.  *
  27.  *  The HTTPClient's home page is located at:
  28.  *
  29.  *  http://www.innovation.ch/java/HTTPClient/ 
  30.  *
  31.  */
  32. package HTTPClient;
  33. import java.io.IOException;
  34. import java.io.BufferedReader;
  35. import java.io.FileInputStream;
  36. import java.io.DataInputStream;
  37. import java.io.InputStreamReader;
  38. import java.util.Vector;
  39. import java.util.StringTokenizer;
  40. import java.awt.Frame;
  41. import java.awt.Panel;
  42. import java.awt.Label;
  43. import java.awt.Button;
  44. import java.awt.Dimension;
  45. import java.awt.TextField;
  46. import java.awt.GridLayout;
  47. import java.awt.BorderLayout;
  48. import java.awt.GridBagLayout;
  49. import java.awt.GridBagConstraints;
  50. import java.awt.event.ActionEvent;
  51. import java.awt.event.ActionListener;
  52. import java.awt.event.WindowEvent;
  53. import java.awt.event.WindowAdapter;
  54. /**
  55.  * This class is the default authorization handler. It currently handles the
  56.  * authentication schemes "Basic", "Digest", and "SOCKS5" (used for the
  57.  * SocksClient and not part of HTTP per se).
  58.  *
  59.  * <P>By default, when a username and password is required, this handler throws
  60.  * up a message box requesting the desired info. However, applications can
  61.  * {@link #setAuthorizationPrompter(HTTPClient.AuthorizationPrompter) set their
  62.  * own authorization prompter} if desired.
  63.  *
  64.  * <P><strong>Note:</strong> all methods except for
  65.  * <var>setAuthorizationPrompter</var> are meant to be invoked by the
  66.  * AuthorizationModule only, i.e. should not be invoked by the application
  67.  * (those methods are only public because implementing the
  68.  * <var>AuthorizationHandler</var> interface requires them to be).
  69.  *
  70.  * @version 0.3-3  06/05/2001
  71.  * @author Ronald Tschal鋜
  72.  * @since V0.2
  73.  */
  74. public class DefaultAuthHandler implements AuthorizationHandler, GlobalConstants
  75. {
  76.     private static final byte[] NUL = new byte[0];
  77.     private static final int DI_A1  = 0;
  78.     private static final int DI_A1S = 1;
  79.     private static final int DI_QOP = 2;
  80.     private static byte[] digest_secret = null;
  81.     private static AuthorizationPrompter prompter    = null;
  82.     private static boolean               prompterSet = false;
  83.     /**
  84.      * For Digest authentication we need to set the uri, response and
  85.      * opaque parameters. For "Basic" and "SOCKS5" nothing is done.
  86.      */
  87.     public AuthorizationInfo fixupAuthInfo(AuthorizationInfo info,
  88.    RoRequest req,
  89.    AuthorizationInfo challenge,
  90.    RoResponse resp)
  91.     throws AuthSchemeNotImplException
  92.     {
  93. // nothing to do for Basic and SOCKS5 schemes
  94. if (info.getScheme().equalsIgnoreCase("Basic")  ||
  95.     info.getScheme().equalsIgnoreCase("SOCKS5"))
  96.     return info;
  97. else if (!info.getScheme().equalsIgnoreCase("Digest"))
  98.     throw new AuthSchemeNotImplException(info.getScheme());
  99. if (Log.isEnabled(Log.AUTH))
  100.     Log.write(Log.AUTH, "Auth:  fixing up Authorization for host " +
  101. info.getHost()+":"+info.getPort() +
  102. "; scheme: " + info.getScheme() +
  103. "; realm: " + info.getRealm());
  104. return digest_fixup(info, req, challenge, resp);
  105.     }
  106.     /**
  107.      * returns the requested authorization, or null if none was given.
  108.      *
  109.      * @param challenge the parsed challenge from the server.
  110.      * @param req the request which solicited this response
  111.      * @param resp the full response received
  112.      * @return a structure containing the necessary authorization info,
  113.      *         or null
  114.      * @exception AuthSchemeNotImplException if the authentication scheme
  115.      *             in the challenge cannot be handled.
  116.      */
  117.     public AuthorizationInfo getAuthorization(AuthorizationInfo challenge,
  118.       RoRequest req, RoResponse resp)
  119.     throws AuthSchemeNotImplException, IOException
  120.     {
  121. AuthorizationInfo cred;
  122. if (Log.isEnabled(Log.AUTH))
  123.     Log.write(Log.AUTH, "Auth:  Requesting Authorization for host " +
  124. challenge.getHost()+":"+challenge.getPort() +
  125. "; scheme: " + challenge.getScheme() +
  126. "; realm: " + challenge.getRealm());
  127. // we only handle Basic, Digest and SOCKS5 authentication
  128. if (!challenge.getScheme().equalsIgnoreCase("Basic")  &&
  129.     !challenge.getScheme().equalsIgnoreCase("Digest")  &&
  130.     !challenge.getScheme().equalsIgnoreCase("SOCKS5"))
  131.     throw new AuthSchemeNotImplException(challenge.getScheme());
  132. // For digest authentication, check if stale is set
  133. if (challenge.getScheme().equalsIgnoreCase("Digest"))
  134. {
  135.     cred = digest_check_stale(challenge, req, resp);
  136.     if (cred != null)
  137. return cred;
  138. }
  139. // Ask the user for username/password
  140. NVPair answer;
  141. synchronized (getClass())
  142. {
  143.     if (!req.allowUI()  ||  prompterSet  &&  prompter == null)
  144. return null;
  145.     if (prompter == null)
  146. setDefaultPrompter();
  147.     answer = prompter.getUsernamePassword(challenge,
  148.   resp.getStatusCode() == 407);
  149. }
  150. if (answer == null)
  151.     return null;
  152. // Now process the username/password
  153. if (challenge.getScheme().equalsIgnoreCase("basic"))
  154. {
  155.     cred = new AuthorizationInfo(challenge.getHost(),
  156.  challenge.getPort(),
  157.  challenge.getScheme(),
  158.  challenge.getRealm(),
  159.  Codecs.base64Encode(
  160. answer.getName() + ":" +
  161. answer.getValue()));
  162. }
  163. else if (challenge.getScheme().equalsIgnoreCase("Digest"))
  164. {
  165.     cred = digest_gen_auth_info(challenge.getHost(),
  166. challenge.getPort(),
  167.         challenge.getRealm(), answer.getName(),
  168. answer.getValue(),
  169. req.getConnection().getContext());
  170.     cred = digest_fixup(cred, req, challenge, null);
  171. }
  172. else // SOCKS5
  173. {
  174.     NVPair[] upwd = { answer };
  175.     cred = new AuthorizationInfo(challenge.getHost(),
  176.  challenge.getPort(),
  177.  challenge.getScheme(),
  178.  challenge.getRealm(),
  179.  upwd, null);
  180. }
  181. // try to get rid of any unencoded passwords in memory
  182. answer = null;
  183. System.gc();
  184. // Done
  185. Log.write(Log.AUTH, "Auth:  Got Authorization");
  186. return cred;
  187.     }
  188.     /**
  189.      * We handle the "Authentication-Info" and "Proxy-Authentication-Info"
  190.      * headers here.
  191.      */
  192.     public void handleAuthHeaders(Response resp, RoRequest req,
  193.   AuthorizationInfo prev,
  194.   AuthorizationInfo prxy)
  195.     throws IOException
  196.     {
  197. String auth_info = resp.getHeader("Authentication-Info");
  198. String prxy_info = resp.getHeader("Proxy-Authentication-Info");
  199. if (auth_info == null  &&  prev != null  &&
  200.     hasParam(prev.getParams(), "qop", "auth-int"))
  201.     auth_info = "";
  202. if (prxy_info == null  &&  prxy != null  &&
  203.     hasParam(prxy.getParams(), "qop", "auth-int"))
  204.     prxy_info = "";
  205. try
  206. {
  207.     handleAuthInfo(auth_info, "Authentication-Info", prev, resp, req,
  208.    true);
  209.     handleAuthInfo(prxy_info, "Proxy-Authentication-Info", prxy, resp,
  210.    req, true);
  211. }
  212. catch (ParseException pe)
  213.     { throw new IOException(pe.toString()); }
  214.     }
  215.     /**
  216.      * We handle the "Authentication-Info" and "Proxy-Authentication-Info"
  217.      * trailers here.
  218.      */
  219.     public void handleAuthTrailers(Response resp, RoRequest req,
  220.    AuthorizationInfo prev,
  221.    AuthorizationInfo prxy)
  222.     throws IOException
  223.     {
  224. String auth_info = resp.getTrailer("Authentication-Info");
  225. String prxy_info = resp.getTrailer("Proxy-Authentication-Info");
  226. try
  227. {
  228.     handleAuthInfo(auth_info, "Authentication-Info", prev, resp, req,
  229.    false);
  230.     handleAuthInfo(prxy_info, "Proxy-Authentication-Info", prxy, resp,
  231.    req, false);
  232. }
  233. catch (ParseException pe)
  234.     { throw new IOException(pe.toString()); }
  235.     }
  236.     private static void handleAuthInfo(String auth_info, String hdr_name,
  237.        AuthorizationInfo prev, Response resp,
  238.        RoRequest req, boolean in_headers)
  239.     throws ParseException, IOException
  240.     {
  241. if (auth_info == null)  return;
  242. Vector pai = Util.parseHeader(auth_info);
  243. HttpHeaderElement elem;
  244. if (handle_nextnonce(prev, req,
  245.      elem = Util.getElement(pai, "nextnonce")))
  246.     pai.removeElement(elem);
  247. if (handle_discard(prev, req, elem = Util.getElement(pai, "discard")))
  248.     pai.removeElement(elem);
  249. if (in_headers)
  250. {
  251.     HttpHeaderElement qop = null;
  252.     if (pai != null  &&
  253. (qop = Util.getElement(pai, "qop")) != null  &&
  254. qop.getValue() != null)
  255.     {
  256. handle_rspauth(prev, resp, req, pai, hdr_name);
  257.     }
  258.     else if (prev != null  &&
  259.      (Util.hasToken(resp.getHeader("Trailer"), hdr_name)  &&
  260.       hasParam(prev.getParams(), "qop", null)  ||
  261.       hasParam(prev.getParams(), "qop", "auth-int")))
  262.     {
  263. handle_rspauth(prev, resp, req, null, hdr_name);
  264.     }
  265.     else if ((pai != null  &&  qop == null  &&
  266.       pai.contains(new HttpHeaderElement("digest")))  ||
  267.      (Util.hasToken(resp.getHeader("Trailer"), hdr_name)  &&
  268.       prev != null  &&
  269.       !hasParam(prev.getParams(), "qop", null)))
  270.     {
  271. handle_digest(prev, resp, req, hdr_name);
  272.     }
  273. }
  274. if (pai.size() > 0)
  275.     resp.setHeader(hdr_name, Util.assembleHeader(pai));
  276. else
  277.     resp.deleteHeader(hdr_name);
  278.     }
  279.     private static final boolean hasParam(NVPair[] params, String name,
  280.   String val)
  281.     {
  282. for (int idx=0; idx<params.length; idx++)
  283.     if (params[idx].getName().equalsIgnoreCase(name)  &&
  284. (val == null  ||  params[idx].getValue().equalsIgnoreCase(val)))
  285. return true;
  286. return false;
  287.     }
  288.     /*
  289.      * Here are all the Digest specific methods
  290.      */
  291.     private static AuthorizationInfo digest_gen_auth_info(String host, int port,
  292.   String realm,
  293.   String user,
  294.   String pass,
  295.   Object context)
  296.     {
  297. String A1 = user + ":" + realm + ":" + pass;
  298. String[] info = { MD5.hexDigest(A1), null, null };
  299. AuthorizationInfo prev = AuthorizationInfo.getAuthorization(host, port,
  300.     "Digest", realm, context);
  301. NVPair[] params;
  302. if (prev == null)
  303. {
  304.     params = new NVPair[4];
  305.     params[0] = new NVPair("username", user);
  306.     params[1] = new NVPair("uri", "");
  307.     params[2] = new NVPair("nonce", "");
  308.     params[3] = new NVPair("response", "");
  309. }
  310. else
  311. {
  312.     params = prev.getParams();
  313.     for (int idx=0; idx<params.length; idx++)
  314.     {
  315. if (params[idx].getName().equalsIgnoreCase("username"))
  316. {
  317.     params[idx] = new NVPair("username", user);
  318.     break;
  319. }
  320.     }
  321. }
  322. return new AuthorizationInfo(host, port, "Digest", realm, params, info);
  323.     }
  324.     /**
  325.      * The fixup handler
  326.      */
  327.     private static AuthorizationInfo digest_fixup(AuthorizationInfo info,
  328.   RoRequest req,
  329.   AuthorizationInfo challenge,
  330.   RoResponse resp)
  331.     throws AuthSchemeNotImplException
  332.     {
  333. // get various parameters from challenge
  334. int ch_domain=-1, ch_nonce=-1, ch_alg=-1, ch_opaque=-1, ch_stale=-1,
  335.     ch_dreq=-1, ch_qop=-1;
  336. NVPair[] ch_params = null;
  337. if (challenge != null)
  338. {
  339.     ch_params = challenge.getParams();
  340.     for (int idx=0; idx<ch_params.length; idx++)
  341.     {
  342. String name = ch_params[idx].getName().toLowerCase();
  343. if (name.equals("domain"))               ch_domain = idx;
  344. else if (name.equals("nonce"))           ch_nonce  = idx;
  345. else if (name.equals("opaque"))          ch_opaque = idx;
  346. else if (name.equals("algorithm"))       ch_alg    = idx;
  347. else if (name.equals("stale"))           ch_stale  = idx;
  348. else if (name.equals("digest-required")) ch_dreq   = idx;
  349. else if (name.equals("qop"))             ch_qop    = idx;
  350.     }
  351. }
  352. // get various parameters from info
  353. int uri=-1, user=-1, alg=-1, response=-1, nonce=-1, cnonce=-1, nc=-1,
  354.     opaque=-1, digest=-1, dreq=-1, qop=-1;
  355. NVPair[] params;
  356. String[] extra;
  357. synchronized (info) // we need to juggle nonce, nc, etc
  358. {
  359.     params = info.getParams();
  360.     for (int idx=0; idx<params.length; idx++)
  361.     {
  362. String name = params[idx].getName().toLowerCase();
  363. if (name.equals("uri"))                  uri      = idx;
  364. else if (name.equals("username"))        user     = idx;
  365. else if (name.equals("algorithm"))       alg      = idx;
  366. else if (name.equals("nonce"))           nonce    = idx;
  367. else if (name.equals("cnonce"))          cnonce   = idx;
  368. else if (name.equals("nc"))              nc       = idx;
  369. else if (name.equals("response"))        response = idx;
  370. else if (name.equals("opaque"))          opaque   = idx;
  371. else if (name.equals("digest"))          digest   = idx;
  372. else if (name.equals("digest-required")) dreq     = idx;
  373. else if (name.equals("qop"))             qop      = idx;
  374.     }
  375.     extra = (String[]) info.getExtraInfo();
  376.     // currently only MD5 hash (and "MD5-sess") is supported
  377.     if (alg != -1  &&
  378. !params[alg].getValue().equalsIgnoreCase("MD5")  &&
  379. !params[alg].getValue().equalsIgnoreCase("MD5-sess"))
  380. throw new AuthSchemeNotImplException("Digest auth scheme: " +
  381.     "Algorithm " + params[alg].getValue() +
  382.     " not implemented");
  383.     if (ch_alg != -1  &&
  384. !ch_params[ch_alg].getValue().equalsIgnoreCase("MD5") &&
  385. !ch_params[ch_alg].getValue().equalsIgnoreCase("MD5-sess"))
  386. throw new AuthSchemeNotImplException("Digest auth scheme: " +
  387.     "Algorithm " + ch_params[ch_alg].getValue()+
  388.     " not implemented");
  389.     // fix up uri and nonce
  390.     params[uri] = new NVPair("uri",
  391.     URI.escape(req.getRequestURI(), URI.escpdPathChar, false));
  392.     String old_nonce = params[nonce].getValue();
  393.     if (ch_nonce != -1  &&
  394. !old_nonce.equals(ch_params[ch_nonce].getValue()))
  395. params[nonce] = ch_params[ch_nonce];
  396.     // update or add optional attributes (opaque, algorithm, cnonce,
  397.     // nonce-count, and qop
  398.     if (ch_opaque != -1)
  399.     {
  400. if (opaque == -1)
  401. {
  402.     params = Util.resizeArray(params, params.length+1);
  403.     opaque = params.length-1;
  404. }
  405. params[opaque] = ch_params[ch_opaque];
  406.     }
  407.     if (ch_alg != -1)
  408.     {
  409. if (alg == -1)
  410. {
  411.     params = Util.resizeArray(params, params.length+1);
  412.     alg = params.length-1;
  413. }
  414. params[alg] = ch_params[ch_alg];
  415.     }
  416.     if (ch_qop != -1  ||
  417. (ch_alg != -1  &&
  418.  ch_params[ch_alg].getValue().equalsIgnoreCase("MD5-sess")))
  419.     {
  420. if (cnonce == -1)
  421. {
  422.     params = Util.resizeArray(params, params.length+1);
  423.     cnonce = params.length-1;
  424. }
  425. if (digest_secret == null)
  426.     digest_secret = gen_random_bytes(20);
  427. long l_time = System.currentTimeMillis();
  428. byte[] time = new byte[8];
  429. time[0] = (byte) (l_time & 0xFF);
  430. time[1] = (byte) ((l_time >>  8) & 0xFF);
  431. time[2] = (byte) ((l_time >> 16) & 0xFF);
  432. time[3] = (byte) ((l_time >> 24) & 0xFF);
  433. time[4] = (byte) ((l_time >> 32) & 0xFF);
  434. time[5] = (byte) ((l_time >> 40) & 0xFF);
  435. time[6] = (byte) ((l_time >> 48) & 0xFF);
  436. time[7] = (byte) ((l_time >> 56) & 0xFF);
  437. params[cnonce] =
  438.     new NVPair("cnonce", MD5.hexDigest(digest_secret, time));
  439.     }
  440.     // select qop option
  441.     if (ch_qop != -1)
  442.     {
  443. if (qop == -1)
  444. {
  445.     params = Util.resizeArray(params, params.length+1);
  446.     qop = params.length-1;
  447. }
  448. extra[DI_QOP] = ch_params[ch_qop].getValue();
  449. // select qop option
  450. String[] qops = splitList(extra[DI_QOP], ",");
  451. String p = null;
  452. for (int idx=0; idx<qops.length; idx++)
  453. {
  454.     if (qops[idx].equalsIgnoreCase("auth-int")  &&
  455. (req.getStream() == null  ||
  456.  req.getConnection().ServProtVersKnown  &&
  457.  req.getConnection().ServerProtocolVersion >= HTTP_1_1))
  458.     {
  459. p = "auth-int";
  460. break;
  461.     }
  462.     if (qops[idx].equalsIgnoreCase("auth"))
  463. p = "auth";
  464. }
  465. if (p == null)
  466. {
  467.     for (int idx=0; idx<qops.length; idx++)
  468. if (qops[idx].equalsIgnoreCase("auth-int"))
  469.     throw new AuthSchemeNotImplException(
  470. "Digest auth scheme: Can't comply with qop " +
  471. "option 'auth-int' because an HttpOutputStream " +
  472. "is being used and the server doesn't speak " +
  473. "HTTP/1.1");
  474.     throw new AuthSchemeNotImplException("Digest auth scheme: "+
  475. "None of the available qop options '" +
  476. ch_params[ch_qop].getValue() + "' implemented");
  477. }
  478. params[qop] = new NVPair("qop", p);
  479.     }
  480.     // increment nonce-count.
  481.     if (qop != -1)
  482.     {
  483. /* Note: we should actually be serializing all requests through
  484.  *       here so that the server sees the nonce-count in a
  485.  *       strictly increasing order. However, this would be a
  486.  *       *major* hassle to do, so we're just winging it. Most
  487.  *       of the time the requests will go over the wire in the
  488.  *       same order as they pass through here, but in MT apps
  489.  *       it's possible for one request to "overtake" another
  490.  *       between here and the synchronized block in
  491.  *       sendRequest().
  492.  */
  493. if (nc == -1)
  494. {
  495.     params = Util.resizeArray(params, params.length+1);
  496.     nc = params.length-1;
  497.     params[nc] = new NVPair("nc", "00000001");
  498. }
  499. else if (old_nonce.equals(params[nonce].getValue()))
  500. {
  501.     String c = Long.toHexString(
  502. Long.parseLong(params[nc].getValue(), 16) + 1);
  503.     params[nc] =
  504. new NVPair("nc", "00000000".substring(c.length()) + c);
  505. }
  506. else
  507.     params[nc] = new NVPair("nc", "00000001");
  508.     }
  509.     // calc new session key if necessary
  510.     if (challenge != null  &&
  511. (ch_stale == -1  ||
  512.  !ch_params[ch_stale].getValue().equalsIgnoreCase("true"))  &&
  513. alg != -1  &&
  514. params[alg].getValue().equalsIgnoreCase("MD5-sess"))
  515.     {
  516. extra[DI_A1S] = MD5.hexDigest(extra[DI_A1] + ":" +
  517.       params[nonce].getValue() + ":" +
  518.       params[cnonce].getValue());
  519.     }
  520.     // update parameters for next auth cycle
  521.     info.setParams(params);
  522.     info.setExtraInfo(extra);
  523. }
  524. // calc "response" attribute
  525. String hash = null;
  526. if (qop != -1 && params[qop].getValue().equalsIgnoreCase("auth-int") &&
  527.     req.getStream() == null)
  528. {
  529.     hash = MD5.hexDigest(req.getData() == null ? NUL : req.getData());
  530. }
  531. if (req.getStream() == null)
  532.     params[response] = new NVPair("response", 
  533.   calcResponseAttr(hash, extra, params, alg, uri, qop, nonce,
  534.    nc, cnonce, req.getMethod()));
  535. // calc digest if necessary
  536. AuthorizationInfo new_info;
  537. boolean ch_dreq_val = false;
  538. if (ch_dreq != -1  &&
  539.     (ch_params[ch_dreq].getValue() == null  ||
  540.      ch_params[ch_dreq].getValue().equalsIgnoreCase("true")))
  541.     ch_dreq_val = true;
  542. if ((ch_dreq_val  ||  digest != -1)  &&  req.getStream() == null)
  543. {
  544.     NVPair[] d_params;
  545.     if (digest == -1)
  546.     {
  547. d_params = Util.resizeArray(params, params.length+1);
  548. digest = params.length;
  549.     }
  550.     else
  551. d_params = params;
  552.     d_params[digest] = new NVPair("digest",
  553.    calc_digest(req, extra[DI_A1], params[nonce].getValue()));
  554.     if (dreq == -1) // if server requires digest, then so do we...
  555.     {
  556. dreq = d_params.length;
  557. d_params = Util.resizeArray(d_params, d_params.length+1);
  558. d_params[dreq] = new NVPair("digest-required", "true");
  559.     }
  560.     new_info = new AuthorizationInfo(info.getHost(), info.getPort(),
  561.      info.getScheme(), info.getRealm(),
  562.      d_params, extra);
  563. }
  564. else if (ch_dreq_val)
  565.     new_info = null;
  566. else
  567.     new_info = new AuthorizationInfo(info.getHost(), info.getPort(),
  568.      info.getScheme(), info.getRealm(),
  569.      params, extra);
  570. // add info for other domains, if listed
  571. boolean from_server = (challenge != null)  &&
  572.     challenge.getHost().equalsIgnoreCase(req.getConnection().getHost());
  573. if (ch_domain != -1)
  574. {
  575.     URI base = null;
  576.     try
  577.     {
  578. base = new URI(req.getConnection().getProtocol(),
  579.        req.getConnection().getHost(),
  580.        req.getConnection().getPort(),
  581.        req.getRequestURI());
  582.     }
  583.     catch (ParseException pe)
  584. { }
  585.     StringTokenizer tok =
  586. new StringTokenizer(ch_params[ch_domain].getValue());
  587.     while (tok.hasMoreTokens())
  588.     {
  589. URI Uri;
  590. try
  591.     { Uri = new URI(base, tok.nextToken()); }
  592. catch (ParseException pe)
  593.     { continue; }
  594. if (Uri.getHost() == null)
  595.     continue;
  596. AuthorizationInfo tmp =
  597.     AuthorizationInfo.getAuthorization(Uri.getHost(),
  598.        Uri.getPort(),
  599.        info.getScheme(),
  600.        info.getRealm(),
  601.      req.getConnection().getContext());
  602. if (tmp == null)
  603. {
  604.     params[uri] = new NVPair("uri", Uri.getPathAndQuery());
  605.     tmp = new AuthorizationInfo(Uri.getHost(), Uri.getPort(),
  606.         info.getScheme(),
  607. info.getRealm(), params,
  608. extra);
  609.     AuthorizationInfo.addAuthorization(tmp);
  610. }
  611. if (from_server)
  612.     tmp.addPath(Uri.getPathAndQuery());
  613.     }
  614. }
  615. else if (from_server  &&  challenge != null)
  616. {
  617.     // Spec says that if no domain attribute is present then the
  618.     // whole server should be considered being in the same space
  619.     AuthorizationInfo tmp =
  620. AuthorizationInfo.getAuthorization(challenge.getHost(),
  621.    challenge.getPort(),
  622.    info.getScheme(),
  623.    info.getRealm(),
  624.      req.getConnection().getContext());
  625.     if (tmp != null)  tmp.addPath("/");
  626. }
  627. // now return the one to use
  628. return new_info;
  629.     }
  630.     /**
  631.      * @return the fixed info is stale=true; null otherwise
  632.      */
  633.     private static AuthorizationInfo digest_check_stale(
  634.       AuthorizationInfo challenge,
  635.       RoRequest req, RoResponse resp)
  636.     throws AuthSchemeNotImplException, IOException
  637.     {
  638. AuthorizationInfo cred = null;
  639. NVPair[] params = challenge.getParams();
  640. for (int idx=0; idx<params.length; idx++)
  641. {
  642.     String name = params[idx].getName();
  643.     if (name.equalsIgnoreCase("stale")  &&
  644. params[idx].getValue().equalsIgnoreCase("true"))
  645.     {
  646. cred = AuthorizationInfo.getAuthorization(challenge, req, resp,
  647.   false);
  648. if (cred != null) // should always be the case
  649.     return digest_fixup(cred, req, challenge, resp);
  650. break; // should never be reached
  651.     }
  652. }
  653. return cred;
  654.     }
  655.     /**
  656.      * Handle nextnonce field.
  657.      */
  658.     private static boolean handle_nextnonce(AuthorizationInfo prev,
  659.     RoRequest req,
  660.     HttpHeaderElement nextnonce)
  661.     throws IOException
  662.     {
  663. if (prev == null  ||  nextnonce == null  ||
  664.     nextnonce.getValue() == null)
  665.     return false;
  666. AuthorizationInfo ai;
  667. try
  668.     { ai = AuthorizationInfo.getAuthorization(prev, req, null, false); }
  669. catch (AuthSchemeNotImplException asnie)
  670.     { ai = prev; /* shouldn't happen */ }
  671. synchronized (ai)
  672. {
  673.     NVPair[] params = ai.getParams();
  674.     params = setValue(params, "nonce", nextnonce.getValue());
  675.     params = setValue(params, "nc", "00000000");
  676.     ai.setParams(params);
  677. }
  678. return true;
  679.     }
  680.     /**
  681.      * Handle digest field of the Authentication-Info response header.
  682.      */
  683.     private static boolean handle_digest(AuthorizationInfo prev, Response resp,
  684.  RoRequest req, String hdr_name)
  685.     throws IOException
  686.     {
  687. if (prev == null)
  688.     return false;
  689. NVPair[] params = prev.getParams();
  690. VerifyDigest
  691.     verifier = new VerifyDigest(((String[]) prev.getExtraInfo())[0],
  692. getValue(params, "nonce"),
  693. req.getMethod(),
  694. getValue(params, "uri"),
  695. hdr_name, resp);
  696. if (resp.hasEntity())
  697. {
  698.     Log.write(Log.AUTH, "Auth:  pushing md5-check-stream to verify "+
  699. "digest from " + hdr_name);
  700.     resp.inp_stream = new MD5InputStream(resp.inp_stream, verifier);
  701. }
  702. else
  703. {
  704.     Log.write(Log.AUTH, "Auth:  verifying digest from " + hdr_name);
  705.     verifier.verifyHash(MD5.digest(NUL), 0);
  706. }
  707. return true;
  708.     }
  709.     /**
  710.      * Handle rspauth field of the Authentication-Info response header.
  711.      */
  712.     private static boolean handle_rspauth(AuthorizationInfo prev, Response resp,
  713.   RoRequest req, Vector auth_info,
  714.   String hdr_name)
  715.     throws IOException
  716.     {
  717. if (prev == null)
  718.     return false;
  719. // get the parameters we sent
  720. NVPair[] params = prev.getParams();
  721. int uri=-1, alg=-1, nonce=-1, cnonce=-1, nc=-1;
  722. for (int idx=0; idx<params.length; idx++)
  723. {
  724.     String name = params[idx].getName().toLowerCase();
  725.     if (name.equals("uri"))            uri    = idx;
  726.     else if (name.equals("algorithm")) alg    = idx;
  727.     else if (name.equals("nonce"))     nonce  = idx;
  728.     else if (name.equals("cnonce"))    cnonce = idx;
  729.     else if (name.equals("nc"))        nc     = idx;
  730. }
  731. // create hash verifier to verify rspauth
  732. VerifyRspAuth
  733.     verifier = new VerifyRspAuth(params[uri].getValue(),
  734.       ((String[]) prev.getExtraInfo())[0],
  735.       (alg == -1 ? null : params[alg].getValue()),
  736.   params[nonce].getValue(),
  737.       (cnonce == -1 ? "" : params[cnonce].getValue()),
  738.       (nc == -1 ? "" : params[nc].getValue()),
  739.       hdr_name, resp);
  740. // if Authentication-Info in header and qop=auth then verify immediately
  741. HttpHeaderElement qop = null;
  742. if (auth_info != null  &&
  743.     (qop = Util.getElement(auth_info, "qop")) != null  &&
  744.     qop.getValue() != null  &&
  745.     (qop.getValue().equalsIgnoreCase("auth")  ||
  746.      !resp.hasEntity()  &&  qop.getValue().equalsIgnoreCase("auth-int"))
  747.    )
  748. {
  749.     Log.write(Log.AUTH, "Auth:  verifying rspauth from " + hdr_name);
  750.     verifier.verifyHash(MD5.digest(NUL), 0);
  751. }
  752. else
  753. {
  754.     // else push md5 stream and verify after body
  755.     Log.write(Log.AUTH, "Auth:  pushing md5-check-stream to verify "+
  756. "rspauth from " + hdr_name);
  757.     resp.inp_stream = new MD5InputStream(resp.inp_stream, verifier);
  758. }
  759. return true;
  760.     }
  761.     /**
  762.      * Calc "response" attribute for a request.
  763.      */
  764.     private static String calcResponseAttr(String hash, String[] extra,
  765.    NVPair[] params, int alg,
  766.    int uri, int qop, int nonce,
  767.    int nc, int cnonce, String method)
  768.     {
  769. String A1, A2, resp_val;
  770. if (alg != -1  &&
  771.     params[alg].getValue().equalsIgnoreCase("MD5-sess"))
  772.     A1 = extra[DI_A1S];
  773. else
  774.     A1 = extra[DI_A1];
  775. A2 = method + ":" + params[uri].getValue();
  776. if (qop != -1  &&
  777.     params[qop].getValue().equalsIgnoreCase("auth-int"))
  778. {
  779.     A2 += ":" + hash;
  780. }
  781. A2 = MD5.hexDigest(A2);
  782. if (qop == -1)
  783.     resp_val =
  784. MD5.hexDigest(A1 + ":" + params[nonce].getValue() + ":" + A2);
  785. else
  786.     resp_val =
  787. MD5.hexDigest(A1 + ":" + params[nonce].getValue() + ":" +
  788.       params[nc].getValue() + ":" +
  789.       params[cnonce].getValue() + ":" +
  790.       params[qop].getValue() + ":" + A2);
  791. return resp_val;
  792.     }
  793.     /**
  794.      * Calculates the digest of the request body. This was in RFC-2069
  795.      * and draft-ietf-http-authentication-00.txt, but has subsequently
  796.      * been removed. Here for backwards compatibility.
  797.      */
  798.     private static String calc_digest(RoRequest req, String A1_hash,
  799.       String nonce)
  800.     {
  801. if (req.getStream() != null)
  802.     return "";
  803. int ct=-1, ce=-1, lm=-1, ex=-1, dt=-1;
  804. for (int idx=0; idx<req.getHeaders().length; idx++)
  805. {
  806.     String name = req.getHeaders()[idx].getName();
  807.     if (name.equalsIgnoreCase("Content-type"))
  808. ct = idx;
  809.     else if (name.equalsIgnoreCase("Content-Encoding"))
  810. ce = idx;
  811.     else if (name.equalsIgnoreCase("Last-Modified"))
  812. lm = idx;
  813.     else if (name.equalsIgnoreCase("Expires"))
  814. ex = idx;
  815.     else if (name.equalsIgnoreCase("Date"))
  816. dt = idx;
  817. }
  818. NVPair[] hdrs = req.getHeaders();
  819. byte[] entity_body = (req.getData() == null ? NUL : req.getData());
  820. String entity_hash = MD5.hexDigest(entity_body);
  821. String entity_info = MD5.hexDigest(req.getRequestURI() + ":" +
  822.      (ct == -1 ? "" : hdrs[ct].getValue()) + ":" +
  823.      entity_body.length + ":" +
  824.      (ce == -1 ? "" : hdrs[ce].getValue()) + ":" +
  825.      (lm == -1 ? "" : hdrs[lm].getValue()) + ":" +
  826.      (ex == -1 ? "" : hdrs[ex].getValue()));
  827. String entity_digest = A1_hash + ":" + nonce + ":" + req.getMethod() +
  828. ":" + (dt == -1 ? "" : hdrs[dt].getValue()) +
  829. ":" + entity_info + ":" + entity_hash;
  830. if (Log.isEnabled(Log.AUTH))
  831. {
  832.     Log.write(Log.AUTH, "Auth:  Entity-Info: '" + req.getRequestURI() + ":" +
  833.  (ct == -1 ? "" : hdrs[ct].getValue()) + ":" +
  834.  entity_body.length + ":" +
  835.  (ce == -1 ? "" : hdrs[ce].getValue()) + ":" +
  836.  (lm == -1 ? "" : hdrs[lm].getValue()) + ":" +
  837.  (ex == -1 ? "" : hdrs[ex].getValue()) +"'");
  838.     Log.write(Log.AUTH, "Auth:  Entity-Body: '" + entity_hash + "'");
  839.     Log.write(Log.AUTH, "Auth:  Entity-Digest: '" + entity_digest + "'");
  840. }
  841. return MD5.hexDigest(entity_digest);
  842.     }
  843.     /**
  844.      * Handle discard token
  845.      */
  846.     private static boolean handle_discard(AuthorizationInfo prev, RoRequest req,
  847.   HttpHeaderElement discard)
  848.     {
  849. if (discard != null  &&  prev != null)
  850. {
  851.     AuthorizationInfo.removeAuthorization(prev,
  852.     req.getConnection().getContext());
  853.     return true;
  854. }
  855. return false;
  856.     }
  857.     /**
  858.      * Generate <var>num</var> bytes of random data.
  859.      *
  860.      * @param num  the number of bytes to generate
  861.      * @return a byte array of random data
  862.      */
  863.     private static byte[] gen_random_bytes(int num)
  864.     {
  865. // first try /dev/random
  866. try
  867. {
  868.     FileInputStream rnd = new FileInputStream("/dev/random");
  869.     DataInputStream din = new DataInputStream(rnd);
  870.     byte[] data = new byte[num];
  871.     din.readFully(data);
  872.     try { din.close(); } catch (IOException ioe) { }
  873.     return data;
  874. }
  875. catch (Throwable t)
  876.     { }
  877. /* This is probably a much better generator, but it can be awfully
  878.  * slow (~ 6 secs / byte on my old LX)
  879.  */
  880. //return new java.security.SecureRandom().getSeed(num);
  881. /* this is faster, but needs to be done better... */
  882. byte[] data = new byte[num];
  883. try
  884. {
  885.     long fm = Runtime.getRuntime().freeMemory();
  886.     data[0] = (byte) (fm & 0xFF);
  887.     data[1] = (byte) ((fm >>  8) & 0xFF);
  888.     int h = data.hashCode();
  889.     data[2] = (byte) (h & 0xFF);
  890.     data[3] = (byte) ((h >>  8) & 0xFF);
  891.     data[4] = (byte) ((h >> 16) & 0xFF);
  892.     data[5] = (byte) ((h >> 24) & 0xFF);
  893.     long time = System.currentTimeMillis();
  894.     data[6] = (byte) (time & 0xFF);
  895.     data[7] = (byte) ((time >>  8) & 0xFF);
  896. }
  897. catch (ArrayIndexOutOfBoundsException aioobe)
  898.     { }
  899. return data;
  900.     }
  901.     /**
  902.      * Return the value of the first NVPair whose name matches the key
  903.      * using a case-insensitive search.
  904.      *
  905.      * @param list an array of NVPair's
  906.      * @param key  the key to search for
  907.      * @return the value of the NVPair with that key, or null if not
  908.      *         found.
  909.      */
  910.     private final static String getValue(NVPair[] list, String key)
  911.     {
  912. int len = list.length;
  913. for (int idx=0; idx<len; idx++)
  914.     if (list[idx].getName().equalsIgnoreCase(key))
  915. return list[idx].getValue();
  916. return null;
  917.     }
  918.     /**
  919.      * Return the index of the first NVPair whose name matches the key
  920.      * using a case-insensitive search.
  921.      *
  922.      * @param list an array of NVPair's
  923.      * @param key  the key to search for
  924.      * @return the index of the NVPair with that key, or -1 if not
  925.      *         found.
  926.      */
  927.     private final static int getIndex(NVPair[] list, String key)
  928.     {
  929. int len = list.length;
  930. for (int idx=0; idx<len; idx++)
  931.     if (list[idx].getName().equalsIgnoreCase(key))
  932. return idx;
  933. return -1;
  934.     }
  935.     /**
  936.      * Sets the value of the NVPair with the name that matches the key
  937.      * (case-insensitive). If no name matches, a new entry is created.
  938.      *
  939.      * @param list an array of NVPair's
  940.      * @param key  the name of the NVPair
  941.      * @param val  the value of the new NVPair
  942.      * @return the (possibly) new list
  943.      */
  944.     private final static NVPair[] setValue(NVPair[] list, String key, String val)
  945.     {
  946. int idx = getIndex(list, key);
  947. if (idx == -1)
  948. {
  949.     idx = list.length;
  950.     list = Util.resizeArray(list, list.length+1);
  951. }
  952. list[idx] = new NVPair(key, val);
  953. return list;
  954.     }
  955.     /**
  956.      * Split a list into an array of Strings, using sep as the
  957.      * separator and removing whitespace around the separator.
  958.      */
  959.     private static String[] splitList(String str, String sep)
  960.     {
  961. if (str == null)  return new String[0];
  962. StringTokenizer tok = new StringTokenizer(str, sep);
  963. String[] list = new String[tok.countTokens()];
  964. for (int idx=0; idx<list.length; idx++)
  965.     list[idx] = tok.nextToken().trim();
  966. return list;
  967.     }
  968.     /**
  969.      * Produce a string of the form "A5:22:F1:0B:53"
  970.      */
  971.     static String hex(byte[] buf)
  972.     {
  973. StringBuffer str = new StringBuffer(buf.length*3);
  974. for (int idx=0; idx<buf.length; idx++)
  975. {
  976.     str.append(Character.forDigit((buf[idx] >> 4) & 15, 16));
  977.     str.append(Character.forDigit(buf[idx] & 15, 16));
  978.     str.append(':');
  979. }
  980. str.setLength(str.length()-1);
  981. return str.toString();
  982.     }
  983.     static final byte[] unHex(String hex)
  984.     {
  985. byte[] digest = new byte[hex.length()/2];
  986. for (int idx=0; idx<digest.length; idx++)
  987. {
  988.     digest[idx] = (byte) (0xFF & Integer.parseInt(
  989.   hex.substring(2*idx, 2*(idx+1)), 16));
  990. }
  991. return digest;
  992.     }
  993.     /**
  994.      * Set a new username/password prompter.
  995.      *
  996.      * @param prompt the AuthorizationPrompter to use whenever a username
  997.      *               and password are needed; if null, no querying will be
  998.      *               done
  999.      * @return the previous prompter
  1000.      */
  1001.     public static synchronized AuthorizationPrompter setAuthorizationPrompter(
  1002.     AuthorizationPrompter prompt)
  1003.     {
  1004. AuthorizationPrompter prev = prompter;
  1005. prompter    = prompt;
  1006. prompterSet = true;
  1007. return prev;
  1008.     }
  1009.     /**
  1010.      * Set the default authorization prompter. It first tries to figure out
  1011.      * if the AWT is running, and if it is then the GUI popup prompter is used;
  1012.      * otherwise the command line prompter is used.
  1013.      */
  1014.     private static void setDefaultPrompter()
  1015.     {
  1016. // if the AWT is running use the popup box; else use the
  1017. // the command line prompter.
  1018. if (!SimpleAuthPrompt.canUseCLPrompt()  ||  isAWTRunning())
  1019.     prompter = new SimpleAuthPopup();
  1020. else
  1021.     prompter = new SimpleAuthPrompt();
  1022.     }
  1023.     /**
  1024.      * Try and figure out if the AWT is running. This is done by searching all
  1025.      * threads and looking for one whose name starts with "AWT-". 
  1026.      */
  1027.     private static final boolean isAWTRunning() {
  1028. // find top-level thread group
  1029. ThreadGroup root = Thread.currentThread().getThreadGroup();
  1030. while (root.getParent() != null)
  1031.     root = root.getParent();
  1032. // search all threads
  1033. Thread[] t_list = new Thread[root.activeCount() + 5];
  1034. int t_num = root.enumerate(t_list);
  1035. for (int idx=0; idx<t_num; idx++)
  1036. {
  1037.     if (t_list[idx].getName().startsWith("AWT-"))
  1038. return true;
  1039. }
  1040. return false;
  1041.     }
  1042. }
  1043. /**
  1044.  * This verifies the "rspauth" from draft-ietf-http-authentication-03
  1045.  */
  1046. class VerifyRspAuth implements HashVerifier, GlobalConstants
  1047. {
  1048.     private String     uri;
  1049.     private String     HA1;
  1050.     private String     alg;
  1051.     private String     nonce;
  1052.     private String     cnonce;
  1053.     private String     nc;
  1054.     private String     hdr;
  1055.     private RoResponse resp;
  1056.     public VerifyRspAuth(String uri, String HA1, String alg, String nonce,
  1057.  String cnonce, String nc, String hdr, RoResponse resp)
  1058.     {
  1059. this.uri    = uri;
  1060. this.HA1    = HA1;
  1061. this.alg    = alg;
  1062. this.nonce  = nonce;
  1063. this.cnonce = cnonce;
  1064. this.nc     = nc;
  1065. this.hdr    = hdr;
  1066. this.resp   = resp;
  1067.     }
  1068.     public void verifyHash(byte[] hash, long len)  throws IOException
  1069.     {
  1070. String auth_info = resp.getHeader(hdr);
  1071. if (auth_info == null)
  1072.     auth_info = resp.getTrailer(hdr);
  1073. if (auth_info == null)
  1074.     return;
  1075. Vector pai;
  1076. try
  1077.     { pai = Util.parseHeader(auth_info); }
  1078. catch (ParseException pe)
  1079.     { throw new IOException(pe.toString()); }
  1080. String qop;
  1081. HttpHeaderElement elem = Util.getElement(pai, "qop");
  1082. if (elem == null  ||  (qop = elem.getValue()) == null  ||
  1083.     (!qop.equalsIgnoreCase("auth")  &&
  1084.      !qop.equalsIgnoreCase("auth-int")))
  1085.     return;
  1086. elem = Util.getElement(pai, "rspauth");
  1087. if (elem == null  ||  elem.getValue() == null) return;
  1088. byte[] digest = DefaultAuthHandler.unHex(elem.getValue());
  1089. elem = Util.getElement(pai, "cnonce");
  1090. if (elem != null  &&  elem.getValue() != null  &&
  1091.     !elem.getValue().equals(cnonce))
  1092.     throw new IOException("Digest auth scheme: received wrong " +
  1093.   "client-nonce '" + elem.getValue() +
  1094.   "' - expected '" + cnonce + "'");
  1095. elem = Util.getElement(pai, "nc");
  1096. if (elem != null  &&  elem.getValue() != null  &&
  1097.     !elem.getValue().equals(nc))
  1098.     throw new IOException("Digest auth scheme: received wrong " +
  1099.   "nonce-count '" + elem.getValue() +
  1100.   "' - expected '" + nc + "'");
  1101. String A1, A2;
  1102. if (alg != null  &&  alg.equalsIgnoreCase("MD5-sess"))
  1103.     A1 = MD5.hexDigest(HA1 + ":" + nonce + ":" + cnonce);
  1104. else
  1105.     A1 = HA1;
  1106. // draft-01 was: A2 = resp.getStatusCode() + ":" + uri;
  1107. A2 = ":" + uri;
  1108. if (qop.equalsIgnoreCase("auth-int"))
  1109.     A2 += ":" + MD5.toHex(hash);
  1110. A2 = MD5.hexDigest(A2);
  1111. hash = MD5.digest(A1 + ":" + nonce + ":" +  nc + ":" + cnonce + ":" +
  1112.   qop + ":" + A2);
  1113. for (int idx=0; idx<hash.length; idx++)
  1114. {
  1115.     if (hash[idx] != digest[idx])
  1116. throw new IOException("MD5-Digest mismatch: expected " +
  1117.       DefaultAuthHandler.hex(digest) +
  1118.       " but calculated " +
  1119.       DefaultAuthHandler.hex(hash));
  1120. }
  1121. Log.write(Log.AUTH, "Auth:  rspauth from " + hdr +
  1122.     " successfully verified");
  1123.     }
  1124. }
  1125. /**
  1126.  * This verifies the "digest" from rfc-2069
  1127.  */
  1128. class VerifyDigest implements HashVerifier, GlobalConstants
  1129. {
  1130.     private String     HA1;
  1131.     private String     nonce;
  1132.     private String     method;
  1133.     private String     uri;
  1134.     private String     hdr;
  1135.     private RoResponse resp;
  1136.     public VerifyDigest(String HA1, String nonce, String method, String uri,
  1137. String hdr, RoResponse resp)
  1138.     {
  1139. this.HA1    = HA1;
  1140. this.nonce  = nonce;
  1141. this.method = method;
  1142. this.uri    = uri;
  1143. this.hdr    = hdr;
  1144. this.resp   = resp;
  1145.     }
  1146.     public void verifyHash(byte[] hash, long len)  throws IOException
  1147.     {
  1148. String auth_info = resp.getHeader(hdr);
  1149. if (auth_info == null)
  1150.     auth_info = resp.getTrailer(hdr);
  1151. if (auth_info == null)
  1152.     return;
  1153. Vector pai;
  1154. try
  1155.     { pai = Util.parseHeader(auth_info); }
  1156. catch (ParseException pe)
  1157.     { throw new IOException(pe.toString()); }
  1158. HttpHeaderElement elem = Util.getElement(pai, "digest");
  1159. if (elem == null  ||  elem.getValue() == null)
  1160.     return;
  1161. byte[] digest = DefaultAuthHandler.unHex(elem.getValue());
  1162. String entity_info = MD5.hexDigest(
  1163. uri + ":" +
  1164. header_val("Content-Type", resp) + ":" +
  1165. header_val("Content-Length", resp) + ":" +
  1166. header_val("Content-Encoding", resp) + ":" +
  1167. header_val("Last-Modified", resp) + ":" +
  1168. header_val("Expires", resp));
  1169. hash = MD5.digest(HA1 + ":" + nonce + ":" + method + ":" +
  1170.   header_val("Date", resp) +
  1171.   ":" + entity_info + ":" + MD5.toHex(hash));
  1172. for (int idx=0; idx<hash.length; idx++)
  1173. {
  1174.     if (hash[idx] != digest[idx])
  1175. throw new IOException("MD5-Digest mismatch: expected " +
  1176.       DefaultAuthHandler.hex(digest) +
  1177.       " but calculated " +
  1178.       DefaultAuthHandler.hex(hash));
  1179. }
  1180. Log.write(Log.AUTH, "Auth:  digest from " + hdr +
  1181.     " successfully verified");
  1182.     }
  1183.     private static final String header_val(String hdr_name, RoResponse resp)
  1184.     throws IOException
  1185.     {
  1186. String hdr = resp.getHeader(hdr_name);
  1187. String tlr = resp.getTrailer(hdr_name);
  1188. return (hdr != null ? hdr : (tlr != null ? tlr : ""));
  1189.     }
  1190. }
  1191. class SimpleAuthPopup implements AuthorizationPrompter
  1192. {
  1193.     private static BasicAuthBox inp = null;
  1194.     /**
  1195.      * the method called by DefaultAuthHandler.
  1196.      *
  1197.      * @return the username/password pair
  1198.      */
  1199.     public NVPair getUsernamePassword(AuthorizationInfo challenge, boolean forProxy)
  1200.     {
  1201. String line1, line2, line3;
  1202. if (challenge.getScheme().equalsIgnoreCase("SOCKS5"))
  1203. {
  1204.     line1 = "Enter username and password for SOCKS server on host";
  1205.     line2 = challenge.getHost();
  1206.     line3 = "Authentication Method: username/password";
  1207. }
  1208. else
  1209. {
  1210.     line1 = "Enter username and password for realm `" +
  1211.     challenge.getRealm() + "'";
  1212.     line2 = "on host " + challenge.getHost() + ":" +
  1213.     challenge.getPort();
  1214.     line3 = "Authentication Scheme: " + challenge.getScheme();
  1215. }
  1216. synchronized(getClass())
  1217. {
  1218.     if (inp == null)
  1219. inp = new BasicAuthBox();
  1220. }
  1221. return inp.getInput(line1, line2, line3, challenge.getScheme());
  1222.     }
  1223.     /**
  1224.      * This class implements a simple popup that request username and password
  1225.      * used for the "basic" and "digest" authentication schemes.
  1226.      *
  1227.      * @version 0.3-3  06/05/2001
  1228.      * @author Ronald Tschal鋜
  1229.      */
  1230.     private static class BasicAuthBox extends Frame
  1231.     {
  1232. private final static String title = "Authorization Request";
  1233. private Dimension           screen;
  1234. private Label               line1, line2, line3;
  1235. private TextField           user, pass;
  1236. private int                 done;
  1237. private final static int    OK = 1, CANCEL = 0;
  1238. /**
  1239.  * Constructs the popup with two lines of text above the input fields
  1240.  */
  1241. BasicAuthBox()
  1242. {
  1243.     super(title);
  1244.     screen = getToolkit().getScreenSize();
  1245.     addNotify();
  1246.     addWindowListener(new Close());
  1247.     setLayout(new BorderLayout());
  1248.     Panel p = new Panel(new GridLayout(3,1));
  1249.     p.add(line1 = new Label());
  1250.     p.add(line2 = new Label());
  1251.     p.add(line3 = new Label());
  1252.     add("North", p);
  1253.     p = new Panel(new GridLayout(2,1));
  1254.     p.add(new Label("Username:"));
  1255.     p.add(new Label("Password:"));
  1256.     add("West", p);
  1257.     p = new Panel(new GridLayout(2,1));
  1258.     p.add(user = new TextField(30));
  1259.     p.add(pass = new TextField(30));
  1260.     pass.addActionListener(new Ok());
  1261.     pass.setEchoChar('*');
  1262.     add("East", p);
  1263.     GridBagLayout gb = new GridBagLayout();
  1264.     p = new Panel(gb);
  1265.     GridBagConstraints constr = new GridBagConstraints();
  1266.     Panel pp = new Panel();
  1267.     p.add(pp);
  1268.     constr.gridwidth = GridBagConstraints.REMAINDER;
  1269.     gb.setConstraints(pp, constr);
  1270.     constr.gridwidth = 1;
  1271.     constr.weightx = 1.0;
  1272.     Button b;
  1273.     p.add(b = new Button("  OK  "));
  1274.     b.addActionListener(new Ok());
  1275.     constr.weightx = 1.0;
  1276.     gb.setConstraints(b, constr);
  1277.     p.add(b = new Button("Clear"));
  1278.     b.addActionListener(new Clear());
  1279.     constr.weightx = 2.0;
  1280.     gb.setConstraints(b, constr);
  1281.     p.add(b = new Button("Cancel"));
  1282.     b.addActionListener(new Cancel());
  1283.     constr.weightx = 1.0;
  1284.     gb.setConstraints(b, constr);
  1285.     add("South", p);
  1286.     pack();
  1287. }
  1288. /**
  1289.  * our event handlers
  1290.  */
  1291. private class Ok implements ActionListener
  1292. {
  1293.     public void actionPerformed(ActionEvent ae)
  1294.     {
  1295. done = OK;
  1296. synchronized (BasicAuthBox.this)
  1297.     { BasicAuthBox.this.notifyAll(); }
  1298.     }
  1299. }
  1300. private class Clear implements ActionListener
  1301. {
  1302.     public void actionPerformed(ActionEvent ae)
  1303.     {
  1304. user.setText("");
  1305. pass.setText("");
  1306. user.requestFocus();
  1307.     }
  1308. }
  1309. private class Cancel implements ActionListener
  1310. {
  1311.     public void actionPerformed(ActionEvent ae)
  1312.     {
  1313. done = CANCEL;
  1314. synchronized (BasicAuthBox.this)
  1315.     { BasicAuthBox.this.notifyAll(); }
  1316.     }
  1317. }
  1318. private class Close extends WindowAdapter
  1319. {
  1320.     public void windowClosing(WindowEvent we)
  1321.     {
  1322. new Cancel().actionPerformed(null);
  1323.     }
  1324. }
  1325. /**
  1326.  * the method called by SimpleAuthPopup.
  1327.  *
  1328.  * @return the username/password pair
  1329.  */
  1330. synchronized NVPair getInput(String l1, String l2, String l3,
  1331.      String scheme)
  1332. {
  1333.     line1.setText(l1);
  1334.     line2.setText(l2);
  1335.     line3.setText(l3);
  1336.     line1.invalidate();
  1337.     line2.invalidate();
  1338.     line3.invalidate();
  1339.     setResizable(true);
  1340.     pack();
  1341.     setResizable(false);
  1342.     setLocation((screen.width-getPreferredSize().width)/2,
  1343. (int) ((screen.height-getPreferredSize().height)/2*.7));
  1344.     boolean user_focus = true;
  1345.     if (scheme.equalsIgnoreCase("NTLM"))
  1346.     {
  1347. // prefill the user field with the username
  1348. try
  1349. {
  1350.     user.setText(System.getProperty("user.name", ""));
  1351.     user_focus = false;
  1352. }
  1353. catch (SecurityException se)
  1354.     { }
  1355.     }
  1356.     setVisible(true);
  1357.     if (user_focus)
  1358. user.requestFocus();
  1359.     else
  1360. pass.requestFocus();
  1361.     try { wait(); } catch (InterruptedException e) { }
  1362.     setVisible(false);
  1363.     NVPair result = new NVPair(user.getText(), pass.getText());
  1364.     user.setText("");
  1365.     pass.setText("");
  1366.     if (done == CANCEL)
  1367. return null;
  1368.     else
  1369. return result;
  1370. }
  1371.     }
  1372. }
  1373. /**
  1374.  * This class implements a simple command line prompter that request
  1375.  * username and password used for the "basic" and "digest" authentication
  1376.  * schemes.
  1377.  *
  1378.  * @version 0.3-3  06/05/2001
  1379.  * @author Ronald Tschal鋜
  1380.  */
  1381. class SimpleAuthPrompt implements AuthorizationPrompter
  1382. {
  1383.     /**
  1384.      * the method called by DefaultAuthHandler.
  1385.      *
  1386.      * @return the username/password pair
  1387.      */
  1388.     public NVPair getUsernamePassword(AuthorizationInfo challenge, boolean forProxy)
  1389.     {
  1390. String user, pass;
  1391. if (challenge.getScheme().equalsIgnoreCase("SOCKS5"))
  1392. {
  1393.     System.out.println("Enter username and password for SOCKS " +
  1394.        "server on host " + challenge.getHost());
  1395.     System.out.println("Authentication Method: username/password");
  1396. }
  1397. else
  1398. {
  1399.     System.out.println("Enter username and password for realm `" +
  1400.        challenge.getRealm() + "' on host " +
  1401.        challenge.getHost() + ":" +
  1402.        challenge.getPort());
  1403.     System.out.println("Authentication Scheme: " +
  1404.        challenge.getScheme());
  1405. }
  1406. // get username
  1407. BufferedReader inp =
  1408.     new BufferedReader(new InputStreamReader(System.in));
  1409. System.out.print("Username: "); System.out.flush();
  1410. try
  1411.     { user = inp.readLine(); }
  1412. catch (IOException ioe)
  1413.     { return null; }
  1414. if (user == null  ||  user.length() == 0)
  1415.     return null; // cancel'd
  1416. // get password
  1417. echo(false);
  1418. System.out.print("Password: "); System.out.flush();
  1419. try
  1420.     { pass = inp.readLine(); }
  1421. catch (IOException ioe)
  1422.     { return null; }
  1423. System.out.println();
  1424. echo(true);
  1425. if (pass == null)
  1426.     return null; // cancel'd
  1427. // done
  1428. return new NVPair(user, pass);
  1429.     }
  1430.     /*
  1431.      * Turn command-line echoing of typed characters on or off.
  1432.      */
  1433.     private static void echo(boolean on)
  1434.     {
  1435. String os = System.getProperty("os.name");
  1436. String[] cmd = null;
  1437. if (os.equalsIgnoreCase("Windows 95")  ||
  1438.     os.equalsIgnoreCase("Windows NT"))
  1439.     // I don't think this works on M$ ...
  1440.     cmd = new String[] { "echo", on ? "on" : "off" };
  1441. else if (os.equalsIgnoreCase("Windows")  ||
  1442.  os.equalsIgnoreCase("16-bit Windows"))
  1443.     ; // ???
  1444. else if (os.equalsIgnoreCase("OS/2"))
  1445.     ; // ???
  1446. else if (os.equalsIgnoreCase("Mac OS")  ||
  1447.  os.equalsIgnoreCase("MacOS"))
  1448.     ; // ???
  1449. else if (os.equalsIgnoreCase("OpenVMS") ||
  1450.  os.equalsIgnoreCase("VMS"))
  1451.     cmd = new String[] { "SET TERMINAL " + (on ? "/ECHO" : "/NOECHO") };
  1452. else // probably unix
  1453.     cmd = new String[] { "/bin/sh", "-c",
  1454.  "stty " + (on ? "echo" : "-echo") + " < /dev/tty" };
  1455.         if (cmd != null)
  1456.     try
  1457. { Runtime.getRuntime().exec(cmd).waitFor(); }
  1458.     catch (Exception e)
  1459. { }
  1460.     }
  1461.     /**
  1462.      * @return true for Unix's and VMS
  1463.      */
  1464.     static boolean canUseCLPrompt() {
  1465. String os = System.getProperty("os.name");
  1466. return (os.indexOf("Linux") >= 0    ||  os.indexOf("SunOS") >= 0  ||
  1467. os.indexOf("Solaris") >= 0  ||  os.indexOf("BSD") >= 0    ||
  1468. os.indexOf("AIX") >= 0      ||  os.indexOf("HP-UX") >= 0  ||
  1469. os.indexOf("IRIX") >= 0     ||  os.indexOf("OSF") >= 0    ||
  1470. os.indexOf("A/UX") >= 0     ||  os.indexOf("VMS") >= 0);
  1471.     }
  1472. }