/**
   * Check the response and answer true if authentication succeeds. We are making simplifying
   * assumptions here and assuming that the password is available to us for computation of the MD5
   * hash. We also dont cache authentications so that the user has to authenticate on each
   * registration.
   *
   * @param user is the username
   * @param authHeader is the Authroization header from the SIP request.
   * @param requestLine is the SIP Request line from the SIP request.
   * @exception SIPAuthenticationException is thrown when authentication fails or message is bad
   */
  public boolean doAuthenticate(String user, AuthorizationHeader authHeader, Request request) {
    String realm = authHeader.getRealm();
    String username = authHeader.getUsername();
    URI requestURI = request.getRequestURI();

    if (username == null) {
      ProxyDebug.println(
          "DEBUG, DigestAuthenticateMethod, doAuthenticate(): "
              + "WARNING: userName parameter not set in the header received!!!");
      username = user;
    }
    if (realm == null) {
      ProxyDebug.println(
          "DEBUG, DigestAuthenticateMethod, doAuthenticate(): "
              + "WARNING: realm parameter not set in the header received!!! WE use the default one");
      realm = DEFAULT_REALM;
    }

    ProxyDebug.println(
        "DEBUG, DigestAuthenticateMethod, doAuthenticate(): "
            + "Trying to authenticate user: "******" for "
            + " the realm: "
            + realm);

    String password = (String) passwordTable.get(username + "@" + realm);
    if (password == null) {
      ProxyDebug.println(
          "DEBUG, DigestAuthenticateMethod, doAuthenticate(): "
              + "ERROR: password not found for the user: "******"@"
              + realm);
      return false;
    }

    String nonce = authHeader.getNonce();
    // If there is a URI parameter in the Authorization header,
    // then use it.
    URI uri = authHeader.getURI();
    // There must be a URI parameter in the authorization header.
    if (uri == null) {
      ProxyDebug.println(
          "DEBUG, DigestAuthenticateMethod, doAuthenticate(): "
              + "ERROR: uri paramater not set in the header received!");
      return false;
    }

    ProxyDebug.println(
        "DEBUG, DigestAuthenticationMethod, doAuthenticate(), username:"******"!");
    ProxyDebug.println("DEBUG, DigestAuthenticationMethod, doAuthenticate(), realm:" + realm + "!");
    ProxyDebug.println(
        "DEBUG, DigestAuthenticationMethod, doAuthenticate(), password:"******"!");
    ProxyDebug.println("DEBUG, DigestAuthenticationMethod, doAuthenticate(), uri:" + uri + "!");
    ProxyDebug.println("DEBUG, DigestAuthenticationMethod, doAuthenticate(), nonce:" + nonce + "!");
    ProxyDebug.println(
        "DEBUG, DigestAuthenticationMethod, doAuthenticate(), method:" + request.getMethod() + "!");

    String A1 = username + ":" + realm + ":" + password;
    String A2 = request.getMethod().toUpperCase() + ":" + uri.toString();
    byte mdbytes[] = messageDigest.digest(A1.getBytes());
    String HA1 = ProxyUtilities.toHexString(mdbytes);

    ProxyDebug.println("DEBUG, DigestAuthenticationMethod, doAuthenticate(), HA1:" + HA1 + "!");
    mdbytes = messageDigest.digest(A2.getBytes());
    String HA2 = ProxyUtilities.toHexString(mdbytes);
    ProxyDebug.println("DEBUG, DigestAuthenticationMethod, doAuthenticate(), HA2:" + HA2 + "!");
    String cnonce = authHeader.getCNonce();
    String KD = HA1 + ":" + nonce;
    if (cnonce != null) {
      KD += ":" + cnonce;
    }
    KD += ":" + HA2;
    mdbytes = messageDigest.digest(KD.getBytes());
    String mdString = ProxyUtilities.toHexString(mdbytes);
    String response = authHeader.getResponse();
    ProxyDebug.println(
        "DEBUG, DigestAuthenticateMethod, doAuthenticate(): "
            + "we have to compare his response: "
            + response
            + " with our computed"
            + " response: "
            + mdString);

    int res = (mdString.compareTo(response));
    if (res == 0) {
      ProxyDebug.println(
          "DEBUG, DigestAuthenticateMethod, doAuthenticate(): " + "User authenticated...");
    } else {
      ProxyDebug.println(
          "DEBUG, DigestAuthenticateMethod, doAuthenticate(): " + "User not authenticated...");
    }

    return res == 0;
  }
  /**
   * Generates an authorisation header in response to wwwAuthHeader.
   *
   * @param method method of the request being authenticated
   * @param uri digest-uri
   * @param requestBody the body of the request.
   * @param authHeader the challenge that we should respond to
   * @param userCredentials username and pass
   * @return an authorisation header in response to authHeader.
   * @throws OperationFailedException if auth header was malformated.
   */
  private AuthorizationHeader getAuthorization(
      String method,
      String uri,
      String requestBody,
      WWWAuthenticateHeader authHeader,
      UserCredentialHash userCredentials) {
    String response = null;

    // JvB: authHeader.getQop() is a quoted _list_ of qop values
    // (e.g. "auth,auth-int") Client is supposed to pick one
    String qopList = authHeader.getQop();
    String qop = (qopList != null) ? "auth" : null;
    String nc_value = "00000001";
    String cnonce = "xyz";

    response =
        MessageDigestAlgorithm.calculateResponse(
            authHeader.getAlgorithm(),
            userCredentials.getHashUserDomainPassword(),
            authHeader.getNonce(),
            nc_value, // JvB added
            cnonce, // JvB added
            method,
            uri,
            requestBody,
            qop,
            sipStack.getStackLogger()); // jvb changed

    AuthorizationHeader authorization = null;
    try {
      if (authHeader instanceof ProxyAuthenticateHeader) {
        authorization = headerFactory.createProxyAuthorizationHeader(authHeader.getScheme());
      } else {
        authorization = headerFactory.createAuthorizationHeader(authHeader.getScheme());
      }

      authorization.setUsername(userCredentials.getUserName());
      authorization.setRealm(authHeader.getRealm());
      authorization.setNonce(authHeader.getNonce());
      authorization.setParameter("uri", uri);
      authorization.setResponse(response);
      if (authHeader.getAlgorithm() != null) {
        authorization.setAlgorithm(authHeader.getAlgorithm());
      }

      if (authHeader.getOpaque() != null) {
        authorization.setOpaque(authHeader.getOpaque());
      }

      // jvb added
      if (qop != null) {
        authorization.setQop(qop);
        authorization.setCNonce(cnonce);
        authorization.setNonceCount(Integer.parseInt(nc_value));
      }

      authorization.setResponse(response);

    } catch (ParseException ex) {
      throw new RuntimeException("Failed to create an authorization header!");
    }

    return authorization;
  }
  /**
   * Generates an authorisation header in response to wwwAuthHeader.
   *
   * @param method method of the request being authenticated
   * @param uri digest-uri
   * @param requestBody the body of the request.
   * @param authHeader the challenge that we should respond to
   * @param username
   * @param password
   * @return an authorisation header in response to authHeader.
   * @throws OperationFailedException if auth header was malformated.
   */
  public static AuthorizationHeader getAuthorizationHeader(
      String method,
      String uri,
      String requestBody,
      WWWAuthenticateHeader authHeader,
      String username,
      String password) {
    String response = null;
    HeaderFactory headerFactory = SipFactories.headerFactory;

    // JvB: authHeader.getQop() is a quoted _list_ of qop values
    // (e.g. "auth,auth-int") Client is supposed to pick one
    String qopList = authHeader.getQop();
    String qop = (qopList != null) ? "auth" : null;
    String nc_value = "00000001";
    String cnonce = "xyz";

    try {
      response =
          MessageDigestResponseAlgorithm.calculateResponse(
              authHeader.getAlgorithm(),
              username,
              authHeader.getRealm(),
              password,
              authHeader.getNonce(),
              nc_value, // JvB added
              cnonce, // JvB added
              method,
              uri,
              requestBody,
              qop); // jvb changed
    } catch (NullPointerException exc) {
      throw new IllegalStateException("The authenticate header was malformatted", exc);
    }

    AuthorizationHeader authorization = null;
    try {
      if (authHeader instanceof ProxyAuthenticateHeader) {
        authorization = headerFactory.createProxyAuthorizationHeader(authHeader.getScheme());
      } else {
        authorization = headerFactory.createAuthorizationHeader(authHeader.getScheme());
      }

      authorization.setUsername(username);
      authorization.setRealm(authHeader.getRealm());
      authorization.setNonce(authHeader.getNonce());
      authorization.setParameter("uri", uri);
      authorization.setResponse(response);
      if (authHeader.getAlgorithm() != null) {
        authorization.setAlgorithm(authHeader.getAlgorithm());
      }

      if (authHeader.getOpaque() != null && authHeader.getOpaque().length() > 0) {
        authorization.setOpaque(authHeader.getOpaque());
      }

      // jvb added
      if (qop != null) {
        authorization.setQop(qop);
        authorization.setCNonce(cnonce);
        authorization.setNonceCount(Integer.parseInt(nc_value));
      }

      authorization.setResponse(response);

    } catch (ParseException ex) {
      throw new SecurityException("Failed to create an authorization header!");
    }

    return authorization;
  }
  /*
   * (non-Javadoc)
   *
   * @see gov.nist.javax.sip.clientauthutils.AuthenticationHelper#handleChallenge(javax.sip.message.Response,
   *      javax.sip.ClientTransaction, javax.sip.SipProvider)
   */
  public ClientTransaction handleChallenge(
      Response challenge,
      ClientTransaction challengedTransaction,
      SipProvider transactionCreator,
      int cacheTime)
      throws SipException, NullPointerException {
    try {
      if (sipStack.isLoggingEnabled()) {
        sipStack.getStackLogger().logDebug("handleChallenge: " + challenge);
      }

      SIPRequest challengedRequest = ((SIPRequest) challengedTransaction.getRequest());

      Request reoriginatedRequest = null;
      /*
       * If the challenged request is part of a Dialog and the
       * Dialog is confirmed the re-originated request should be
       * generated as an in-Dialog request.
       */
      if (challengedRequest.getToTag() != null
          || challengedTransaction.getDialog() == null
          || challengedTransaction.getDialog().getState() != DialogState.CONFIRMED) {
        reoriginatedRequest = (Request) challengedRequest.clone();
      } else {
        /*
         * Re-originate the request by consulting the dialog. In particular
         * the route set could change between the original request and the
         * in-dialog challenge.
         */
        reoriginatedRequest =
            challengedTransaction.getDialog().createRequest(challengedRequest.getMethod());
        Iterator<String> headerNames = challengedRequest.getHeaderNames();
        while (headerNames.hasNext()) {
          String headerName = headerNames.next();
          if (reoriginatedRequest.getHeader(headerName) != null) {
            ListIterator<Header> iterator = reoriginatedRequest.getHeaders(headerName);
            while (iterator.hasNext()) {
              reoriginatedRequest.addHeader(iterator.next());
            }
          }
        }
      }

      // remove the branch id so that we could use the request in a new
      // transaction
      removeBranchID(reoriginatedRequest);

      if (challenge == null || reoriginatedRequest == null) {
        throw new NullPointerException("A null argument was passed to handle challenge.");
      }

      ListIterator authHeaders = null;

      if (challenge.getStatusCode() == Response.UNAUTHORIZED) {
        authHeaders = challenge.getHeaders(WWWAuthenticateHeader.NAME);
      } else if (challenge.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED) {
        authHeaders = challenge.getHeaders(ProxyAuthenticateHeader.NAME);
      } else {
        throw new IllegalArgumentException("Unexpected status code ");
      }

      if (authHeaders == null) {
        throw new IllegalArgumentException(
            "Could not find WWWAuthenticate or ProxyAuthenticate headers");
      }

      // Remove all authorization headers from the request (we'll re-add them
      // from cache)
      reoriginatedRequest.removeHeader(AuthorizationHeader.NAME);
      reoriginatedRequest.removeHeader(ProxyAuthorizationHeader.NAME);

      // rfc 3261 says that the cseq header should be augmented for the new
      // request. do it here so that the new dialog (created together with
      // the new client transaction) takes it into account.
      // Bug report - Fredrik Wickstrom
      CSeqHeader cSeq = (CSeqHeader) reoriginatedRequest.getHeader((CSeqHeader.NAME));
      try {
        cSeq.setSeqNumber(cSeq.getSeqNumber() + 1l);
      } catch (InvalidArgumentException ex) {
        throw new SipException("Invalid CSeq -- could not increment : " + cSeq.getSeqNumber());
      }

      /* Resolve this to the next hop based on the previous lookup. If we are not using
       * lose routing (RFC2543) then just attach hop as a maddr param.
       */
      if (challengedRequest.getRouteHeaders() == null) {
        Hop hop = ((SIPClientTransaction) challengedTransaction).getNextHop();
        SipURI sipUri = (SipURI) reoriginatedRequest.getRequestURI();
        sipUri.setMAddrParam(hop.getHost());
        if (hop.getPort() != -1) sipUri.setPort(hop.getPort());
      }
      ClientTransaction retryTran = transactionCreator.getNewClientTransaction(reoriginatedRequest);

      WWWAuthenticateHeader authHeader = null;
      SipURI requestUri = (SipURI) challengedTransaction.getRequest().getRequestURI();
      while (authHeaders.hasNext()) {
        authHeader = (WWWAuthenticateHeader) authHeaders.next();
        String realm = authHeader.getRealm();
        AuthorizationHeader authorization = null;
        String sipDomain;
        if (this.accountManager instanceof SecureAccountManager) {
          UserCredentialHash credHash =
              ((SecureAccountManager) this.accountManager)
                  .getCredentialHash(challengedTransaction, realm);
          URI uri = reoriginatedRequest.getRequestURI();
          sipDomain = credHash.getSipDomain();
          authorization =
              this.getAuthorization(
                  reoriginatedRequest.getMethod(),
                  uri.toString(),
                  (reoriginatedRequest.getContent() == null)
                      ? ""
                      : new String(reoriginatedRequest.getRawContent()),
                  authHeader,
                  credHash);
        } else {
          UserCredentials userCreds =
              ((AccountManager) this.accountManager).getCredentials(challengedTransaction, realm);
          sipDomain = userCreds.getSipDomain();
          if (userCreds == null)
            throw new SipException("Cannot find user creds for the given user name and realm");

          // we haven't yet authenticated this realm since we were
          // started.

          authorization =
              this.getAuthorization(
                  reoriginatedRequest.getMethod(),
                  reoriginatedRequest.getRequestURI().toString(),
                  (reoriginatedRequest.getContent() == null)
                      ? ""
                      : new String(reoriginatedRequest.getRawContent()),
                  authHeader,
                  userCreds);
        }
        sipStack
            .getStackLogger()
            .logDebug("Created authorization header: " + authorization.toString());

        if (cacheTime != 0)
          cachedCredentials.cacheAuthorizationHeader(sipDomain, authorization, cacheTime);

        reoriginatedRequest.addHeader(authorization);
      }

      if (sipStack.isLoggingEnabled()) {
        sipStack.getStackLogger().logDebug("Returning authorization transaction." + retryTran);
      }
      return retryTran;
    } catch (SipException ex) {
      throw ex;
    } catch (Exception ex) {
      sipStack.getStackLogger().logError("Unexpected exception ", ex);
      throw new SipException("Unexpected exception ", ex);
    }
  }