#include <sys/types.h>
#include <ctype.h>
#include <stdio.h>
// added by nhatier for compilation under MSVC
#ifdef _MSC_VER
#include <string.h>
  #define strncasecmp strnicmp
  #define strcasecmp stricmp
  #define snprintf _snprintf
#else
  #ifndef _STRINGS_H
    #include <strings.h>
  #endif
  #ifndef _STRING_H
    #include <string.h>
  #endif
#endif
// end added by nhatier
extern char *dns_text(char *);

/* STARTHEAD */
/* This is libdomainkeys.  It's Copyright (c) 2004 Yahoo, Inc.
 * This code incorporates intellectual property owned by
 * Yahoo! and licensed pursuant to the Yahoo! DomainKeys Public License
 * Agreement: http://domainkeys.sourceforge.net/license/softwarelicense1-0.html
 */
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>

#ifdef SWIG
%module domainkeys
%{
#include "domainkeys.h"
%}
#endif

#include "dktrace.h"

/* Performance/Debug options.
 * Uncomment below or use -D switch in gcc
 * DK_DEBUG Dumps whatever dkhash() hashes in to stderr and turns on
 *  some debug warnings that should never happen
 * DK_HASH_BUFF Enables code that uses a buffer when processing the
 *  canocalized message, reducing calls to the crypto library (from dkhash()),
 *  but can use up slightly more memory
*/
//#define DK_DEBUG 1
#define DK_HASH_BUFF 1


#define DKMARK ('D' | 'K'<<8 | 'E'<<16 | 'Y'<<24)
#define DK_SIGNING_SIGN 0
#define DK_SIGNING_VERIFY 1
#define DK_SIGNING_NOSIGN 2
#define DK_SIGNING_NOVERIFY 3
#define DK_MALLOC(s)  OPENSSL_malloc(s)
#define DK_MFREE(s)   OPENSSL_free(s); s = NULL;
#define DKERR(x) ((dk->errline=__LINE__),(dk->errfile=__FILE__),(x))
#define DK_BLOCK 1024 //default size of malloc'd block

/*
 * Option Flags for dk_setopts
 * OR together or run dk_setopts several times
 * All option flags are OFF by default
*/
#define DKOPT_TRACE_h 0x01 //enables tracking character count in pre-canon header
#define DKOPT_TRACE_H 0x02 //enables tracking character count in post-canon header
#define DKOPT_TRACE_b 0x04 //enables tracking character count in pre-canon body
#define DKOPT_TRACE_B 0x08 //enables tracking character count in post-canon header
#define DKOPT_RDUPE 0x10 //enables skipping duplicate headers when generateing a signature

typedef enum
{
  DK_STAT_OK, /* Function completed successfully */
  DK_STAT_BADSIG, /* Signature was available but failed to verify against domain specified key */
  DK_STAT_NOSIG, /* No signature available in message */
  DK_STAT_NOKEY, /* No public key available (permanent failure) */
  DK_STAT_BADKEY, /* Unusable key, public if verifying, private if signing */
  DK_STAT_CANTVRFY, /* Cannot get domain key to verify signature (temporary failure) */
  DK_STAT_SYNTAX, /* Message is not valid syntax. Signature could not be created/checked */
  DK_STAT_NORESOURCE, /* Could not get critical resource (temporary failure) */
  DK_STAT_ARGS, /* Arguments are not usable. */
  DK_STAT_REVOKED,    /* Key has been revoked. */
  DK_STAT_INTERNAL, /* cannot call this routine in this context.  Internal error. */
} DK_STAT;

typedef enum
{
  DK_FLAG_TESTING = 1,    /* set when in testing mode. */
  DK_FLAG_SIGNSALL = 2,   /* domain signs all outgoing email. */
  DK_FLAG_SET = 4,    /* flags set from a successful DNS query */
  DK_FLAG_G = 8,    /* g tag was present in the selector. */
} DK_FLAGS;
typedef enum
{
  DK_TXT_KEY = 0,
  DK_TXT_POLICY
} DK_TXT;

typedef enum
{
  DK_CANON_SIMPLE = 0,
  DK_CANON_NOFWS = 1,
} DK_CANON;
/* STARTSTRUCT */
typedef struct
{
/* STARTPRIV */
  const EVP_MD *md;
/* STOPPRIV */

} DK_LIB;
/* STOPSTRUCT */

//UnixWare Fix -Tim
/* STARTSTRUCT */
typedef struct
{
/* STARTPRIV */
  int dkmarker;     /* in case somebody casts in */
  EVP_MD_CTX mdctx;   /* the hash */
  int signing;      /* our current signing/verifying state */
  int in_headers;   /* true if we're still processing headers */
  char *header;     /* points to a malloc'ed block for header. */
  int headerlen;    /* total length of the header. */
  int headermax;    /* length of malloc'ed block */
  int headerlinelen;    /* current column of the header */
  int start_signed;   /* offset into header of the start of the headers. */
  char *from;     /* saved copy of From: header */
  char *sender;     /* saved copy of Sender: header */
  char *dksign;     /* saved copy of DK-Sig: header */
  char *dktrace;  /* saved copy of DK-Trace: header */
  char *domain;     /* d= */
  char *selector;   /* s= */
  char *signature;    /* b= */
  char *keyrec; /* caller supplied TXT key record */
  char *policyrec; /* caller supplied TXT policy record */
  int canon;      /* c= canonicalization being applied. */
  int state;      /* state variable for canonicalization */
  char *headers;    /* h= */
  int errline;      /* source line of most recent error */
  char *errfile;    /* source file of most recent error */
  char *sender_beforesign; /* saved copy of Sender: header */
  int opts;  /* trace and rdupe flags */
  char last_char; /* the last character processed by dk_message() */
#ifdef DK_HASH_BUFF
  char *hash_buff; /* buffer for hashing */
  int hash_buff_len; /* length of hashing buffer */
#endif
  DK_TRACE *trace; /* pointer to trace count table */
  DK_TRACE *traceheader; /* pointer to trace count table from DK-Trace:*/
/* STOPPRIV */
} DK;
/* STOPSTRUCT */


/* STARTPRIV */
static char *errors[] =
{
  "DK_STAT_OK: Function completed successfully",
  "DK_STAT_BADSIG: Signature was available but failed to verify against domain specified key",
  "DK_STAT_NOSIG: No signature available in message",
  "DK_STAT_NOKEY: No public key available (permanent failure)",
  "DK_STAT_BADKEY: Unusable key, public if verifying, private if signing.",
  "DK_STAT_CANTVRFY: Cannot get domain key to verify signature (temporary failure)",
  "DK_STAT_SYNTAX: Message is not valid syntax. Signature could not be created/checked",
  "DK_STAT_NORESOURCE: Could not get critical resource (temporary failure)",
  "DK_STAT_ARGS: Arguments are not usable.",
  "DK_STAT_REVOKED: Key has been revoked.",
  "DK_STAT_INTERNAL: cannot call this routine in this context.  Internal error.",
};
/* STOPPRIV */
/* STOPHEAD */
//prototype for dk_from
char* dk_from(DK *);
int dk_headers(DK *, char *);

char* dk_strdup(const char* s)
{
  char* new = DK_MALLOC(strlen(s)+1);
  if (new != 0)
  {
    strcpy(new, s);
  }
  return new;
}

/* HEADER */
/* returns the source file from which an error was returned. */
char * dk_errfile(DK *dk)
{
  return dk->errfile;
}

/* HEADER */
/* returns the source line number from which an error was returned. */
int dk_errline(DK *dk)
{
  return dk->errline;
}

/* HEADER */
/* Per-process, one-time initialization
 * Returns library structure for subsequent dk_sign or dk_verify calls.
 * Consult statp before using.
 *
 * When terminating the PROCESS its a good idea to call dk_shutdown()
 * When terminating a THREAD it's a good idea to call ERR_remove_state(0); defined in <openssl/err.h>
 * NOTE: DK_LIB pointers are safe to use over multiple threads
 *       DK pointers are NOT safe to use over multiple threads
 */
DK_LIB *dk_init(DK_STAT *statp)
{
  DK_LIB *dklib;

  dklib = DK_MALLOC(sizeof(DK_LIB));
  if (!dklib)
  {
    if (statp)
    {
      *statp = DK_STAT_NORESOURCE;
    }
    return NULL;
  }
  dklib->md = EVP_sha1();
  if (!dklib->md)
  {
    if (statp)
    {
      *statp = DK_STAT_INTERNAL;
    }
    DK_MFREE(dklib);
    return NULL;
  }
  if (statp)
  {
    *statp = DK_STAT_OK;
  }
  return dklib;
}

/* HEADER */
/* Per-process, one-time cleanup
 * Should be called just before the application ends.
 * the dklib pointer is not valid anymore after this call
 * This function should be called even if dk_init failed.
 * It's safe to call dk_shutdown with a NULL pointer
 */
void dk_shutdown(DK_LIB * dklib)
{
   if (dklib)
   {
      DK_MFREE(dklib);
   }
   CRYPTO_cleanup_all_ex_data();
}

/* start a new header */
static DK_STAT dkinit_new_header(DK *dk)
{
  dk->headermax = DK_BLOCK;
  dk->header = DK_MALLOC(DK_BLOCK);
  if (!dk->header)
  {
    return DKERR(DK_STAT_NORESOURCE);
  }
  dk->headerlen = 0;
  dk->headerlinelen = 1;  /* always store the first char. */
  dk->in_headers = 1;
  dk->start_signed = 0;
  dk->from = NULL;
  dk->sender = NULL;
  dk->dksign = NULL;
  dk->dktrace = NULL;
  dk->canon = DK_CANON_SIMPLE;
  dk->state = 0;
  dk->headers = 0;
  dk->sender_beforesign = NULL;
  dk->opts = 0;
  dk->last_char = '\0';
  dk->trace = NULL;
  dk->traceheader = NULL;
  dk->keyrec = NULL;
  dk->policyrec = NULL;
#ifdef DK_HASH_BUFF
  dk->hash_buff = DK_MALLOC(DK_BLOCK);
  if (!dk->hash_buff)
  {
    return DKERR(DK_STAT_NORESOURCE);
  }
  dk->hash_buff_len = 0;
#endif
  return DKERR(DK_STAT_OK);
}

/* HEADER */
/* Set dk options, use instead of dk_remdupe and dk_enable_trace
 * Can be called multiple times.
 * use after dk_sign()/dk_verify()
 *
 * the bits field can be an OR of any of the following
 *DKOPT_TRACE_h Trace pre-canon header
 *DKOPT_TRACE_H Trace post-canon header
 *DKOPT_TRACE_b Trace pre-canon body
 *DKOPT_TRACE_B Trace post-canon body
 *DKOPT_RDUPE   Exclude duplicate headers from hash (Signing only)
 */
DK_STAT dk_setopts(DK *dk, int bits)
{
  if (!dk)
    return DK_STAT_ARGS;
  if ((dk->headerlen == 0)&&((dk->signing == DK_SIGNING_NOVERIFY)||(dk->signing == DK_SIGNING_SIGN)))
  {
    dk->opts |= bits;
    if ((bits & (DKOPT_TRACE_h|DKOPT_TRACE_H|DKOPT_TRACE_b|DKOPT_TRACE_B)) && !dk->trace)
    {
      dk->trace = DK_MALLOC(sizeof(DK_TRACE));
      if (!dk->trace)
        return DKERR(DK_STAT_NORESOURCE);
      dkt_init(dk->trace);
    }
    if ((dk->signing != DK_SIGNING_SIGN) && (bits & DKOPT_RDUPE))
    {
      return DKERR(DK_STAT_INTERNAL); //cant do rdupe in verify mode
    }
    return DKERR(DK_STAT_OK);
  }
  return DKERR(DK_STAT_INTERNAL);
}
/* HEADER */
/* returns the int holding the options set
 * See dk_setopts for bit flags
 */
int dk_getopts(DK *dk)
{
  if (!dk)
    return 0;
  return (dk->opts);
}

/* HEADER */
/* DEPRECATED in favor of calling dk_setopts().
 * Enables character trace tracking
 *
 * use after dk_sign()/dk_verify()
 */
DK_STAT dk_enable_trace(DK *dk)
{
  return dk_setopts(dk,(DKOPT_TRACE_h|DKOPT_TRACE_H|DKOPT_TRACE_b|DKOPT_TRACE_B));
}
/* HEADER */
/* Prints trace table to *store variable (char string)
 * *dk is the container for the table
 * *store is a pointer to a character array to output to
 * store_size is the size of the character array *store
 *
 */
DK_STAT dk_get_trace(DK *dk, DK_TRACE_TYPE type, char *store, int store_size)
{
  if (!dk)
    return DK_STAT_ARGS;
  if (dk->trace)
  {
    if (!dkt_generate(dk->trace, type, store, store_size))
    {
      return DK_STAT_NORESOURCE; //not enough buffer space
    }
    return DKERR(DK_STAT_OK);
  }
  return DKERR(DK_STAT_INTERNAL);
}

/* HEADER */
/* Prints difference trace table to *store variable (char string)
 * *dk is the container for the table
 * *store is a pointer to a character array to output to
 * store_size is the size of the character array *store
 * return DK_STAT_NOSIG if no DK-Trace header was found
 */
DK_STAT dk_compare_trace(DK *dk, DK_TRACE_TYPE type, char *store, int store_size)
{
  DK_TRACE table;
  if (!dk)
    return DK_STAT_ARGS;
  if (!dk->dktrace || !dk->trace)
    return DK_STAT_NOSIG;
  dkt_init(&table);
  if (!dk->traceheader) //make DK_TRACE from header
  {
    dk->traceheader = DK_MALLOC(sizeof(DK_TRACE));
    if (!dk->traceheader)
      return DKERR(DK_STAT_NORESOURCE);
    dkt_init(dk->traceheader);

    if (!dkt_hdrtotrace(dk->dktrace,dk->traceheader))
    {
      return DK_STAT_NORESOURCE;
    }
  }
  dkt_diff(dk->traceheader, dk->trace, type, &table);
  if (!dkt_generate(&table, type, store, store_size))
  {
    return DK_STAT_NORESOURCE; //not enough buffer space
  }
  return DK_STAT_OK;
}


/* HEADER */
/* Sets the DNS key/policy record manually (no DNS lookup)
 * txtrecord needs to be set to "e=perm;" to force a permanent DNS failure
 * txtrecord needs to be set to "e=temp;" to force a temporary DNS failure
 * Valid DK_TXT types are:
 * DK_TXT_KEY (normal selector record; for <selctor>._domainkey.<domain>)
 * DK_TXT_POLICY (domain policy record; for _domainkey.<domain>)
 */
DK_STAT dk_settxt(DK *dk, DK_TXT recordtype, const char *txtrecord)
{
  char **ptr; //pointer to keyrecord/policyrecord pointer
  if (!dk|| !txtrecord)
    return DK_STAT_ARGS;
  switch (recordtype)
  {
    case DK_TXT_KEY:
      ptr = &dk->keyrec;
      break;
    case DK_TXT_POLICY:
      ptr = &dk->policyrec;
      break;
    default:
      return DK_STAT_ARGS;
  }
  // Free the value iff it is currently set.
  if (*ptr)
  {
    DK_MFREE(*ptr);
  }
  *ptr = dk_strdup(txtrecord);
  return DKERR(DK_STAT_OK);
}

static DK_STAT dkstore_char(DK *dk, char ch)
{
  if (dk->headerlen >= dk->headermax)
  {
    char *hp;
    hp = DK_MALLOC(dk->headermax * 2 + 1024 + 1); /* leave room for null */
    if (!hp)
    {
      return DKERR(DK_STAT_NORESOURCE);
    }
    if (dk->headermax)
    {
      memcpy(hp, dk->header, dk->headerlen);
      DK_MFREE(dk->header);
    }
    dk->header = hp;
    dk->headermax = dk->headermax * 2 + 1024;
  }
  dk->header[dk->headerlen++] = ch;
  dk->headerlinelen++;
  return DKERR(DK_STAT_OK);
}

/* HEADER */
/* Per-message, may be threaded.
 * canon is one of DK_CANON_*.
 * Returns state structure for operation.  Consult statp before using.
 */
DK *dk_sign(DK_LIB *dklib, DK_STAT *statp, int canon)
{
  DK *dk;

  dk = DK_MALLOC(sizeof(DK));
  if (!dk)
  {
    if (statp)
    {
      *statp = DKERR(DK_STAT_NORESOURCE);
    }
    return NULL;
  }
  dk->dkmarker = DKMARK;
  dk->signing = DK_SIGNING_SIGN;
  if (dkinit_new_header(dk) != DK_STAT_OK)
  {
    /* couldn't malloc a header. */
    DK_MFREE(dk);
    if (statp)
    {
      *statp = DKERR(DK_STAT_NORESOURCE);
    }
    return NULL;
  }
  dk->canon = canon; /* TC13-simple, TC13-nofws */
  EVP_SignInit(&dk->mdctx, dklib->md);

  if (statp)
  {
    *statp = DKERR(DK_STAT_OK);
  }
  return dk;
}

/* HEADER */
/* Per-message, may be threaded.
 * Returns state structure for operation.  Consult statp before using.
 */
DK *dk_verify(DK_LIB *dklib, DK_STAT *statp)
{
  DK *dk;

  dk = DK_MALLOC(sizeof(DK));
  if (!dk)
  {
    if (statp)
    {
      *statp = DKERR(DK_STAT_NORESOURCE);
    }
    return NULL;
  }
  dk->dkmarker = DKMARK;
  dk->signing = DK_SIGNING_NOVERIFY; /* wait to verify until we see DK-Signature. */
  if (dkinit_new_header(dk) != DK_STAT_OK)
  {
    /* couldn't malloc a header. */
    DK_MFREE(dk);
    if (statp)
    {
      *statp = DKERR(DK_STAT_NORESOURCE);
    }
    return NULL;
  }
  EVP_VerifyInit(&dk->mdctx, dklib->md);

  if (statp)
  {
    *statp = DKERR(DK_STAT_OK);
  }
  return dk;
}

/* parse an rfc822 address */
/* leave it in the dk_address() format; that is, with the existing
 * first letter, followed by the address */
static DK_STAT dkparse822(char *address, unsigned int offset)
{
  int foundangle = 0;   /* true if it's in angle brackets */
  int foundat = 0;
  char *from, *to;
  int level = 1;

  to = address + 1;
  from = address + offset;
  while (*from)
  {   /* nuke comments. */
    if (*from == ')')
    {
      return DK_STAT_SYNTAX;
    }
    else if (*from == '\\' && from[1])
    {
      /* skip backslash-quoted characters */
      *to++ = *from++;
      *to++ = *from++;
    }
    else if (*from == '"')
    {
      /* we have to ignore parenthesis inside quotes. */
      level = 1;
      from++;
      while (level)
      {
        if (*from == '\0')
        {
          return DK_STAT_SYNTAX;
        }
        else if (*from == '"')
        {
          --level;
        }
        else if (*from == '\\' && from[1])
        {
          *to++ = *from++;
        }
        *to++ = *from++;
      }
    }
    else if (*from == '(')
    {
      level = 1;
      from++;
      while (level)
      {
        if (*from == '\0')
        {
          return DK_STAT_SYNTAX;
        }
        else if (*from == '(')
        {
          ++level;
        }
        else if (*from == ')')
        {
          --level;
        }
        else if (*from == '\\' && from[1])
        {
          ++from;
        }
        ++from;
      }
    }
    else if (*from == ' ')
    {
      ++from;
    }
    else if (*from == '\t')
    {
      ++from;
    }
    else
    {
      *to++ = *from++;
    }
  } //end while(*from)
  *to = '\0';
  from = address + 1;
  to = address + 1;
  while (*from)
  {
    if (*from == '@')
    {
      foundat = 1;
    }
    if (*from == '\\' && from[1])
    {
      *to++ = *from++;
      *to++ = *from++;
    }
    else if (*from == '<')
    {
      from++;
      to = address + 1;
      foundangle = 1;
      foundat = 0;
    }
    else if (*from == ',' || *from == ':' )
    {
      from++;
      to = address + 1;
      foundat = 0;
    }
    else if (foundangle && *from == '>')
    {
      break;
    }
    else if (*from == ';')
    {
      break;
    }
    else
    {
      *to++ = *from++;
    }
  }
  *to = '\0';
  if (foundat)
  {
    return DK_STAT_OK;
  }
  else if (!address[1])
  {
    return DK_STAT_OK;
  }
  else
  {
    return DK_STAT_SYNTAX;  /* no @ in the address */
  }
}

/* Given a list of key=value; pairs, find all the keys found in letters.
 * Store the value in the corresponding entry in values[].
 * Modifies list to insert nulls in the place of the semicolons which terminate
 * each of the values except possibly the last.
 * You can only call dkparselist() once on a given list.
 * Caller must ensure that values[] is as large as strlen(letters)
 * According to the spec, a value can have embedded spaces in it, however none
 * of the existing values need spaces, so we always remove them.
 */

static DK_STAT dkparselist(char *list, char *letters, char *values[])
{
  char key;
  int i;
  char *value;

  /* start with all args unset */
  for (i = 0; letters[i]; i++)
  {
    values[i] = NULL;
  }
  key = 0;
  while (*list)
  {
    if ((*list == ' ')||(*list == '\t')||(*list == '\r')||(*list == '\n'))
    {
      list++;
    }
    else if (*list == '=')
    {
      char *ws;

      ++list;
      value = list;
      ws = list;
      while (1)
      {
        /* copy up to null or semicolon, deleting whitespace as we go */
        *ws = *list;
        if ((*list == ' ')||(*list == '\t')||(*list == '\r')||(*list == '\n'))
        {
          /* ignore */
        }
        else if (!*list)
        {
          break;
        }
        else if (*list == ';')
        {
          *ws = '\0';
          list++;
          break;
        }
        else
        {
          ws++;
        }
        list++;
      }
      if (!key)
      {
        return DK_STAT_SYNTAX; /* we didn't get a key. TC22 */
      }
      /* if we find a matching letter, remember the value */
      for (i = 0; letters[i]; i++)
      {
        if (key == letters[i])
        {
          if (values[i])
          {
            return DK_STAT_SYNTAX; /* no duplicate keys. TC23 */
          }
          values[i] = value;
        }
      }
      key = 0;
    }
    else
    {
      if (key)
      {
        return DK_STAT_SYNTAX; /* they already gave us a key. TC24 */
      }
      key = *list++;
      if (!islower((int)key))
      {
        return DK_STAT_SYNTAX; /* must be lowercase letter. TC25 */
      }
    }
  }
  if (key)
  {
    return DK_STAT_SYNTAX; /* we ended up with an extra key. TC25-1 */
  }
  return DK_STAT_OK;
}

/* HEADER */
/* DEPRECATED in favor of calling dk_setopts()
 * set option to remove dupe headers
 * should be called after dk_sign();
 * any int NOT 0 turns dupe removal on
 */
DK_STAT dk_remdupe(DK *dk,int i)
{
  if (i != 0)
    return dk_setopts(dk,DKOPT_RDUPE);
  return DK_STAT_OK;
}


/* HEADER */
/* Returns the policy flags belonging to the signing domain.
 * Sender: overrides From:, and the d= entry in the DK-Sig overrides both.
 * If the policy flags were not successfully fetched, DK_FLAG_SET will not
 * be set.
 */
DK_FLAGS dk_policy(DK *dk)
{
  char *query, *results, *flags[2];
  char *domain;
  int dkf = 0;

  if (!dk)
    return 0;
  domain = NULL;
  if (dk->dksign)
  {
    domain = dk->domain;
  }
  if (!domain)
  {
    domain = dk_from(dk);
  }
  if (!domain)
  {
    return 0;
  }

  query = DK_MALLOC(strlen("_domainkey.") + strlen(domain) + 1);
  if (query)
  {
     /* allow user to supply the DNS TXT policy record */
    if (!dk->policyrec)
    {
      sprintf(query, "_domainkey.%s", domain);
      results = dns_text(query);
      DK_MFREE(query);
    }
    else
    {
      results = dk_strdup(dk->policyrec);
    }

    if (!strcmp(results,"e=perm;"))
    {
    }
    else if (!strcmp(results,"e=temp;"))
    {
    }
    else
    {
      dkparselist(results, "ot", flags);

      if (flags[0] && *flags[0] == '-')  /* TC36 */
      {
        dkf |= DK_FLAG_SIGNSALL;
      }
      if (flags[1] && *flags[1] == 'y')  /* TC36-1 */
      {
        dkf |= DK_FLAG_TESTING;
      }
      dkf |= DK_FLAG_SET;
    }
    DK_MFREE(results);
  }
  return dkf;
}

/*
dkhash
Internal function. Hashes in a single character, Will NOT has in ' ' or '\t'
if in NOFWS canon. Otherwise tracks \r and \r\n and hashes
them accordingly with the rest of the message.
Should ONLY be called when
  dk->signing == DK_SIGNING_SIGN||DK_SIGNING_VERIFY
otherwise its pointless
*/
static void dkhash(DK *dk, const unsigned char *ptr)
{
#ifdef DK_DEBUG
  if ((dk->signing != DK_SIGNING_SIGN)&&(dk->signing != DK_SIGNING_VERIFY))
  {
    //called when we shouldnt of, this will break the sig
    fprintf(stderr,"\nDKHASH() called for \'%c\' in mode: %i\n",*ptr,dk->signing);
  }
#endif
  if (dk->canon == DK_CANON_NOFWS && (*ptr == ' ' || *ptr == '\t'))
  {
    return;
  }

  if ((dk->in_headers && dk->trace) && (dk->opts & DKOPT_TRACE_H))
  {
    if (dk->canon == DK_CANON_NOFWS)
    {
      if (*ptr != '\r')
      {
        if (*ptr == '\n')
        {
          dk->trace->ccounts_H[10]++;
          dk->trace->ccounts_H[13]++;
        }
        else
        {
          dk->trace->ccounts_H[(int) *ptr]++;
        }
      }
    }
    else
    {
      dk->trace->ccounts_H[(int) *ptr]++;
    }
  }

  if (*ptr == '\r' && ((dk->state & 1) == 0))
  {
    dk->state++;
  }
  else if (*ptr == '\n' && ((dk->state & 1) == 1))
  {
    dk->state++;
  }
  else
  {
    while (dk->state >= 2)
    {

#ifndef DK_HASH_BUFF
      EVP_DigestUpdate(&dk->mdctx, "\r\n", 2);
#else
      /* buffer hack */
      dk->hash_buff[dk->hash_buff_len++] = '\r';
      dk->hash_buff[dk->hash_buff_len++] = '\n';
      if (dk->hash_buff_len >= (DK_BLOCK - 1))
      {
        EVP_DigestUpdate(&dk->mdctx, dk->hash_buff, dk->hash_buff_len);
        dk->hash_buff_len = 0;
      }
      /* buffer hack */
#endif
      if ((!dk->in_headers && dk->trace) && (dk->opts & DKOPT_TRACE_B))
      {
        dk->trace->ccounts_B[10]++;
        dk->trace->ccounts_B[13]++;
      }
#ifdef DK_DEBUG
      fprintf(stderr,"\r\n");
#endif
      dk->state -= 2;
    }
    if (dk->state)
    {
      if (dk->canon == DK_CANON_SIMPLE)//if nofws we ignore \r
      {
#ifndef DK_HASH_BUFF
        EVP_DigestUpdate(&dk->mdctx, "\r", 1);
#else
        /* buffer hack */
        dk->hash_buff[dk->hash_buff_len++] = '\r';
        if (dk->hash_buff_len >= (DK_BLOCK - 1))
        {
          EVP_DigestUpdate(&dk->mdctx, dk->hash_buff, dk->hash_buff_len);
          dk->hash_buff_len = 0;
        }
        /* buffer hack */
#endif
        if ((!dk->in_headers && dk->trace) && (dk->opts & DKOPT_TRACE_B))
        {
          dk->trace->ccounts_B[13]++;
        }
#ifdef DK_DEBUG
        fprintf(stderr,"\r");
#endif
      }
      dk->state --;
    }
#ifndef DK_HASH_BUFF
    EVP_DigestUpdate(&dk->mdctx, ptr, 1);
#else
    /* buffer hack */
    dk->hash_buff[dk->hash_buff_len++] = *ptr;
    if (dk->hash_buff_len >= (DK_BLOCK - 1))
    {
      EVP_DigestUpdate(&dk->mdctx, dk->hash_buff, dk->hash_buff_len);
      dk->hash_buff_len = 0;
    }
    /* buffer hack */
#endif

    if ((!dk->in_headers && dk->trace) && (dk->opts & DKOPT_TRACE_B))
    {
      dk->trace->ccounts_B[(int) *ptr]++;
    }

#ifdef DK_DEBUG
    fprintf(stderr,"%c", *ptr);
#endif
  }
}

/* process headers if there's a h=*/
/* dk->start_signed + 1 is the offset into headers pointing at the first header after DK-Sig */
/* Changed to fix h= handling, checks if header is listed in h=
 *  handles duplicate Header items properly. (see section 3.3 h= draft-02)
 * We don't "look" for missing headers, maybe later.... -Tim
 */
static DK_STAT dkheaders_header(DK *dk)
{
  char *header_line_start,*header_label_end;
  char header_list[1024];
  char header_label[1024];

  char *p;
  //search hack redo later? -tim
  if (snprintf(header_list,sizeof(header_list),":%s:",dk->headers) >= sizeof(header_list))
  {
    //header list is too large for buffer
    return DKERR(DK_STAT_SYNTAX);
  }

  /*convert to all lowercase*/
  for (p = header_list; p[0] != '\0'; ++p)
  {
    p[0] = tolower(p[0]);
  }

  header_label[0] = ':';
  //first char in header_line_start is '\0' unless signing
  header_line_start = dk->header + dk->start_signed;
  if (dk->signing != DK_SIGNING_SIGN)
  {
    ++header_line_start;
  }
  while (1)
  {
    if (header_line_start >= (dk->header + dk->headerlen))
    {
      return DKERR(DK_STAT_OK);  //done reading headers
    }
    header_label_end = header_line_start;
    p = &header_label[1];
    while (p < (header_label + sizeof(header_label) - 2))
    {
      if (header_label_end[0] != ':')
      {
        p[0] = tolower(header_label_end[0]);
        ++p;
        ++header_label_end;
      }
      else
      {
        break;
      }
    }
    p[0] = ':';
    p[1] = '\0';
    //fprintf(stderr,"searching for: %s in %s\n",header_label, header_list);
    //if the header is found in the h= list
    if (strstr(header_list, header_label) != NULL)
    {
      //Found listed header, hash it in
      while(header_line_start[0] != '\0')
      {
        //we kept folded headers \r\n as markers -Tim
        if ((header_line_start[0] == '\n')||(header_line_start[0] == '\r'))
        {
          //if simple we keep the folded lines
          //if nofws we ignore it to unwrap it
          if (dk->canon == DK_CANON_NOFWS)
          {
            ++header_line_start;
            continue;
          }
        }
        dkhash(dk,&header_line_start[0]);
        ++header_line_start;
      }
      dkhash(dk, "\r");
      dkhash(dk, "\n");
    }
    else //skip hashing this header
    {
      while(header_line_start[0] != '\0')
      {
        ++header_line_start;
      }
    }
    //goto next header
    ++header_line_start;

  }//end while
}
/* dkheaders
  When Signing (DK_SIGNING_SIGN), Hashes in ALL headers and Greps out Sender:/From: headers

  When Verifying, reads headers until DK-Sig is found
  then
  If an h= tag is found, Greps out Sender:/From: headers, then calls dkheaders_header
    to hash in the listed headers
  If NO h= tag is present, it Greps out Sender:/From: headers as well as hashes in the
    headers that follow the DK-Sig header.
*/
static DK_STAT dkheaders(DK *dk)
{
  int i;

  for (i = 0; i < dk->headerlen; i++)
  {
    if (dk->headers && dk->signing == DK_SIGNING_VERIFY) //if h= wait till after parsing
    {
      //NOOP
    }
    else if (dk->canon == DK_CANON_NOFWS && (dk->header[i] == '\r' || dk->header[i] == '\n'))
    {
      //NOOP
    }
    /* Newcode */
    else if ((dk->opts & DKOPT_RDUPE)&&(dk->signing == DK_SIGNING_SIGN))
    {
      //NOOP
    }
    else if ((dk->signing == DK_SIGNING_SIGN)||(dk->signing == DK_SIGNING_VERIFY))
    {
      //we wont bother calling dkhash unless we are signing or verifying -Tim
      if (dk->header[i])
      {
        dkhash(dk, dk->header + i);
      }
      else //terminate end of header line
      {
        dkhash(dk, "\r");
        dkhash(dk, "\n");
      }
    }
    /* Newcode end */

    if (i==0 || dk->header[i-1] == '\0')
    {
      if (!strncasecmp(dk->header + i, "From:", 5))
      {
        /* Remember the From: and forget the current. */
        if (dk->from)
        {
          /* if we already got a From: header, fuhgeddabout it. */
          return DKERR(DK_STAT_SYNTAX);
        }
        dk->from = dk_strdup(dk->header + i);
        if (dkparse822(dk->from, 5) != DK_STAT_OK)
        {
          DK_MFREE(dk->from);
          dk->from = 0;
          return DKERR(DK_STAT_SYNTAX);
        }
      }
      else if (!strncasecmp(dk->header + i, "Sender:", 7))
      {
        if (dk->sender)
        {
          /* if we already got a Sender: header, fuhgeddabout it. */
          return DKERR(DK_STAT_SYNTAX);
        }
        if (dk->signing != DK_SIGNING_NOVERIFY)
        {
          /* only remember the Sender if we're verifying already. */
          dk->sender = dk_strdup(dk->header + i);
          if (dkparse822(dk->sender, 7) != DK_STAT_OK)
          {
            DK_MFREE(dk->sender);
            dk->sender = 0;
            return DKERR(DK_STAT_SYNTAX);
          }
          else if (!dk->sender_beforesign)
          {
            dk->sender_beforesign = dk_strdup(dk->header + i);
            if (dkparse822(dk->sender_beforesign, 7) != DK_STAT_OK)
            {
              DK_MFREE(dk->sender_beforesign);
              dk->sender_beforesign = 0;
              return DKERR(DK_STAT_SYNTAX);
            }
          }
        }
      }
      else if (!strncasecmp(dk->header + i, "DomainKey-Trace:", 16))
      {
        if (dk->trace && !dk->dktrace)//only set this once
        {
          dk->dktrace = dk->header + i;
        }
      }
      else if (!strncasecmp(dk->header + i, "DomainKey-Signature:", 20))
      {
        int thisheaderlen = strlen(dk->header + i);

        /* Do not sign email that already has a dksign unless the Sender was found first, 3.5.2 TC41, TC41-1*/
        if (dk->signing == DK_SIGNING_SIGN && !dk->sender)
        {
          return DKERR(DK_STAT_SYNTAX);
        }
        /* check the outermost (first encountered) signature (need to fix when multiple sigs are present)
        * ONLY if we are verifying, if we are signing then ignore it (sender before dk-sig) -Tim
        */
        if ((!dk->dksign)&&(dk->signing != DK_SIGNING_SIGN))
        {
          char *values[7];  /* dsbchqa */
          dk->dksign = dk->header + i;
          /* parse the dksign header .*/
          if (dkparselist(dk->dksign + 20,"dsbchqa", values) != DK_STAT_OK)
          {
            return DKERR(DK_STAT_SYNTAX);
          }

          dk->domain = values[0];
          dk->selector = values[1];
          dk->signature = values[2];
          if (!dk->selector || !dk->domain || !dk->signature)
          {
            /* we really do need to have a domain, selector and key. TC21 TC40 */
            return DKERR(DK_STAT_NOSIG);
          }

          if (!values[3])
          {
            dk->canon = DK_CANON_SIMPLE;
          }
          else if (!strcasecmp(values[3], "simple"))
          {
            dk->canon = DK_CANON_SIMPLE;
          }
          else if (!strcasecmp(values[3], "nofws"))
          {
            dk->canon = DK_CANON_NOFWS;
          }
          else
          {
            return DKERR(DK_STAT_SYNTAX); /* TC42 */
          }

          dk->headers = values[4];

          if (values[5] && strcasecmp(values[5], "dns")) /* TC42-2 */
          {
            return DKERR(DK_STAT_SYNTAX);
          }
          if (values[6] && strcasecmp(values[6], "rsa-sha1")) /* TC42-3 */
          {
            return DKERR(DK_STAT_SYNTAX);
          }

        }
        /* if we're waiting to verify, start now. */
        if (dk->signing == DK_SIGNING_NOVERIFY)
        {
          dk->signing = DK_SIGNING_VERIFY; /* the signature starts here */
          i += thisheaderlen;
          if (dk->start_signed == 0) //we should set it only once right?
          {
            dk->start_signed = i;
          }
        }
      }
      //end trace
    }
  }
  if ((!dk->from)||(!dk_from(dk)))
  {
    /* No From:, 3.1 says that it's no good..  TC11/TC16 */
    return DKERR(DK_STAT_SYNTAX);
  }
  if (dk->signing == DK_SIGNING_NOVERIFY)
  {
    /* No DK-Sig: should return No Signature. */
    return DKERR(DK_STAT_NOSIG);
  }
  if (dk->headers && dk->signing == DK_SIGNING_VERIFY)
  {
    return dkheaders_header(dk);
  }
  if ((dk->signing == DK_SIGNING_SIGN)&&(dk->opts & DKOPT_RDUPE)) //remove dupe headers for sig
  {
    DK_STAT ret;
    dk->headers = DK_MALLOC(dk->headermax);
    (void *)dk_headers(dk, dk->headers);
    ret = dkheaders_header(dk);
    DK_MFREE(dk->headers);
    return ret;
  }
  return DKERR(DK_STAT_OK);
}

/* HEADER */
/* Copies the header names that were signed into the pointer.
 * Returns the number of bytes copied.
 * ptr may be NULL, in which case the bytes are just counted, not copied.
 * Feel free to call this twice; once to get the length, and again to
 * copy the data.
 * If we preserve duplicate headers, actually return something (len)
 * If we remove duplicate headers, len may be innacurate (greater than what's
 * really needed when calling with ptr == NULL
 */
int dk_headers(DK *dk, char *ptr)
{
  int len;
  char *headers;
  int k, m;
  if (!dk)
  {
    return 0;
  }
  if (dk->dkmarker != DKMARK)
  {
    return 0;
  }

  headers = dk->header + dk->start_signed;

  len = 0;
  m = dk->start_signed;
  for (k = dk->start_signed; k < dk->headerlen; k++)
  {
    if (dk->header[k] == '\0')
    {
      m = k+1;
    }
    else if (dk->header[k] == ':' && m >= 0)
    {
      if (ptr)
      {
        memcpy(ptr + len, dk->header + m, k - m + 1);
      }
      len += k - m + 1;
      m = -1;
    }
  }

  if ((dk->opts & DKOPT_RDUPE)&&(ptr))
  {
    int headpos = 0;
    char * copy = (char*)DK_MALLOC(len + 2);
    copy[0] = ':';
    memcpy(copy + 1, ptr, len-1);
    copy[len] = ':';
    copy[len + 1] = 0;
    ptr[0] = 0;
    //fprintf(stderr,"!!%s\n",copy);
    m = 0;
    for (k = 1; k < len + 2; k++)
    {
      if (copy[k] == ':')
      {
        int found = 0;
        int sub;
        for (sub = 0; sub < len + 2; sub++)
        {
          if ((copy[sub] == ':') && (sub != m))
          {
            if (!strncmp(copy + sub, copy + m, k - m))
            {
              found = 1;
              break;
            }
          }
        }
        if (!found)
        {
          memcpy(ptr + headpos, copy + m + 1, k - m);
          headpos += k - m;
          ptr[headpos] = 0;
        }
        m = k;
      }
    }
    DK_MFREE(copy);
    len = headpos;
  }

  if (len && ptr)
  {
    ptr[len-1] = '\0';  /* change the last colon into a null. */
  }
  return len; //return size including '\0'
}

/* HEADER */
/* Must NOT include dots inserted for SMTP encapsulation.
 * Must NOT include CRLF.CRLF which terminates the message.
 * Otherwise must be exactly that which is sent or received over the SMTP session.
 * May be called multiple times (not necessary to read an entire message into memory).
 */
DK_STAT dk_message(DK *dk, const unsigned char *ptr, size_t len)
{
  DK_STAT st;
  int tb=0,th=0;
  if (!dk)
  {
    return DK_STAT_ARGS;
  }
  if (dk->dkmarker != DKMARK)
  {
    return DK_STAT_ARGS;
  }
  if (len && !ptr)
  {
    return DKERR(DK_STAT_ARGS);
  }
  if (dk->trace && (dk->opts & DKOPT_TRACE_b))
    tb = 1;
  if (dk->trace && (dk->opts & DKOPT_TRACE_h))
    th = 1;

  while (len--)
  {
    if ((*ptr == '\n') && (dk->last_char != '\r'))
    {
      //input not formatted correctly
      //CR should preceed LF
      return DKERR(DK_STAT_SYNTAX);
    }
    /* parse headers */
    if (!dk->in_headers)
    {
      dkhash(dk, ptr); //hash body of message
      //precanon probably wont be accurate as dk_message() only takes in SMTP format -Tim
      if (tb)
        dk->trace->ccounts_b[(int) *ptr]++;
    }
    else if (*ptr == '\n' && dk->headerlinelen != 0)
    {
      /* beginning a new line, but we can't do anything until we get a char. */
      dk->headerlinelen = 0;
    }
    else if (*ptr == '\r')
    {
      /* ignore carriage-returns, even bare ones.  We'll add them back in later.*/
      if (dk->last_char == '\r') //keep embedded CR
      {
        if (dkstore_char(dk, '\r') != DK_STAT_OK)
        {
          return DKERR(DK_STAT_NORESOURCE);
        }
        if (th)
          dk->trace->ccounts_h[13]++;
      }
    }
    else if (dk->headerlinelen)
    {

      if (dk->last_char == '\r') //keep embedded CR
      {
        if (dkstore_char(dk, '\r') != DK_STAT_OK)
        {
          return DKERR(DK_STAT_NORESOURCE);
        }
        if (th)
          dk->trace->ccounts_h[13]++;
      }

      /* if we're not starting a new header, just store it */
      if (dkstore_char(dk, *ptr) != DK_STAT_OK)
      {
        return DKERR(DK_STAT_NORESOURCE);
      }
      if (th)
        dk->trace->ccounts_h[(int) *ptr]++;
    }
    else if (*ptr == ' ' || *ptr == '\t')
    {
      /* a new header ... starting with whitespace.  Must be continuation */
      /* just remember the whitespace */
      //preserv folded headers for simple canon -Tim
      if (dkstore_char(dk, '\r') != DK_STAT_OK)
      {
        return DKERR(DK_STAT_NORESOURCE);
      }
      if (dkstore_char(dk, '\n') != DK_STAT_OK)
      {
        return DKERR(DK_STAT_NORESOURCE);
      }
      if (dkstore_char(dk, *ptr) != DK_STAT_OK)
      {
        return DKERR(DK_STAT_NORESOURCE);
      }
      if (th)
      {
        dk->trace->ccounts_h[10]++;
        dk->trace->ccounts_h[13]++;
        dk->trace->ccounts_h[(int) *ptr]++;
      }

    }
    else if (*ptr == '\n')
    {
      /* an empty line terminates header processing */
      /* terminate the previous header */
      if (dkstore_char(dk, '\0') != DK_STAT_OK)
      {
        return DKERR(DK_STAT_NORESOURCE);
      }
      if (th)
      {
        dk->trace->ccounts_h[10]++;
        dk->trace->ccounts_h[13]++;
      }
      if (tb)
      {
        dk->trace->ccounts_b[10]++;
        dk->trace->ccounts_b[13]++;
      }
      st = dkheaders(dk);
      dk->in_headers = 0;
      dkhash(dk, "\r");
      dkhash(dk, "\n");

      if (st != DK_STAT_OK)
      {
        return st;
      }
    }
    else
    {
      /* we're starting a new header.  Terminate the previous one. */
      if (dkstore_char(dk, '\0') != DK_STAT_OK)
      {
        return DKERR(DK_STAT_NORESOURCE);
      }
      /* remember the first character of a new header. */
      if (dkstore_char(dk, *ptr) != DK_STAT_OK)
      {
        return DKERR(DK_STAT_NORESOURCE);
      }
      if (th)
      {
        dk->trace->ccounts_h[10]++;
        dk->trace->ccounts_h[13]++;
        dk->trace->ccounts_h[(int) *ptr]++;
      }
    }
    dk->last_char = *ptr; //remember last character processed
    ptr++;
  }
  return DKERR(DK_STAT_OK);
}

/* HEADER */
/* DEPRECATED in favor of calling dk_address().
 * Returns a pointer to a null-terminated domain name portion of an RFC 2822 address.
 * If a Sender: was encountered, it returns that domain.  Otherwise,
 * if a From: was encountered, it returns that domain.  Otherwise,
 * return NULL.
 * return NULL if no domain name found in the address.
 * return NULL if the dk is unusable for any reason.
 * return NULL if the address is unusable for any reason.
 */
char *dk_from(DK *dk)
{
  char *s;

  if (!dk)
  {
    return NULL;
  }
  if (dk->dkmarker != DKMARK)
  {
    return NULL;
  }

  s = NULL;
  if (dk->sender)
  {
    s = dk->sender; /* TC14-1 */
  }
  else if (dk->sender_beforesign && (dk->signing == DK_SIGNING_NOVERIFY))
  {
    s = dk->sender_beforesign;
  }
  else if (dk->from)
  {
    s = dk->from; /* TC14-2 */
  }
  if (s && *s && s[1] && s[1] != '@')
  {
    s = strchr(s, '@') + 1;
    if (*s == '\0')
    {
      //make sure we have an actual domain after @
      s = NULL;
    }
  }
  else
  {
    s = NULL;
  }
  return s;
}

/* HEADER */
/* Returns a pointer to the selector name used or NULL if there isn't one
 * Added by rjp
 */
const char *dk_selector(DK *dk)
{
  if (!dk) return NULL;
  return (dk->selector);
}

/* HEADER */
/* Returns a pointer to the domain name used or NULL if there isn't one
 */
const char *dk_domain(DK *dk)
{
  if (!dk) return NULL;
  return (dk->domain);
}

/* HEADER */
/*
 * Returns a pointer to a string which begins with "N", "S", or "F",
 * corresponding to None, Sender: and From:, respectively.
 * This single character is followed by a null-terminated RFC 2822 address.
 * The first character is "N" if no valid address has been seen yet,
 * "S" if the address came from the Sender: field, and "F" if the
 * address came from the From: field.
 */
char *dk_address(DK *dk)
{
  if ((!dk)||(dk->dkmarker != DKMARK))
  {
    return "N";
  }
  if (dk->sender)
  {
    return dk->sender; /* TC14-3 */
  }
  if (dk->from)
  {
    return dk->from; /* TC14-4 */
  }
  return "N";     /* TC14-5 */
}

/* HEADER */
/*
 * Called at end-of-message (before response to DATA-dot, if synchronous with SMTP session).
 * If verifying, returns signature validity.
 * This does not calculate the signature.  Call dk_getsig() for that.
 * Flags are returned indirectly through dkf.
 * If you pass in NULL for dkf, the flags will not be fetched.
 * If there is a DK-Sig line, the d= entry will be used to fetch the flags.
 * Otherwise the Sender: domain will be used to fetch the flags.
 * Otherwise the From: domain will be used to fetch the flags.
 *
 * NOTE: If for some reason dk_end() returns an error (!DK_STAT_OK) dk_policy() should be called
 * to get the domain signing policy (o=) and handle accordingly.
 * dkf (selector flags) wont be set if dk_end() returns
 * DK_STAT_NOSIG
 * DK_STAT_NOKEY
 * DK_STAT_SYNTAX
 * DK_STAT_NORESOURCE
 * DK_STAT_BADKEY
 * DK_STAT_CANTVERIFY
 */
DK_STAT dk_end(DK *dk, DK_FLAGS *dkf)
{
  if ((!dk)||(dk->dkmarker != DKMARK))
  {
    return DK_STAT_ARGS;
  }

  /* yes, the only way we can still be in the headers at the end of
     the message is if they supplied us with a message that not only
     has no body, but also lacks the blank line terminating the
     headers.  Still, we have to handle it correctly.  TC13-nobody */
  if (dk->in_headers)
  {
    DK_STAT st;

    if (dkstore_char(dk, '\0') != DK_STAT_OK)
    {
      return DKERR(DK_STAT_NORESOURCE);
    }
    st = dkheaders(dk);
    if (st != DK_STAT_OK)
    {
      return st;
    }
  }

  switch (dk->signing)
  {
  case DK_SIGNING_SIGN:
  case DK_SIGNING_VERIFY:
    //force hash final CRLF to terminate email
#ifdef DK_HASH_BUFF
    //clean out hash buffer
    dk->hash_buff[dk->hash_buff_len++] = '\r';
    dk->hash_buff[dk->hash_buff_len++] = '\n';
    EVP_DigestUpdate(&dk->mdctx, dk->hash_buff, dk->hash_buff_len);
    dk->hash_buff_len = 0;
#else
    EVP_DigestUpdate(&dk->mdctx, "\r\n", 2);
#endif
#ifdef DK_DEBUG
    fprintf(stderr,"\r\n");
#endif

  }

  switch (dk->signing)
  {
  case DK_SIGNING_SIGN:
  case DK_SIGNING_NOSIGN:
    if (!dk->from)
    {
      return DKERR(DK_STAT_SYNTAX);
    }
    return DKERR(DK_STAT_OK);
  case DK_SIGNING_VERIFY:
  case DK_SIGNING_NOVERIFY:
    {
      unsigned char md_value[1024];
      int md_len,i;
      char *s = NULL;
      char *domainkeys; /* malloc'ed */
      char *txtrec; /* malloc'ed */
      char *pubkeyvals[4];
      int okay;
      DK_STAT st;

      BIO *bio, *b64;
      EVP_PKEY *publickey;

      /* make sure that we got a header */
      if (!dk->dksign)
      {
        return DKERR(DK_STAT_NOSIG); /* TC11 */
      }

      /* make sure that domain on the From: matches the d data. TC26 */
      /* Or is a subdomain of the d data.  TC26-1, etc. */
      if (dk->sender)
      {
        s = dk->sender;
      }
      else if (dk->sender_beforesign && (dk->signing == DK_SIGNING_NOVERIFY))
      {
        s = dk->sender_beforesign;
      }
      else
      {
        s = dk->from;
      }

      if (!s)
      {
        return DKERR(DK_STAT_SYNTAX);
      }
      /* we already know that s has an '@' in it; otherwise it would not
      * have passed dkparse822. */
      s = strchr(s, '@')+1;

      i = 0;
      if (strcasecmp(dk->domain, s))
      {
        for (; i < (int)strlen(s); i++)
        {
          if (s[i] == '.')
          {
            if (!strcasecmp(dk->domain, s + i + 1))
            {
              break;
            }
          }
        }
      }
      if (i == (int)strlen(s))
      {
        return DKERR(DK_STAT_SYNTAX);
      }

      /* convert their signature from base64 into binary */
      bio = BIO_new_mem_buf(dk->signature, -1);
      b64 = BIO_new(BIO_f_base64());
      BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
      BIO_push(b64, bio);
      md_len = BIO_read(b64, md_value, sizeof(md_value));
      BIO_free_all(b64);
      if (md_len >= sizeof(md_value))
      {
        return DKERR(DK_STAT_NORESOURCE);
      }

      /* allow user to supply the DNS TXT key record */
      if (!dk->keyrec )
      {
        /* get the s data and the d data and lookup s._domainkey.d */
        domainkeys = DK_MALLOC(strlen(dk->selector) + strlen(dk->domain) + strlen("._domainkey.") + 1);
        if (!domainkeys)
        {
          return DKERR(DK_STAT_NORESOURCE);
        }
        sprintf(domainkeys, "%s._domainkey.%s", dk->selector, dk->domain);
        txtrec = dns_text(domainkeys);
        DK_MFREE(domainkeys);
      }
      else
      {
        txtrec = dk_strdup(dk->keyrec);
      }

      if (!strcmp(txtrec,"e=perm;")) /* TC31 */
      {
        DK_MFREE(txtrec);
        return DKERR(DK_STAT_NOKEY);
      }

      if (!strcmp(txtrec,"e=temp;")) /* TC30 */
      {
        DK_MFREE(txtrec);
        return DKERR(DK_STAT_CANTVRFY);
      }

      if (dkparselist(txtrec, "ptog", pubkeyvals) != DK_STAT_OK)
      {
        DK_MFREE(txtrec);
        return DKERR(DK_STAT_BADKEY);
      }
      if (dkf) //set this asap
      {
        /* TC35 and TC37 */
        if (pubkeyvals[1] && *pubkeyvals[1] == 'y')
        {
          *dkf |= DK_FLAG_TESTING;
        }
        /* tell them that we got the g= flag.  This means that the entire
         * address matches, not just the domain name */
        if (pubkeyvals[3] && *pubkeyvals[3])
        {
          *dkf |= DK_FLAG_G;
        }
        *dkf |= DK_FLAG_SET;
      }

      if (!pubkeyvals[0])
      {
        DK_MFREE(txtrec);
        return DKERR(DK_STAT_NOKEY); /* TC27 */
      }

      if (!*pubkeyvals[0])
      {
        DK_MFREE(txtrec);
        return DKERR(DK_STAT_REVOKED); /* TC27 */
      }

      /* convert their public key from base64 into binary */
      bio = BIO_new_mem_buf(pubkeyvals[0], -1);
      if (!bio)
      {
        DK_MFREE(txtrec);
        return DKERR(DK_STAT_NORESOURCE);
      }
      b64 = BIO_new(BIO_f_base64());
      if (!b64)
      {
        DK_MFREE(txtrec);
        BIO_free(bio);
        return DKERR(DK_STAT_NORESOURCE);
      }
      BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
      BIO_push(b64, bio);
      publickey = d2i_PUBKEY_bio(b64, NULL);
      BIO_free_all(b64);
      DK_MFREE(txtrec);
      if (!publickey)
      {
        return DKERR(DK_STAT_BADKEY);
      }

      /* using that key, verify that the digest is properly signed */
      okay = EVP_VerifyFinal(&dk->mdctx, md_value, md_len, publickey);

      if (okay > 0)
      {
        st = DK_STAT_OK;
      }
      else
      {
        st = DK_STAT_BADSIG;
      }
      EVP_PKEY_free(publickey);
      return DKERR(st);
    }
  }//End switch
  return DK_STAT_ARGS;
}


/* HEADER */
/*
 * DEPRECATED in favor of calling dk_end and dk_policy() directly.
 * If you pass in NULL for dkf, the policy flags will not be fetched.
 * If the message verified okay, the policy flags will not be fetched.
 */
DK_STAT dk_eom(DK *dk, DK_FLAGS *dkf)
{
  DK_STAT dkstat;

  dkstat = dk_end(dk, dkf);  /* TC36* */
  if (dkf && dkstat != DK_STAT_OK)
  {
    *dkf |= dk_policy(dk);
  }
  return dkstat;
}


/* HEADER */
/*
 *
 * privatekey is the private key used to create the signature; It should contain
 * the entire contents of a PEM-format private key file, thusly it will begin with
 * -----BEGIN RSA PRIVATE KEY-----.  It should be null-terminated.
 */
size_t dk_siglen(void *privatekey)
{
  BIO *bio;
  EVP_PKEY *pkey;
  size_t len;

  if (!privatekey)
  {
    return 0;
  }

  bio = BIO_new_mem_buf(privatekey, -1);
  pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
  BIO_free(bio);

  len = (EVP_PKEY_size(pkey) + 2) / 3 * 4;
  EVP_PKEY_free(pkey);
  return len;
}


/* HEADER */
/*
 * Sets buf to a null-terminated string.
 * If the message is being signed, signature is stored in the buffer.
 * If the message is being verified, returns DK_STAT_INTERNAL.
 * privatekey is the private key used to create the signature; It should contain
 * the entire contents of a PEM-format private key file, thus it will begin with
 * -----BEGIN RSA PRIVATE KEY-----.  It should be null-terminated.
 * If you pass in NULL for buf, you'll get back DK_STAT_NORESOURCE.
 * If len is not big enough, you'll get back DK_STAT_NORESOURCE.
 */
DK_STAT dk_getsig(DK *dk, void *privatekey, unsigned char buf[], size_t len)
{
  if ((!dk)||(dk->dkmarker != DKMARK)||(!privatekey))
  {
    return DK_STAT_ARGS;
  }
  if (!buf)
  {
    return DKERR(DK_STAT_NORESOURCE);
  }
  switch (dk->signing)
  {
    case DK_SIGNING_SIGN:
    case DK_SIGNING_NOSIGN:
    {
      unsigned int siglen;
      unsigned char *sig;
      int size;
      BIO *bio, *b64;
      EVP_PKEY *pkey;

      bio = BIO_new_mem_buf(privatekey, -1);
      pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
      BIO_free(bio);

      if (!pkey)
      {
        /* their private key is no good TC33 */
        return DKERR(DK_STAT_BADKEY);
      }

      siglen = EVP_PKEY_size(pkey);
      sig = (unsigned char*) OPENSSL_malloc(siglen);
      EVP_SignFinal(&dk->mdctx, sig, &siglen, pkey);
      EVP_PKEY_free(pkey);

      bio = BIO_new(BIO_s_mem());
      if (!bio)
      {
        return DKERR(DK_STAT_NORESOURCE);
      }
      b64 = BIO_new(BIO_f_base64());
      if (!b64)
      {
        BIO_free(bio);
        return DKERR(DK_STAT_NORESOURCE);
      }
      BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
      BIO_push(b64, bio);
      if ((size_t)BIO_write(b64, sig, siglen) < siglen)
      {
        OPENSSL_free(sig);
        BIO_free_all(b64);
        return DKERR(DK_STAT_NORESOURCE);
      }
      BIO_flush(b64);
      OPENSSL_free(sig);

      size = BIO_read(bio, buf, len);
      BIO_free_all(b64);

      if ((size_t)size >= len)
      {
        return DKERR(DK_STAT_NORESOURCE); /* TC28 */
      }

      buf[size] = '\0';
      return DKERR(DK_STAT_OK);
    }
    case DK_SIGNING_VERIFY:
    case DK_SIGNING_NOVERIFY:
      return DKERR(DK_STAT_INTERNAL);
  }//end switch
  return DK_STAT_ARGS;
}


/* HEADER */
/*
 * Free all resources associated with this message.
 * dk is no longer usable.
 * if doClearErrState != 0, the OpenSSL ErrorState is freed.
 * Set clearErrState=0 if you use other openssl functions and
 * want to call openssl's ERR_remove_state(0) by yourself
 * ERR_remove_state(0) is declared in <openssl/err.h>
 */
DK_STAT dk_free(DK *dk, int doClearErrState)
{
  if ((!dk)||(dk->dkmarker != DKMARK))
  {
    return DK_STAT_ARGS;
  }
  if (dk->from)
  {
    DK_MFREE(dk->from);
  }
  if (dk->sender)
  {
    DK_MFREE(dk->sender);
  }
  if (dk->sender_beforesign)
  {
    DK_MFREE(dk->sender_beforesign);
  }
  if (dk->trace)
  {
    DK_MFREE(dk->trace);
  }
  if (dk->traceheader)
  {
    DK_MFREE(dk->trace);
  }
  if (dk->keyrec)
  {
    DK_MFREE(dk->keyrec);
  }
  if (dk->policyrec)
  {
    DK_MFREE(dk->policyrec);
  }
#ifdef DK_HASH_BUFF
  DK_MFREE(dk->hash_buff);
#endif
  EVP_MD_CTX_cleanup(&dk->mdctx);
  DK_MFREE(dk->header);   /* alloc'ing dk->header is not optional. */
  dk->dkmarker = ~DKMARK;
  DK_MFREE(dk);

  if (doClearErrState)
  {
     ERR_remove_state(0);
  }
  return DK_STAT_OK;
}

/* HEADER */
/*
 * return a pointer to a string which describes st.
 * The string is structured.  All the characters up to the first colon
 * contain the name of the DK_STAT constant.  From there to the end of
 * string is a human-readable description of the error.
 */
const char *DK_STAT_to_string(DK_STAT st)
{
  /* TC53 */
  if (st >= (sizeof errors) / (sizeof errors[0]))
  {
    return "DK_STAT_UNKNOWN: unknown status";
  }
  else
  {
    return errors[st];
  }
}


syntax highlighted by Code2HTML, v. 0.9.1