/* Simple BBC News ticker client
 * (c) Darren Salt
 * GPL applies
 * $Id: digest.c,v 1.1.1.1 2003/03/10 23:31:07 ds Exp $
 */

/* System includes */

#include <ctype.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

/* Program includes */

#include "globals.h"

#include "digest.h"
#include "md5.h"
#include "util.h"


/*********************************************
 * HTTP Digest generator code (ref. RFC2617) *
 *********************************************/

#define HASHLEN 16
typedef char HASH[HASHLEN + 1];
#define HASHHEXLEN 32
typedef char HASHHEX[HASHHEXLEN + 1];


static void
CvtHex (HASH Bin, HASHHEX Hex)
{
  int i;

  for (i = 0; i < HASHLEN; i++)
  {
    char j = (Bin[i] >> 4) & 0xF;
    Hex[i * 2] = j + ((j < 10) ? '0' : 'a' - 10);
    j = Bin[i] & 0xF;
    Hex[i * 2 + 1] = j + ((j < 10) ? '0' : 'a' - 10);
  }
  Hex[HASHHEXLEN] = '\0';
}


/* calculate H(A1) */

static void
DigestCalcHA1 (const char *pszAlg, const char *pszUserName,
	       const char *pszRealm, const char *pszPassword,
	       const char *pszNonce, const char *pszCNonce,
	       HASHHEX SessionKey)
{
  struct md5_ctx Md5Ctx;
  HASH HA1;

  md5_init_ctx (&Md5Ctx);
  md5_process_bytes (pszUserName, strlen (pszUserName), &Md5Ctx);
  md5_process_bytes (":", 1, &Md5Ctx);
  md5_process_bytes (pszRealm, strlen (pszRealm), &Md5Ctx);
  md5_process_bytes (":", 1, &Md5Ctx);
  md5_process_bytes (pszPassword, strlen (pszPassword), &Md5Ctx);
  md5_finish_ctx (&Md5Ctx, HA1);
  if (stricmp (pszAlg, "md5-sess") == 0)
  {
    md5_init_ctx (&Md5Ctx);
    md5_process_bytes (HA1, strlen (HA1), &Md5Ctx);
    md5_process_bytes (":", 1, &Md5Ctx);
    md5_process_bytes (pszNonce, strlen (pszNonce), &Md5Ctx);
    md5_process_bytes (":", 1, &Md5Ctx);
    md5_process_bytes (pszCNonce, strlen (pszCNonce), &Md5Ctx);
    md5_finish_ctx (&Md5Ctx, HA1);
  }
  CvtHex (HA1, SessionKey);
}


/* calculate response digest */

static void
DigestCalcResponse (HASHHEX HA1, const char *pszNonce,
		    const char *pszNonceCount, const char *pszCNonce,
		    const char *pszQop, const char *pszMethod,
		    const char *pszDigestUri, HASHHEX HEntity,
		    HASHHEX Response)
{
  struct md5_ctx Md5Ctx;
  HASH HA2;
  HASH RespHash;
  HASHHEX HA2Hex;

  /* calculate H(A2) */
  md5_init_ctx (&Md5Ctx);
  md5_process_bytes (pszMethod, strlen (pszMethod), &Md5Ctx);
  md5_process_bytes (":", 1, &Md5Ctx);
  md5_process_bytes (pszDigestUri, strlen (pszDigestUri), &Md5Ctx);
  if (!stricmp (pszQop, "auth-int"))
  {
    md5_process_bytes (":", 1, &Md5Ctx);
    md5_process_bytes (HEntity, HASHHEXLEN, &Md5Ctx);
  }
  md5_finish_ctx (&Md5Ctx, HA2);
  CvtHex (HA2, HA2Hex);

  /* calculate response */
  md5_init_ctx (&Md5Ctx);
  md5_process_bytes (HA1, HASHHEXLEN, &Md5Ctx);
  md5_process_bytes (":", 1, &Md5Ctx);
  md5_process_bytes (pszNonce, strlen (pszNonce), &Md5Ctx);
  md5_process_bytes (":", 1, &Md5Ctx);
  if (*pszQop)
  {
    md5_process_bytes (pszNonceCount, strlen (pszNonceCount), &Md5Ctx);
    md5_process_bytes (":", 1, &Md5Ctx);
    md5_process_bytes (pszCNonce, strlen (pszCNonce), &Md5Ctx);
    md5_process_bytes (":", 1, &Md5Ctx);
    md5_process_bytes (pszQop, strlen (pszQop), &Md5Ctx);
    md5_process_bytes (":", 1, &Md5Ctx);
  }
  md5_process_bytes (HA2Hex, HASHHEXLEN, &Md5Ctx);
  md5_finish_ctx (&Md5Ctx, RespHash);
  CvtHex (RespHash, Response);
}


/********************
 * Ticker internals *
 ********************/


/* Read a possibly quoted string from *text, updating the pointer */

static char *
get_string (char *buf, char **text)
{
  char *src = *text;
  /* Get a quoted string (with \ escapes). Reallocate buf; update *text. */
  if (*src == '"')
  {
    int ptr = 1, length = 1;
    while (src[ptr] && src[ptr] != '"')
    {
      if (src[ptr++] == '\\' && src[ptr])
	ptr++;
      length++;
    }
    buf = realloc (buf, (strlen (buf) + length + 511) & ~511);
    ptr = 1;
    length = 0;
    if (buf)
    {
      while (src[ptr] && src[ptr] != '"')
      {
	if (src[ptr] == '\\')
	{
	  if (src[++ptr])
	    buf[length++] = src[ptr++];
	}
	else
	  buf[length++] = src[ptr++];
      }
      buf[length] = '\0';
    }
    *text = src + ptr + 1;
  }
  else
  {
    /* Unquoted */
    char *end = strpbrk (src, " \t\r\n");
    if (!end)
      end = src + strlen (src);
    buf = realloc (buf, (end - src + 512) & ~511);
    if (buf)
    {
      buf[end - src] = '\0';
      memcpy (buf, text, end - src);
    }
    *text = end;
  }

  return buf;
}


/* Append strings, quoting some */

char *
concat (char *buffer, ...)
{
  /* Concatenate arguments into buffer. */
  int quoted = 0;
  const char *text;
  va_list arg;

  va_start (arg, buffer);

  if (!buffer)
  {
    buffer = malloc (1);
    if (!buffer)
      return 0;
    *buffer = '\0';
  }

  while ((text = va_arg (arg, const char *)) != END)
  {
    if (text == QUOTE)
      quoted = 1;
    else if (quoted)
    {
      int ptr = 0, length = 3;
      while (text[ptr])
      {
	if (text[ptr] == '"' || text[ptr] == '\\')
	  length++;
	ptr++;
	length++;
      }
      buffer = realloc (buffer, (strlen (buffer) + length + 511) & ~511);
      if (!buffer)
	return 0;
      ptr = strlen (buffer);
      buffer[ptr++] = '"';
      while (*text)
      {
	char c = *text++;
	if (c == '"' || c == '\\')
	  buffer[ptr++] = '\\';
	buffer[ptr++] = c;
      }
      buffer[ptr++] = '"';
      buffer[ptr++] = '\0';
      quoted = 0;
    }
    else
    {
      buffer =
	realloc (buffer, (strlen (buffer) + strlen (text) + 512) & ~511);
      if (!buffer)
	return 0;
      strcat (buffer, text);
    }
  }

  return buffer;
}


/* Given a set of headers, a user name, password and URI, either check for
 * staleness or calculate the response.
 * If calculating:
 *   returns the digest, "" if not enough data, or NULL if not enough memory;
 * else
 *   returns "b" if basic auth, "d" if digest auth, "?" if unknown;
 *   appends "s" if stale.
 */

static struct
{
  char *Nonce;
  char *Realm;
  char *Alg;
  char *Qop;
  char *opaque;
}
cache = {0};


char *
calculate_digest (const char *headers, const char *user, const char *pass,
		  const char *URI, int check_only, int reset)
{
  static char cnonce[9];
  static ulong intcount = 0;

  char cnoncecount[9];
  int stale = 0;
  char *ret = 0;

  HASHHEX HA1;
  HASHHEX HA2 = "";
  HASHHEX Response;

  if (reset)
    intcount = 0;

  /* Point past protocol and host */
  /*pszURI = strchr (strchr (pszURI, '/') + 2, '/'); */

  if (headers)
  {
    char *p;

    free (cache.Realm);
    free (cache.Nonce);
    free (cache.Alg);
    free (cache.Qop);
    free (cache.opaque);
    cache.Realm = 0;
    cache.Nonce = 0;
    cache.Alg = 0;
    cache.Qop = 0;
    cache.opaque = 0;

    p = strstr (headers, "\nProxy-Authenticate: ");
    if (!p)
      return 0;
    p += 21;
    /* Return now if it's basic (trivial) or not digest (unsupported) */
    if (!strnicmp (p, "basic", 5) && isspace (p[5]))
      return "b";
    if (strnicmp (p, "digest", 6) || !isspace (p[6]))
      return "?";
    /* Assume digest */
    do
    {
      /* Skip whitespace */
      while (*p == ' ' || *p == '\t')
	p++;
      if (!strnicmp (p, "realm=", 6))
      {
	p += 6;
	cache.Realm = get_string (cache.Realm, &p);
      }
      else if (!strnicmp (p, "nonce=", 6))
      {
	p += 6;
	cache.Nonce = get_string (cache.Nonce, &p);
      }
      else if (!strnicmp (p, "algorithm", 10))
      {
	p += 10;
	cache.Alg = get_string (cache.Alg, &p);
      }
      else if (!strnicmp (p, "qop=", 4))
      {
	p += 4;
	cache.Qop = get_string (cache.Qop, &p);
      }
      else if (!strnicmp (p, "opaque=", 7))
      {
	p += 7;
	cache.opaque = get_string (cache.opaque, &p);
      }
      else if (!strnicmp (p, "stale=true", 10)
	       && (p[10] < ' ' || p[10] == ','))
      {
	stale = 1;
	p += 10;
      }
      else
	p = strpbrk (p, ", \t\r\n");
      if (p && *p == ',')
	p++;
    }
    while (p && *p >= ' ');
  }

  if (cache.Alg && stricmp (cache.Alg, "md5")
      && stricmp (cache.Alg, "md5-sess"))
    return check_only ? "?" : "";

  if (!headers && !(cache.Realm && cache.Nonce))
    return "?";

  if (check_only)
    return stale ? "ds" : "d";

  if (!intcount)
    sprintf (cnonce, "%08lX", rnd ());
  sprintf (cnoncecount, "%08lX", ++intcount);

  DigestCalcHA1 (cache.Alg ? cache.Alg : "md5", user, cache.Realm, pass,
		 cache.Nonce, cnonce, HA1);
  DigestCalcResponse (HA1, cache.Nonce, cnoncecount, cnonce,
		      cache.Qop ? cache.Qop : "", "GET", URI, HA2, Response);

  ret =
    concat (ret, "Proxy-Authorization: Digest username=", QUOTE, user,
	    ", realm=", QUOTE, cache.Realm, ", nonce=", QUOTE, cache.Nonce,
	    ", uri=", QUOTE, URI, END);
  if (cache.Qop)
    ret = concat (ret, ", qop=", QUOTE, cache.Qop, ", nc=", cnoncecount,
		  ", cnonce=", QUOTE, cnonce, END);
  ret = concat (ret, ", response=", QUOTE, Response, END);
  if (cache.opaque)
    ret = concat (ret, ", opaque=", QUOTE, cache.opaque, END);
  ret = concat (ret, "\r\n", END);

  return ret;
}


const char *
get_realm (void)
{
  return cache.Realm;
}
