/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2005 Nokia Corporation.
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

/**@CFILE auth_client.c  Authenticators for SIP client
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 *
 * @date Created: Wed Feb 14 18:32:58 2001 ppessi
 */

#include "config.h"

#define SOFIA_EXTEND_AUTH_CLIENT 1

#include <sofia-sip/su.h>
#include <sofia-sip/su_md5.h>

#include "sofia-sip/auth_common.h"
#include "sofia-sip/auth_client.h"
#include "sofia-sip/auth_client_plugin.h"

#include <sofia-sip/msg_types.h>
#include <sofia-sip/msg_header.h>

#include <sofia-sip/auth_digest.h>

#include <sofia-sip/base64.h>
#include <sofia-sip/su_uniqueid.h>
#include <sofia-sip/string0.h>

#include <sofia-sip/su_debug.h>

#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#include <assert.h>

static auth_client_t *ca_create(su_home_t *home, 
				char const *scheme,
				char const *realm);

static void ca_destroy(su_home_t *home, auth_client_t *ca);

static int ca_challenge(auth_client_t *ca,
			msg_auth_t const *auth, 
			msg_hclass_t *credential_class,
			char const *scheme,
			char const *realm);

static int ca_info(auth_client_t *ca,
		   msg_auth_info_t const *ai,
		   msg_hclass_t *credential_class);

static int ca_credentials(auth_client_t *ca, 
			  char const *scheme,
			  char const *realm, 
			  char const *user,
			  char const *pass);

static int ca_clear_credentials(auth_client_t *ca);


/** Initialize authenticators.
 *
 * The function auc_challenge() merges the challenge @a ch to the list of
 * authenticators @a auc_list.  
 *
 * @param[in,out] auc_list  list of authenticators to be updated
 * @param[in,out] home      memory home used for allocating authenticators
 * @param[in] ch        challenge to be processed
 * @param[in] crcl      credential class
 * 
 * @retval 1 when challenge was updated
 * @retval 0 when there was no new challenges
 * @retval -1 upon an error
 */
int auc_challenge(auth_client_t **auc_list,
		  su_home_t *home, 
		  msg_auth_t const *ch,
		  msg_hclass_t *crcl)
{
  auth_client_t **cca;
  int retval = 0;

  /* Go through each challenge in Authenticate or Proxy-Authenticate headers */
  for (; ch; ch = ch->au_next) {
    char const *scheme = ch->au_scheme;
    char const *realm = msg_header_find_param(ch->au_common, "realm=");
    int matched = 0, updated;

    if (!scheme || !realm)
      continue;

    /* Update matching authenticator */
    for (cca = auc_list; (*cca); cca = &(*cca)->ca_next) {
      updated = ca_challenge((*cca), ch, crcl, scheme, realm);
      if (updated < 0)
	return -1;
      if (updated == 0)
	continue;		/* No match, next */
      matched = 1;
      if (updated > 1)
	retval = 1;		/* Updated authenticator */
    }

    if (!matched) {
      /* There was no matching authenticator, create a new one */
      *cca = ca_create(home, scheme, realm);
      if (ca_challenge((*cca), ch, crcl, scheme, realm) < 0) {
	ca_destroy(home, *cca), *cca = NULL;
	return -1;
      }
      retval = 1;		/* Updated authenticator */
    }
  }

  return retval;
}

/** Update authentication client. 
 *
 * @retval -1 upon an error
 * @retval 0 when challenge did not match
 * @retval 1 when challenge did match but was not updated
 * @retval 2 when challenge did match and updated client
 */
static
int ca_challenge(auth_client_t *ca, 
		 msg_auth_t const *ch,
		 msg_hclass_t *credential_class,
		 char const *scheme, 
		 char const *realm)
{
  int stale = 0;

  assert(ca); assert(ch);

  if (!ca || !ch)
    return -1;

  if (strcasecmp(ca->ca_scheme, scheme))
    return 0;
  if (strcmp(ca->ca_realm, realm))
    return 0;

  if (ca->ca_credential_class && 
      ca->ca_credential_class != credential_class)
    return 0;

  if (!ca->ca_auc)
    return 1;

  if (ca->ca_auc->auc_challenge)
    stale = ca->ca_auc->auc_challenge(ca, ch);
  if (stale < 0)
    return -1;

  if (!ca->ca_credential_class)
    stale = 2, ca->ca_credential_class = credential_class;

  return stale > 1 ? 2 : 1;
}

/** Store authentication info to authenticators.
 *
 * The function auc_info() feeds the authentication data from the @b
 * Authentication-Info header @a info to the list of authenticators @a
 * auc_list.
 *
 * @param[in,out] auc_list  list of authenticators to be updated
 * @param[in] info        info header to be processed
 * @param[in] credential_class      corresponding credential class
 *
 * The authentication info can be in either @AuthenticationInfo or in
 * @ProxyAuthenticationInfo headers. If the header is @AuthenticationInfo,
 * the @a credential_class should be #sip_authorization_class or
 * #http_authorization_class. Likewise, If the header is
 * @ProxyAuthenticationInfo, the @a credential_class should be
 * #sip_proxy_authorization_class or #http_proxy_authorization_class.

 * The authentication into header usually contains next nonce or mutual
 * authentication information. Currently, only the nextnonce parameter is
 * processed.
 *
 * @bug
 * In principle, SIP allows more than one challenge for a single request. 
 * For example, there can be multiple proxies that each challenge the
 * client. The result of storing authentication info can be quite unexpected
 * if there are more than one authenticator with the given type (specified
 * by @a credential_class).
 *
 * @retval number of challenges to updated
 * @retval 0 when there was no challenge to update
 * @retval -1 upon an error
 *
 * @NEW_1_12_5.
 */
int auc_info(auth_client_t **auc_list,
	     msg_auth_info_t const *info,
	     msg_hclass_t *credential_class)
{
  auth_client_t *ca;
  int retval = 0;

  /* Go through each challenge in Authenticate or Proxy-Authenticate headers */

  /* Update matching authenticator */
  for (ca = *auc_list; ca; ca = ca->ca_next) {
    int updated = ca_info(ca, info, credential_class);
    if (updated < 0)
      return -1;
    if (updated >= 1)
      retval = 1;		/* Updated authenticator */
  }

  return retval;
}

/** Update authentication client with authentication info. 
 *
 * @retval -1 upon an error
 * @retval 0 when challenge did not match
 * @retval 1 when challenge did match but was not updated
 * @retval 2 when challenge did match and updated client
 */
static
int ca_info(auth_client_t *ca, 
	    msg_auth_info_t const *info,
	    msg_hclass_t *credential_class)
{
  assert(ca); assert(info);

  if (!ca || !info)
    return -1;

  if (!ca->ca_credential_class)
    return 0;

  if (ca->ca_credential_class != credential_class)
    return 0;

  if (!ca->ca_auc
      || (size_t)ca->ca_auc->auc_plugin_size <= 
         offsetof(auth_client_plugin_t, auc_info)
      || !ca->ca_auc->auc_info)
    return 0;

  return ca->ca_auc->auc_info(ca, info);
}


/**Feed authentication data to the authenticator.
 *
 * The function auc_credentials() is used to provide the authenticators in
 * with authentication data (user name, secret).  The authentication data
 * has format as follows:
 *
 * scheme:"realm":user:pass
 *
 * For instance, @c Basic:"nokia-proxy":ppessi:verysecret
 *
 * @todo The authentication data format sucks.
 *
 * @param[in,out] auc_list  list of authenticators 
 * @param[in,out] home      memory home used for allocations
 * @param[in] data          colon-separated authentication data
 * 
 * @retval 0 when successful
 * @retval -1 upon an error
 */
int auc_credentials(auth_client_t **auc_list, su_home_t *home, 
		    char const *data)
{
  int retval = 0, match;
  char *s0, *s;
  char *scheme = NULL, *user = NULL, *pass = NULL, *realm = NULL;

  s0 = s = su_strdup(NULL, data);

  /* Parse authentication data */
  /* Data is string like "Basic:\"agni\":user1:secret" */
  if (s && (s = strchr(scheme = s, ':')))
    *s++ = 0;
  if (s && (s = strchr(realm = s, ':')))
    *s++ = 0;
  if (s && (s = strchr(user = s, ':')))
    *s++ = 0;
  if (s && (s = strchr(pass = s, ':')))
    *s++ = 0;

  if (scheme && realm && user && pass) {
    for (; *auc_list; auc_list = &(*auc_list)->ca_next) {
      match = ca_credentials(*auc_list, scheme, realm, user, pass);
      if (match < 0) {
	retval = -1;
	break;
      }
      if (match) 
	retval++;
    }
  }

  su_free(NULL, s0);

  return retval;
}

/**Feed authentication data to the authenticator.
 *
 * The function auc_credentials() is used to provide the authenticators in
 * with authentication tuple (scheme, realm, user name, secret).  
 *
 * scheme:"realm":user:pass
 *
 * @todo The authentication data format sucks.
 *
 * @param[in,out] auc_list  list of authenticators 
 * @param[in] scheme        scheme to use (NULL, if any)
 * @param[in] realm         realm to use (NULL, if any)
 * @param[in] user          username 
 * @param[in] pass          password
 * 
 * @retval number of updated clients
 * @retval 0 when no client was updated
 * @retval -1 upon an error
 */
int auc_all_credentials(auth_client_t **auc_list, 
			char const *scheme,
			char const *realm, 
			char const *user,
			char const *pass)
{
  int retval = 0, match;

#if HAVE_SC_CRED_H
  /* XXX: add */
#endif

  if (user && pass) {
    for (; *auc_list; auc_list = &(*auc_list)->ca_next) {
      match = ca_credentials(*auc_list, scheme, realm, user, pass);
      if (match < 0)
	return -1;
      if (match) 
	retval++;
    }
  }

  return retval;
}

int ca_credentials(auth_client_t *ca, 
		   char const *scheme,
		   char const *realm, 
		   char const *user,
		   char const *pass)
{
  char *new_user, *new_pass;
  char *old_user, *old_pass;

  assert(ca);

  if (!ca || !ca->ca_scheme || !ca->ca_realm)
    return -1;

  if ((scheme != NULL && strcasecmp(scheme, ca->ca_scheme)) ||
      (realm != NULL && strcmp(realm, ca->ca_realm)))
    return 0;

  old_user = ca->ca_user, old_pass = ca->ca_pass;

  if (str0cmp(user, old_user) == 0 && str0cmp(pass, old_pass) == 0)
    return 0;

  new_user = su_strdup(ca->ca_home, user);
  new_pass = su_strdup(ca->ca_home, pass);

  if (!new_user || !new_pass)
    return -1;

  ca->ca_user = new_user, ca->ca_pass = new_pass;
  if (AUTH_CLIENT_IS_EXTENDED(ca))
    ca->ca_clear = 0;

  su_free(ca->ca_home, old_user);
  su_free(ca->ca_home, old_pass);

  return 1;
}


/** Copy authentication data from @a src to @a dst.
 *
 * @retval >0 if credentials were copied
 * @retval 0 if there was no credentials to copy
 * @retval <0 if an error occurred.
 */
int auc_copy_credentials(auth_client_t **dst,
			 auth_client_t const *src)
{
  int retval = 0;

  if (!dst)
    return -1;

  for (;*dst; dst = &(*dst)->ca_next) {
    auth_client_t *d = *dst;
    auth_client_t const *ca;

    for (ca = src; ca; ca = ca->ca_next) {
      char *u, *p;
      if (!ca->ca_user || !ca->ca_pass)
	continue;
      if (AUTH_CLIENT_IS_EXTENDED(ca) && ca->ca_clear)
	continue;
      if (!ca->ca_scheme[0] || strcasecmp(ca->ca_scheme, d->ca_scheme))
	continue;
      if (!ca->ca_realm[0] || strcmp(ca->ca_realm, d->ca_realm))
	continue;

      if (!(AUTH_CLIENT_IS_EXTENDED(d) && d->ca_clear) &&
	  d->ca_user && strcmp(d->ca_user, ca->ca_user) == 0 &&
	  d->ca_pass && strcmp(d->ca_pass, ca->ca_pass) == 0) {
	retval++;
	break;
      }

      u = su_strdup(d->ca_home, ca->ca_user);
      p = su_strdup(d->ca_home, ca->ca_pass);
      if (!u || !p)
	return -1;

      if (d->ca_user) su_free(d->ca_home, (void *)d->ca_user);
      if (d->ca_pass) su_free(d->ca_home, (void *)d->ca_pass);
      d->ca_user = u, d->ca_pass = p;
      if (AUTH_CLIENT_IS_EXTENDED(d))
	d->ca_clear = 0;

      retval++;
      break;
    }
  }

  return retval;
}
      	      

/**Clear authentication data from the authenticator.
 *
 * The function auc_clear_credentials() is used to remove the credentials
 * from the authenticators.
 *
 * @param[in,out] auc_list  list of authenticators 
 * @param[in] scheme    scheme (if non-null, remove only matching credentials) 
 * @param[in] realm     realm (if non-null, remove only matching credentials)
 *
 * @retval 0 when successful
 * @retval -1 upon an error
 */
int auc_clear_credentials(auth_client_t **auc_list, 
			  char const *scheme,
			  char const *realm)
{
  int retval = 0;
  int match;

  for (; *auc_list; auc_list = &(*auc_list)->ca_next) {
    auth_client_t *ca = *auc_list;

    if (!AUTH_CLIENT_IS_EXTENDED(ca))
      continue;

    if ((scheme != NULL && strcasecmp(scheme, ca->ca_scheme)) ||
	(realm != NULL && strcmp(realm, ca->ca_realm)))
      continue;

    match = ca->ca_auc->auc_clear(*auc_list);

    if (match < 0) {
      retval = -1;
      break;
    }
    if (match) 
      retval++;
  }

  return retval;
}

static
int ca_clear_credentials(auth_client_t *ca)
{
  assert(ca); assert(ca->ca_home->suh_size >= (int)(sizeof *ca));

  if (!ca)
    return -1;

  ca->ca_clear = 1;

  return 1;
}

/** Check if we have all required credentials.
 * 
 * @retval 1 when authorization can proceed
 * @retval 0 when there is not enough credentials
 *
 * @NEW_1_12_5.
 */
int auc_has_authorization(auth_client_t **auc_list)
{
  auth_client_t const *ca;

  if (auc_list == NULL)
    return 0;

  /* Make sure every challenge has credentials */
  for (ca = *auc_list; ca; ca = ca->ca_next) {
    if (!ca->ca_user || !ca->ca_pass || !ca->ca_credential_class)
      return 0;
    if (AUTH_CLIENT_IS_EXTENDED(ca) && ca->ca_clear)
      return 0;
  }

  return 1;
}

/**Authorize a request.
 *
 * The function auc_authorization() is used to add correct authentication
 * headers to a request. The authentication headers will contain the
 * credentials generated by the list of authenticators.
 *
 * @param[in,out] auc_list  list of authenticators 
 * @param[out] msg          message to be authenticated
 * @param[out] pub          headers of the message
 * @param[in] method        request method
 * @param[in] url           request URI
 * @param[in] body          message body (NULL if empty)
 * 
 * @retval 1 when successful
 * @retval 0 when there is not enough credentials
 * @retval -1 upon an error
 */
int auc_authorization(auth_client_t **auc_list, msg_t *msg, msg_pub_t *pub,
		      char const *method, 
		      url_t const *url, 
		      msg_payload_t const *body)
{
  auth_client_t *ca;
  msg_mclass_t const *mc = msg_mclass(msg);

  if (auc_list == NULL || msg == NULL)
    return -1;

  if (!auc_has_authorization(auc_list))
    return 0;

  if (pub == NULL)
    pub = msg_object(msg);

  /* Remove existing credentials */
  for (ca = *auc_list; ca; ca = ca->ca_next) {
    msg_header_t **hh = msg_hclass_offset(mc, pub, ca->ca_credential_class);

    while (hh && *hh)
      msg_header_remove(msg, pub, *hh);
  }

  /* Insert new credentials */
  for (; *auc_list; auc_list = &(*auc_list)->ca_next) {
    su_home_t *home = msg_home(msg);
    msg_header_t *h = NULL;

    ca = *auc_list;

    if (!ca->ca_auc)
      continue;

    if (ca->ca_auc->auc_authorize(ca, home, method, url, body, &h) < 0)
      return -1;
    if (h == NULL)
      continue;
    if (msg_header_insert(msg, pub, h) < 0)
      return -1;
  }

  return 1;
}

/**Generate headers authorizing a request.
 *
 * The function auc_authorization_headers() is used to generate
 * authentication headers for a request. The list of authentication headers
 * will contain the credentials generated by the list of authenticators.
 *
 * @param[in] auc_list      list of authenticators 
 * @param[in] home          memory home used to allocate headers
 * @param[in] method        request method
 * @param[in] url           request URI
 * @param[in] body          message body (NULL if empty)
 * @param[out] return_headers  authorization headers return value
 * 
 * @retval 1 when successful
 * @retval 0 when there is not enough credentials
 * @retval -1 upon an error
 */
int auc_authorization_headers(auth_client_t **auc_list, 
			      su_home_t *home,
			      char const *method, 
			      url_t const *url, 
			      msg_payload_t const *body,
			      msg_header_t **return_headers)
{
  auth_client_t *ca;

  /* Make sure every challenge has credentials */
  if (!auc_has_authorization(auc_list))
    return 0;

  /* Create new credential headers */
  for (; *auc_list; auc_list = &(*auc_list)->ca_next) {
    msg_header_t *h = NULL;

    ca = *auc_list;

    if (!ca->ca_auc)
      continue;

    if (ca->ca_auc->auc_authorize(ca, home, method, url, body, &h) < 0)
      return -1;

    *return_headers = h;

    while (*return_headers)
      return_headers = &(*return_headers)->sh_next;
  }

  return 1;
}

/* ---------------------------------------------------------------------- */
/* Basic scheme */

static int auc_basic_authorization(auth_client_t *ca,
				   su_home_t *h,
				   char const *method, 
				   url_t const *url, 
				   msg_payload_t const *body,
				   msg_header_t **);

static const auth_client_plugin_t ca_basic_plugin = 
{ 
  /* auc_plugin_size: */ sizeof ca_basic_plugin,
  /* auc_size: */        sizeof (auth_client_t),
  /* auc_name: */       "Basic",
  /* auc_challenge: */   NULL,
  /* auc_authorize: */   auc_basic_authorization,
  /* auc_info: */        NULL,
  /* auc_clear: */       ca_clear_credentials
};

/**Create a basic authorization header.
 *
 * The function auc_basic_authorization() creates a basic authorization
 * header from username @a user and password @a pass. The authorization
 * header type is determined by @a hc - it can be sip_authorization_class,
 * sip_proxy_authorization_class, http_authorization_class, or
 * http_proxy_authorization_class, for instance.
 *
 * @param home memory home used to allocate memory for the new header
 * @param hc   header class for the header to be created
 * @param user user name
 * @param pass password
 * 
 * @return
 * The function auc_basic_authorization() returns a pointer to newly created 
 * authorization header, or NULL upon an error.
 */
int auc_basic_authorization(auth_client_t *ca, 
			    su_home_t *home,
			    char const *method, 
			    url_t const *url, 
			    msg_payload_t const *body,
			    msg_header_t **return_headers)
{
  msg_hclass_t *hc = ca->ca_credential_class;
  char const *user = ca->ca_user;
  char const *pass = ca->ca_pass;
  size_t ulen, plen, uplen, b64len, basiclen;
  char *basic, *base64, *userpass;
  char buffer[71];

  if (user == NULL || pass == NULL)
    return -1;

  if (AUTH_CLIENT_IS_EXTENDED(ca) && ca->ca_clear)
    return 0;

  ulen = strlen(user), plen = strlen(pass), uplen = ulen + 1 + plen;
  b64len = BASE64_SIZE(uplen);
  basiclen = strlen("Basic ") + b64len;

  if (sizeof(buffer) > basiclen + 1)
    basic = buffer;
  else
    basic = malloc(basiclen + 1);

  /*
   * Basic authentication consists of username and password separated by
   * colon and then base64 encoded.
   */
  strcpy(basic, "Basic ");
  base64 = basic + strlen("Basic ");
  userpass = base64 + b64len - uplen;
  memcpy(userpass, user, ulen);
  userpass[ulen] = ':';
  memcpy(userpass + ulen + 1, pass, plen);
  userpass[uplen] = '\0';
  
  base64_e(base64, b64len + 1, userpass, uplen);

  base64[b64len] = '\0';

  *return_headers = msg_header_make(home, hc, basic);

  if (buffer != basic)
    free(basic);

  return *return_headers ? 0 : -1;
}

/* ---------------------------------------------------------------------- */
/* Digest scheme */

typedef struct auth_digest_client_s
{
  auth_client_t cda_client;

  int           cda_ncount;
  char const   *cda_cnonce;
  auth_challenge_t cda_ac[1];
} auth_digest_client_t;

static int auc_digest_challenge(auth_client_t *ca, 
				msg_auth_t const *ch);
static int auc_digest_authorization(auth_client_t *ca, 
				    su_home_t *h,
				    char const *method, 
				    url_t const *url, 
				    msg_payload_t const *body,
				    msg_header_t **);
static int auc_digest_info(auth_client_t *ca, 
			   msg_auth_info_t const *info);

static const auth_client_plugin_t ca_digest_plugin = 
{ 
  /* auc_plugin_size: */ sizeof ca_digest_plugin,
  /* auc_size: */        sizeof (auth_digest_client_t),
  /* auc_name: */       "Digest", 
  /* auc_challenge: */   auc_digest_challenge,
  /* auc_authorize: */   auc_digest_authorization,
  /* auc_info: */        auc_digest_info,
  /* auc_clear: */       ca_clear_credentials
};

/** Store a digest authorization challenge.
 *
 * @retval 2 if credentials need to be (re)sent
 * @retval 1 if challenge was updated
 * @retval -1 upon an error
 */
static int auc_digest_challenge(auth_client_t *ca, msg_auth_t const *ch)
{
  su_home_t *home = ca->ca_home;
  auth_digest_client_t *cda = (auth_digest_client_t *)ca;
  auth_challenge_t ac[1] = {{ sizeof ac }};
  int stale;

  if (auth_digest_challenge_get(home, ac, ch->au_params) < 0)
    goto error;

  /* Check that we can handle the challenge */
  if (!ac->ac_md5 && !ac->ac_md5sess)
    goto error;
  if (ac->ac_qop && !ac->ac_auth && !ac->ac_auth_int)
    goto error;

  stale = ac->ac_stale || cda->cda_ac->ac_nonce == NULL;

  if (ac->ac_qop && (cda->cda_cnonce == NULL || ac->ac_stale)) {
    su_guid_t guid[1];
    char *cnonce;
    size_t b64len = BASE64_MINSIZE(sizeof(guid)) + 1;
    if (cda->cda_cnonce != NULL)
      /* Free the old one if we are updating after stale=true */
      su_free(home, (void *)cda->cda_cnonce);
    su_guid_generate(guid);
    cda->cda_cnonce = cnonce = su_alloc(home, b64len);
    base64_e(cnonce, b64len, guid, sizeof(guid));
    cda->cda_ncount = 0;
  }

  auth_digest_challenge_free_params(home, cda->cda_ac);

  *cda->cda_ac = *ac;

  return stale ? 2 : 1;

 error:
  auth_digest_challenge_free_params(home, ac);
  return -1;
}

static int auc_digest_info(auth_client_t *ca,
			   msg_auth_info_t const *info)
{
  auth_digest_client_t *cda = (auth_digest_client_t *)ca;
  su_home_t *home = ca->ca_home;
  char const *nextnonce = NULL;
  issize_t n;

  n = auth_get_params(home, info->ai_params,
		      "nextnonce=", &nextnonce,
		      NULL);

  if (n <= 0)
    return n;

  cda->cda_ac->ac_nonce = nextnonce;

  return 1;
}

/**Create a digest authorization header.
 *
 * Creates a digest authorization header from username @a user and password
 * @a pass, client nonce @a cnonce, client nonce count @a nc, request method
 * @a method, request URI @a uri and message body @a data. The authorization
 * header type is determined by @a hc - it can be either
 * sip_authorization_class or sip_proxy_authorization_class, as well as
 * http_authorization_class or http_proxy_authorization_class.
 *
 * @retval 1 when authorization headers has been created  
 * @retval 0 when there is no credentials
 * @retval -1 upon an error
 */
static
int auc_digest_authorization(auth_client_t *ca, 
			     su_home_t *home,
			     char const *method, 
			     url_t const *url, 
			     msg_payload_t const *body,
			     msg_header_t **return_headers)
{
  auth_digest_client_t *cda = (auth_digest_client_t *)ca;
  msg_hclass_t *hc = ca->ca_credential_class;
  char const *user = ca->ca_user;
  char const *pass = ca->ca_pass;
  auth_challenge_t const *ac = cda->cda_ac;
  char const *cnonce = cda->cda_cnonce;
  unsigned nc = ++cda->cda_ncount;
  char *uri = url_as_string(home, url);
  void const *data = body ? body->pl_data : "";
  usize_t dlen = body ? body->pl_len : 0;

  msg_header_t *h;
  auth_hexmd5_t sessionkey, response;
  auth_response_t ar[1] = {{ 0 }};
  char ncount[17];

  if (!user || !pass || (AUTH_CLIENT_IS_EXTENDED(ca) && ca->ca_clear))
    return 0;

  ar->ar_size = sizeof(ar);
  ar->ar_username = user;
  ar->ar_realm = ac->ac_realm;
  ar->ar_nonce = ac->ac_nonce;
  ar->ar_algorithm = NULL;
  ar->ar_md5 = ac->ac_md5;
  ar->ar_md5sess = ac->ac_md5sess;
  ar->ar_opaque = ac->ac_opaque;
  ar->ar_qop = NULL;
  ar->ar_auth = ac->ac_auth;
  ar->ar_auth_int = ac->ac_auth_int;
  ar->ar_uri = uri;

  /* If there is no qop, we MUST NOT include cnonce or nc */
  if (!ar->ar_auth && !ar->ar_auth_int)
    cnonce = NULL;

  if (cnonce) {
    snprintf(ncount, sizeof(ncount), "%08x", nc);
    ar->ar_cnonce = cnonce;
    ar->ar_nc = ncount;
  }

  auth_digest_sessionkey(ar, sessionkey, pass);
  auth_digest_response(ar, response, sessionkey, method, data, dlen);

  h = msg_header_format(home, hc, 
			"Digest "
			"username=\"%s\", "
			"realm=\"%s\", "
			"nonce=\"%s"
			"%s%s"
			"%s%s"
			"%s%s, "
			"uri=\"%s\", "
			"response=\"%s\""
			"%s%s"
			"%s%s",
			ar->ar_username, 
			ar->ar_realm,
			ar->ar_nonce,
			cnonce ? "\",  cnonce=\"" : "", 
			cnonce ? cnonce : "",
			ar->ar_opaque ? "\",  opaque=\"" : "", 
			ar->ar_opaque ? ar->ar_opaque : "",
			ar->ar_algorithm ? "\", algorithm=" : "",
			ar->ar_algorithm ? ar->ar_algorithm : "",
			ar->ar_uri,
			response,
			ar->ar_auth || ar->ar_auth_int ? ", qop=" : "", 
			ar->ar_auth_int ? "auth-int" : 
			(ar->ar_auth ? "auth" : ""),
			cnonce ? ", nc=" : "", 
			cnonce ? ncount : "");

  su_free(home, uri);

  if (!h)
    return -1;
  *return_headers = h;
  return 0;
}


/* ---------------------------------------------------------------------- */

#define MAX_AUC 20

static auth_client_plugin_t const *ca_plugins[MAX_AUC] = 
{
  &ca_digest_plugin, &ca_basic_plugin, NULL
};

/** Register an authentication client plugin */
int auc_register_plugin(auth_client_plugin_t const *plugin)
{
  int i;

  if (plugin == NULL ||
      plugin->auc_name == NULL ||
      plugin->auc_authorize == NULL)
    return errno = EFAULT, -1;

  if (plugin->auc_size < (int) sizeof (auth_client_t))
    return errno = EINVAL, -1;

  for (i = 0; i < MAX_AUC; i++) {
    if (ca_plugins[i] == NULL || 
	strcmp(plugin->auc_name, ca_plugins[i]->auc_name) == 0) {
      ca_plugins[i] = plugin;
      return 0;
    }
  }

  return errno = ENOMEM, -1;
}

/** Allocate an (possibly extended) auth_client_t structure. */
static
auth_client_t *ca_create(su_home_t *home,
			 char const *scheme,
			 char const *realm)
{
  auth_client_plugin_t const *auc = NULL;
  auth_client_t *ca;
  size_t aucsize = (sizeof *ca), realmlen, size;
  char *s;
  int i;

  if (scheme == NULL || realm == NULL)
    return (void)(errno = EFAULT), NULL;

  realmlen = strlen(realm) + 1;

  for (i = 0; i < MAX_AUC; i++) {
    auc = ca_plugins[i];
    if (!auc || strcasecmp(auc->auc_name, scheme) == 0)
      break;
  }

  /* XXX - should report error if the auth scheme is not known? */

  aucsize = auc ? (size_t)auc->auc_size : (sizeof *ca);
  size = aucsize + realmlen;
  if (!auc)
    size += strlen(scheme) + 1;

  ca = su_home_clone(home, (isize_t)size);
  if (!ca)
    return ca;

  s = (char *)ca + aucsize;
  ca->ca_auc = auc;
  ca->ca_realm = strcpy(s, realm);
  ca->ca_scheme = auc ? auc->auc_name : strcpy(s + realmlen, scheme);

  return ca;
}

void ca_destroy(su_home_t *home, auth_client_t *ca)
{
  su_free(home, ca);
}


#if HAVE_SOFIA_SIP
#include <sofia-sip/sip.h>

/**Authorize a SIP request.
 *
 * The function auc_authorize() is used to add correct authentication
 * headers to a SIP request. The authentication headers will contain the
 * credentials generated by the list of authenticators.
 *
 * @param[in,out] auc_list  list of authenticators 
 * @param[in,out] msg       message to be authenticated
 * @param[in,out] sip       sip headers of the message
 * 
 * @retval 1 when successful
 * @retval 0 when there is not enough credentials
 * @retval -1 upon an error
 */
int auc_authorize(auth_client_t **auc_list, msg_t *msg, sip_t *sip)
{
  sip_request_t *rq = sip ? sip->sip_request : NULL;

  if (!rq)
    return 0;

  return auc_authorization(auc_list, msg, (msg_pub_t *)sip, 
			   rq->rq_method_name, 
			   /*
			     RFC 3261 defines the protection domain based
			     only on realm, so we do not bother get a
			     correct URI to auth module.
			   */
			   rq->rq_url, 
			   sip->sip_payload);
}
#endif
