* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-/* $Cambridge: exim/src/src/pdkim/pdkim.c,v 1.2 2009/06/10 07:34:05 tom Exp $ */
+/* $Cambridge: exim/src/src/pdkim/pdkim.c,v 1.14 2010/05/29 19:14:06 nm4 Exp $ */
#include <stdlib.h>
#include <stdio.h>
/* -------------------------------------------------------------------------- */
/* A bunch of list constants */
-char *pdkim_querymethods[] = {
+const char *pdkim_querymethods[] = {
"dns/txt",
NULL
};
-char *pdkim_algos[] = {
+const char *pdkim_algos[] = {
"rsa-sha256",
"rsa-sha1",
NULL
};
-char *pdkim_canons[] = {
+const char *pdkim_canons[] = {
"simple",
"relaxed",
NULL
};
-char *pdkim_hashes[] = {
+const char *pdkim_hashes[] = {
"sha256",
"sha1",
NULL
};
-char *pdkim_keytypes[] = {
+const char *pdkim_keytypes[] = {
"rsa",
NULL
};
typedef struct pdkim_combined_canon_entry {
- char *str;
+ const char *str;
int canon_headers;
int canon_body;
} pdkim_combined_canon_entry;
};
+const char *pdkim_verify_status_str(int status) {
+ switch(status) {
+ case PDKIM_VERIFY_NONE: return "PDKIM_VERIFY_NONE";
+ case PDKIM_VERIFY_INVALID: return "PDKIM_VERIFY_INVALID";
+ case PDKIM_VERIFY_FAIL: return "PDKIM_VERIFY_FAIL";
+ case PDKIM_VERIFY_PASS: return "PDKIM_VERIFY_PASS";
+ default: return "PDKIM_VERIFY_UNKNOWN";
+ }
+}
+const char *pdkim_verify_ext_status_str(int ext_status) {
+ switch(ext_status) {
+ case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY";
+ case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE";
+ case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: return "PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE";
+ case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE";
+ case PDKIM_VERIFY_INVALID_PUBKEY_PARSING: return "PDKIM_VERIFY_INVALID_PUBKEY_PARSING";
+ default: return "PDKIM_VERIFY_UNKNOWN";
+ }
+}
+
+
/* -------------------------------------------------------------------------- */
/* Print debugging functions */
#ifdef PDKIM_DEBUG
-void pdkim_quoteprint(FILE *stream, char *data, int len, int lf) {
+void pdkim_quoteprint(FILE *stream, const char *data, int len, int lf) {
int i;
- unsigned char *p = (unsigned char *)data;
+ const unsigned char *p = (const unsigned char *)data;
for (i=0;i<len;i++) {
- int c = p[i];
+ const int c = p[i];
switch (c) {
case ' ' : fprintf(stream,"{SP}"); break;
case '\t': fprintf(stream,"{TB}"); break;
if (lf)
fputc('\n',stream);
}
-void pdkim_hexprint(FILE *stream, char *data, int len, int lf) {
+void pdkim_hexprint(FILE *stream, const char *data, int len, int lf) {
int i;
- unsigned char *p = (unsigned char *)data;
+ const unsigned char *p = (const unsigned char *)data;
for (i=0;i<len;i++) {
- int c = p[i];
+ const int c = p[i];
fprintf(stream,"%02x",c);
}
if (lf)
if (new_entry->value == NULL) return NULL;
if (base != NULL) {
pdkim_stringlist *last = base;
- while (last->next != NULL) { last = last->next; };
+ while (last->next != NULL) { last = last->next; }
last->next = new_entry;
return base;
}
else return new_entry;
-};
+}
+pdkim_stringlist *pdkim_prepend_stringlist(pdkim_stringlist *base, char *str) {
+ pdkim_stringlist *new_entry = malloc(sizeof(pdkim_stringlist));
+ if (new_entry == NULL) return NULL;
+ memset(new_entry,0,sizeof(pdkim_stringlist));
+ new_entry->value = strdup(str);
+ if (new_entry->value == NULL) return NULL;
+ if (base != NULL) {
+ new_entry->next = base;
+ }
+ return new_entry;
+}
/* -------------------------------------------------------------------------- */
/* A small "growing string" implementation to escape malloc/realloc hell */
-pdkim_str *pdkim_strnew (char *cstr) {
+pdkim_str *pdkim_strnew (const char *cstr) {
unsigned int len = cstr?strlen(cstr):0;
pdkim_str *p = malloc(sizeof(pdkim_str));
if (p == NULL) return NULL;
p->allocated=(len+1);
p->len=len;
if (cstr) strcpy(p->str,cstr);
+ else p->str[p->len] = '\0';
return p;
-};
-char *pdkim_strncat(pdkim_str *str, char *data, int len) {
+}
+char *pdkim_strncat(pdkim_str *str, const char *data, int len) {
if ((str->allocated - str->len) < (len+1)) {
/* Extend the buffer */
int num_frags = ((len+1)/PDKIM_STR_ALLOC_FRAG)+1;
str->len+=len;
str->str[str->len] = '\0';
return str->str;
-};
-char *pdkim_strcat(pdkim_str *str, char *cstr) {
+}
+char *pdkim_strcat(pdkim_str *str, const char *cstr) {
return pdkim_strncat(str, cstr, strlen(cstr));
-};
+}
char *pdkim_numcat(pdkim_str *str, unsigned long num) {
char minibuf[20];
snprintf(minibuf,20,"%lu",num);
return pdkim_strcat(str,minibuf);
-};
+}
char *pdkim_strtrim(pdkim_str *str) {
char *p = str->str;
char *q = str->str;
while ( (*p != '\0') && ((*p == '\t') || (*p == ' ')) ) p++;
- while (*p != '\0') {*q = *p; q++; p++;};
+ while (*p != '\0') {*q = *p; q++; p++;}
*q = '\0';
while ( (q != str->str) && ( (*q == '\0') || (*q == '\t') || (*q == ' ') ) ) {
*q = '\0';
}
str->len = strlen(str->str);
return str->str;
-};
+}
char *pdkim_strclear(pdkim_str *str) {
str->str[0] = '\0';
str->len = 0;
return str->str;
-};
+}
void pdkim_strfree(pdkim_str *str) {
if (str == NULL) return;
if (str->str != NULL) free(str->str);
free(str);
-};
+}
free(sig);
if (next != NULL) pdkim_free_sig(next);
}
-};
+}
/* -------------------------------------------------------------------------- */
pdkim_strfree(ctx->cur_header);
free(ctx);
}
-};
+}
/* -------------------------------------------------------------------------- */
the passed colon-separated "list", starting at entry
"start". Returns the position of the header name in
the list. */
-int header_name_match(char *header,
- char *tick,
- int do_tick) {
+int header_name_match(const char *header,
+ char *tick,
+ int do_tick) {
char *hname;
char *lcopy;
char *p;
int seen_wsp = 0;
char *p = header;
char *q;
- char *relaxed = malloc(strlen(header));
+ char *relaxed = malloc(strlen(header)+3);
if (relaxed == NULL) return NULL;
q = relaxed;
while (*p != '\0') {
p++;
q++;
}
+ if (seen_wsp) q--;
*q = '\0';
if (crlf) strcat(relaxed,"\r\n");
return relaxed;
-};
+}
/* -------------------------------------------------------------------------- */
if (isxdigit(*qp_p) && isxdigit(qp_p[1])) {
/* Do hex conversion */
if (isdigit(*qp_p)) {*c = *qp_p - '0';}
- else {*c = toupper(*qp_p) - 'A' + 10;};
+ else {*c = toupper(*qp_p) - 'A' + 10;}
*c <<= 4;
if (isdigit(qp_p[1])) {*c |= qp_p[1] - '0';}
- else {*c |= toupper(qp_p[1]) - 'A' + 10;};
+ else {*c |= toupper(qp_p[1]) - 'A' + 10;}
return qp_p + 2;
- };
+ }
/* Illegal char here */
*c = PDKIM_QP_ERROR_DECODE;
pub->srvtype = strdup(cur_val->str);
break;
case 't':
- if (strchr(cur_val->str,'t') != NULL) pub->testing = 1;
+ if (strchr(cur_val->str,'y') != NULL) pub->testing = 1;
if (strchr(cur_val->str,'s') != NULL) pub->no_subdomaining = 1;
break;
default:
/* -------------------------------------------------------------------------- */
-int pdkim_update_bodyhash(pdkim_ctx *ctx, char *data, int len) {
+int pdkim_update_bodyhash(pdkim_ctx *ctx, const char *data, int len) {
pdkim_signature *sig = ctx->sig;
/* Cache relaxed version of data */
char *relaxed_data = NULL;
/* Traverse all signatures, updating their hashes. */
while (sig != NULL) {
/* Defaults to simple canon (no further treatment necessary) */
- char *canon_data = data;
- int canon_len = len;
+ const char *canon_data = data;
+ int canon_len = len;
if (sig->canon_body == PDKIM_CANON_RELAXED) {
/* Relax the line if not done already */
if (relaxed_data == NULL) {
int seen_wsp = 0;
- char *p = data;
+ const char *p = data;
int q = 0;
relaxed_data = malloc(len+1);
if (relaxed_data == NULL) return PDKIM_ERR_OOM;
while (*p != '\0') {
char c = *p;
- if ( (c == '\t') || (c == ' ') ) {
+ if (c == '\r') {
+ if ( (q > 0) && (relaxed_data[q-1] == ' ') ) q--;
+ }
+ else if ( (c == '\t') || (c == ' ') ) {
c = ' '; /* Turns WSP into SP */
if (seen_wsp) {
p++;
if (relaxed_data != NULL) free(relaxed_data);
return PDKIM_OK;
-};
+}
/* -------------------------------------------------------------------------- */
}
return PDKIM_OK;
-};
+}
sig->hnames_check, 1) != PDKIM_OK) goto NEXT_SIG;
}
- /* Add header to the signed headers list */
- list = pdkim_append_stringlist(sig->headers,
- ctx->cur_header->str);
+ /* Add header to the signed headers list (in reverse order) */
+ list = pdkim_prepend_stringlist(sig->headers,
+ ctx->cur_header->str);
if (list == NULL) return PDKIM_ERR_OOM;
sig->headers = list;
ctx->sig = new_sig;
}
else {
- while (last_sig->next != NULL) { last_sig = last_sig->next; };
+ while (last_sig->next != NULL) { last_sig = last_sig->next; }
last_sig->next = new_sig;
}
}
BAIL:
pdkim_strclear(ctx->cur_header); /* Re-use existing pdkim_str */
return PDKIM_OK;
-};
+}
}
}
return PDKIM_OK;
-};
+}
/* -------------------------------------------------------------------------- */
/* Check if we must still flush a (partial) header. If that is the
case, the message has no body, and we must compute a body hash
out of '<CR><LF>' */
- if (ctx->cur_header->len) {
+ if (ctx->cur_header && ctx->cur_header->len) {
int rc = pdkim_header_complete(ctx);
if (rc != PDKIM_OK) return rc;
pdkim_update_bodyhash(ctx,"\r\n",2);
#ifdef PDKIM_DEBUG
if (ctx->debug_stream)
fprintf(ctx->debug_stream,
- "\nPDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
#endif
}
q = strchr(p,':');
if (q != NULL) *q = '\0';
while (hdrs != NULL) {
- if (strncasecmp(hdrs->value,p,strlen(p)) == 0) {
+ if ( (strncasecmp(hdrs->value,p,strlen(p)) == 0) &&
+ ((hdrs->value)[strlen(p)] == ':') ) {
char *rh = NULL;
if (sig->canon_headers == PDKIM_CANON_RELAXED)
rh = pdkim_relax_header(hdrs->value,1); /* cook header for relaxed canon */
pdkim_quoteprint(ctx->debug_stream, rh, strlen(rh), 1);
#endif
free(rh);
+ (hdrs->value)[0] = '_';
+ break;
}
hdrs = hdrs->next;
}
if (ctx->mode == PDKIM_MODE_SIGN) {
rsa_context rsa;
- rsa_init(&rsa,RSA_PKCS_V15,0,NULL,NULL);
+ rsa_init(&rsa,RSA_PKCS_V15,0);
/* Perform private key operation */
if (rsa_parse_key(&rsa, (unsigned char *)sig->rsa_privkey,
if (rsa_pkcs1_sign( &rsa, RSA_PRIVATE,
((sig->algo == PDKIM_ALGO_RSA_SHA1)?
- RSA_SHA1:RSA_SHA256),
+ SIG_RSA_SHA1:SIG_RSA_SHA256),
0,
(unsigned char *)headerhash,
(unsigned char *)sig->sigdata ) != 0) {
rsa_context rsa;
char *dns_txt_name, *dns_txt_reply;
- rsa_init(&rsa,RSA_PKCS_V15,0,NULL,NULL);
+ rsa_init(&rsa,RSA_PKCS_V15,0);
dns_txt_name = malloc(PDKIM_DNS_TXT_MAX_NAMELEN);
if (dns_txt_name == NULL) return PDKIM_ERR_OOM;
sig->verify_status = PDKIM_VERIFY_INVALID;
sig->verify_ext_status = PDKIM_VERIFY_INVALID_BUFFER_SIZE;
goto NEXT_VERIFY;
- };
+ }
if ((ctx->dns_txt_callback(dns_txt_name, dns_txt_reply) != PDKIM_OK) ||
(dns_txt_reply[0] == '\0')) {
if (rsa_pkcs1_verify(&rsa,
RSA_PUBLIC,
((sig->algo == PDKIM_ALGO_RSA_SHA1)?
- RSA_SHA1:RSA_SHA256),
+ SIG_RSA_SHA1:SIG_RSA_SHA256),
0,
(unsigned char *)headerhash,
(unsigned char *)sig->sigdata) != 0) {
sig->verify_status = PDKIM_VERIFY_FAIL;
sig->verify_ext_status = PDKIM_VERIFY_FAIL_MESSAGE;
- #ifdef PDKIM_DEBUG
- if (ctx->debug_stream) {
- fprintf(ctx->debug_stream, "PDKIM [%s] signature did NOT verify OK\n",
- sig->domain);
- }
- #endif
goto NEXT_VERIFY;
}
- /* We have a winner! */
- sig->verify_status = PDKIM_VERIFY_PASS;
+ /* We have a winner! (if bodydhash was correct earlier) */
+ if (sig->verify_status == PDKIM_VERIFY_NONE) {
+ sig->verify_status = PDKIM_VERIFY_PASS;
+ }
+
+ NEXT_VERIFY:
#ifdef PDKIM_DEBUG
if (ctx->debug_stream) {
- fprintf(ctx->debug_stream, "PDKIM [%s] signature verified OK\n",
- sig->domain);
+ fprintf(ctx->debug_stream, "PDKIM [%s] signature status: %s",
+ sig->domain, pdkim_verify_status_str(sig->verify_status));
+ if (sig->verify_ext_status > 0) {
+ fprintf(ctx->debug_stream, " (%s)\n",
+ pdkim_verify_ext_status_str(sig->verify_ext_status));
+ }
+ else {
+ fprintf(ctx->debug_stream, "\n");
+ }
}
#endif
- NEXT_VERIFY:
rsa_free(&rsa);
free(dns_txt_name);
free(dns_txt_reply);
sha2_starts(ctx->sig->sha2_body,0);
return ctx;
-};
+}
#ifdef PDKIM_DEBUG
/* -------------------------------------------------------------------------- */
DLLEXPORT void pdkim_set_debug_stream(pdkim_ctx *ctx,
FILE *debug_stream) {
ctx->debug_stream = debug_stream;
-};
+}
#endif
/* -------------------------------------------------------------------------- */
ctx->sig->expires = expires;
return PDKIM_OK;
-};
+}