content: package gov2.nist.javax2.sip.clientauthutils;
  
  /*
   *
   * This code has been contributed with permission from:
   *
   * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client but has been significantly changed.
   * It is donated to the JAIN-SIP project as it is common code that many sip clients
   * need to perform class and others will consitute a set of utility functions
   * that will implement common operations that ease the life of the developer.
   *
   * Acknowledgements:
   * ----------------
   *
   * Fredrik Wickstrom reported that dialog cseq counters are not incremented
   * when resending requests. He later uncovered additional problems and
   * proposed a way to fix them (his proposition was taken into account).
   */
  
  import gov2.nist.javax2.sip.SipStackImpl;
  import gov2.nist.javax2.sip.address.SipUri;
  import gov2.nist.javax2.sip.message.SIPRequest;
  import gov2.nist.javax2.sip.stack.SIPClientTransaction;
  import gov2.nist.javax2.sip.stack.SIPTransactionStack;
  
  import java.text.ParseException;
  import java.util.Collection;
  import java.util.Iterator;
  import java.util.ListIterator;
  import java.util.Timer;
  
  import javax2.sip.ClientTransaction;
  import javax2.sip.DialogState;
  import javax2.sip.InvalidArgumentException;
  import javax2.sip.SipException;
  import javax2.sip.SipProvider;
  import javax2.sip.address.Hop;
  import javax2.sip.address.SipURI;
  import javax2.sip.address.URI;
  import javax2.sip.header.AuthorizationHeader;
  import javax2.sip.header.CSeqHeader;
  import javax2.sip.header.Header;
  import javax2.sip.header.HeaderFactory;
  import javax2.sip.header.ProxyAuthenticateHeader;
  import javax2.sip.header.ProxyAuthorizationHeader;
  import javax2.sip.header.ViaHeader;
  import javax2.sip.header.WWWAuthenticateHeader;
  import javax2.sip.message.Request;
  import javax2.sip.message.Response;
  
  /**
   * The class handles authentication challenges, caches user credentials and takes care (through
   * the SecurityAuthority interface) about retrieving passwords.
   *
   *
   * @author Emil Ivov
   * @author Jeroen van Bemmel
   * @author M. Ranganathan
   *
   * @since 2.0
   */
  
  public class AuthenticationHelperImpl implements AuthenticationHelper {
  
      /**
       * Credentials cached so far.
       */
      private CredentialsCache cachedCredentials;
  
      /**
       * The account manager for the system. Stores user credentials.
       */
      private Object accountManager = null;
  
      /*
       * Header factory for this security manager.
       */
      private HeaderFactory headerFactory;
  
      private SipStackImpl sipStack;
  
      Timer timer;
  
      /**
       * Default constructor for the security manager. There is one Account manager. There is one
       * SipSecurity manager for every user name,
       *
       * @param sipStack -- our stack.
       * @param accountManager -- an implementation of the AccountManager interface.
       * @param headerFactory -- header factory.
       */
      public AuthenticationHelperImpl(SipStackImpl sipStack, AccountManager accountManager,
              HeaderFactory headerFactory) {
          this.accountManager = accountManager;
          this.headerFactory = headerFactory;
          this.sipStack = sipStack;
  
          this.cachedCredentials = new CredentialsCache(((SIPTransactionStack) sipStack).getTimer());
      }
      
      /**
       * Default constructor for the security manager. There is one Account manager. There is one
       * SipSecurity manager for every user name,
       *
       * @param sipStack -- our stack.
       * @param accountManager -- an implementation of the AccountManager interface.
       * @param headerFactory -- header factory.
       */
      public AuthenticationHelperImpl(SipStackImpl sipStack, SecureAccountManager accountManager,
              HeaderFactory headerFactory) {
          this.accountManager = accountManager;
          this.headerFactory = headerFactory;
          this.sipStack = sipStack;
  
          this.cachedCredentials = new CredentialsCache(((SIPTransactionStack) sipStack).getTimer());
      }
      
  
      /*
       * (non-Javadoc)
       *
       * @see gov2.nist.javax2.sip.clientauthutils.AuthenticationHelper#handleChallenge(javax2.sip.message.Response,
       *      javax2.sip.ClientTransaction, javax2.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);
                  }
                  if (sipStack.isLoggingEnabled())
                  	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) {
              if (sipStack.isLoggingEnabled()) {
              	sipStack.getStackLogger().logError("Unexpected exception ", ex);
              }
              throw new SipException("Unexpected exception ", ex);
          }
      }
      
      
     
  
      /**
       * 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, UserCredentials 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.getUserName(), authHeader.getRealm(), userCredentials
                          .getPassword(), 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 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;
      }
      /**
       * Removes all via headers from <tt>request</tt> and replaces them with a new one, equal to
       * the one that was top most.
       *
       * @param request the Request whose branchID we'd like to remove.
       *
       */
      private void removeBranchID(Request request) {
  
          ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
  
          viaHeader.removeParameter("branch");
  
      }
  
      /*
       * (non-Javadoc)
       *
       * @see gov2.nist.javax2.sip.clientauthutils.AuthenticationHelper#attachAuthenticationHeaders(javax2.sip.message.Request)
       */
      public void setAuthenticationHeaders(Request request) {
          SIPRequest sipRequest = (SIPRequest) request;
  
          String callId = sipRequest.getCallId().getCallId();
  
          request.removeHeader(AuthorizationHeader.NAME);
          Collection<AuthorizationHeader> authHeaders = this.cachedCredentials
                  .getCachedAuthorizationHeaders(callId);
          if (authHeaders == null) {
          	if (sipStack.isLoggingEnabled())
          		sipStack.getStackLogger().logDebug(
                      "Could not find authentication headers for " + callId);
              return;
          }
  
          for (AuthorizationHeader authHeader : authHeaders) {
              request.addHeader(authHeader);
          }
  
      }
  
      /*
       * (non-Javadoc)
       *
       * @see gov2.nist.javax2.sip.clientauthutils.AuthenticationHelper#removeCachedAuthenticationHeaders(java.lang.String)
       */
      public void removeCachedAuthenticationHeaders(String callId) {
          if (callId == null)
              throw new NullPointerException("Null callId argument ");
          this.cachedCredentials.removeAuthenticationHeader(callId);
  
      }
  
  }
  