uschar *dkim_exim_sign(int dkim_fd, uschar *dkim_private_key, uschar *dkim_domain, uschar *dkim_selector, uschar *dkim_canon, uschar *dkim_sign_headers) { int sep = 0; uschar *seen_items = NULL; int seen_items_size = 0; int seen_items_offset = 0; uschar itembuf[256]; uschar *dkim_canon_expanded; uschar *dkim_sign_headers_expanded; uschar *dkim_private_key_expanded; pdkim_ctx *ctx = NULL; uschar *rc = NULL; uschar *sigbuf = NULL; int sigsize = 0; int sigptr = 0; pdkim_signature *signature; int pdkim_canon; int pdkim_rc; int sread; char buf[4096]; int save_errno = 0; int old_pool = store_pool; store_pool = POOL_MAIN; dkim_domain = expand_string(dkim_domain); if (dkim_domain == NULL) { /* expansion error, do not send message. */ log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand " "dkim_domain: %s", expand_string_message); rc = NULL; goto CLEANUP; } /* Set $dkim_domain expansion variable to each unique domain in list. */ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, itembuf, sizeof(itembuf))) != NULL) { if (!dkim_signing_domain || (dkim_signing_domain[0] == '\0')) continue; /* Only sign once for each domain, no matter how often it appears in the expanded list. */ if (seen_items != NULL) { uschar *seen_items_list = seen_items; if (match_isinlist(dkim_signing_domain, &seen_items_list,0,NULL,NULL,MCL_STRING,TRUE,NULL) == OK) continue; seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,":"); } seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,dkim_signing_domain); seen_items[seen_items_offset] = '\0'; /* Set up $dkim_selector expansion variable. */ dkim_signing_selector = expand_string(dkim_selector); if (dkim_signing_selector == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand " "dkim_selector: %s", expand_string_message); rc = NULL; goto CLEANUP; } /* Get canonicalization to use */ dkim_canon_expanded = expand_string(dkim_canon?dkim_canon:US"relaxed"); if (dkim_canon_expanded == NULL) { /* expansion error, do not send message. */ log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand " "dkim_canon: %s", expand_string_message); rc = NULL; goto CLEANUP; } if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0) pdkim_canon = PDKIM_CANON_RELAXED; else if (Ustrcmp(dkim_canon_expanded, "simple") == 0) pdkim_canon = PDKIM_CANON_SIMPLE; else { log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon_expanded); pdkim_canon = PDKIM_CANON_RELAXED; } if (dkim_sign_headers) { dkim_sign_headers_expanded = expand_string(dkim_sign_headers); if (dkim_sign_headers_expanded == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand " "dkim_sign_headers: %s", expand_string_message); rc = NULL; goto CLEANUP; } } else { /* pass NULL, which means default header list */ dkim_sign_headers_expanded = NULL; } /* Get private key to use. */ dkim_private_key_expanded = expand_string(dkim_private_key); if (dkim_private_key_expanded == NULL) { log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand " "dkim_private_key: %s", expand_string_message); rc = NULL; goto CLEANUP; } if ( (Ustrlen(dkim_private_key_expanded) == 0) || (Ustrcmp(dkim_private_key_expanded,"0") == 0) || (Ustrcmp(dkim_private_key_expanded,"false") == 0) ) { /* don't sign, but no error */ continue; } if (dkim_private_key_expanded[0] == '/') { int privkey_fd = 0; /* Looks like a filename, load the private key. */ memset(big_buffer,0,big_buffer_size); privkey_fd = open(CS dkim_private_key_expanded,O_RDONLY); if (privkey_fd < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "unable to open " "private key file for reading: %s", dkim_private_key_expanded); rc = NULL; goto CLEANUP; } if (read(privkey_fd,big_buffer,(big_buffer_size-2)) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s", dkim_private_key_expanded); rc = NULL; goto CLEANUP; } (void)close(privkey_fd); dkim_private_key_expanded = big_buffer; } ctx = pdkim_init_sign(PDKIM_INPUT_SMTP, (char *)dkim_signing_domain, (char *)dkim_signing_selector, (char *)dkim_private_key_expanded ); pdkim_set_debug_stream(ctx,debug_file); pdkim_set_optional(ctx, (char *)dkim_sign_headers_expanded, NULL, pdkim_canon, pdkim_canon, -1, PDKIM_ALGO_RSA_SHA256, 0, 0); lseek(dkim_fd, 0, SEEK_SET); while((sread = read(dkim_fd,&buf,4096)) > 0) { if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) { rc = NULL; goto CLEANUP; } } /* Handle failed read above. */ if (sread == -1) { debug_printf("DKIM: Error reading -K file.\n"); save_errno = errno; rc = NULL; goto CLEANUP; } pdkim_rc = pdkim_feed_finish(ctx,&signature); if (pdkim_rc != PDKIM_OK) { log_write(0, LOG_MAIN|LOG_PANIC, "DKIM: signing failed (RC %d)", pdkim_rc); rc = NULL; goto CLEANUP; } sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2, US signature->signature_header, US"\r\n"); pdkim_free_ctx(ctx); ctx = NULL; } if (sigbuf != NULL) { sigbuf[sigptr] = '\0'; rc = sigbuf; } else rc = US""; CLEANUP: if (ctx != NULL) pdkim_free_ctx(ctx); store_pool = old_pool; errno = save_errno; return rc; }
void dkim_exim_verify_finish(void) { pdkim_signature *sig = NULL; int dkim_signers_size = 0; int dkim_signers_ptr = 0; dkim_signers = NULL; /* Delete eventual previous signature chain */ dkim_signatures = NULL; /* If we have arrived here with dkim_collect_input == FALSE, it means there was a processing error somewhere along the way. Log the incident and disable futher verification. */ if (!dkim_collect_input) { log_write(0, LOG_MAIN, "DKIM: Error while running this message through validation, disabling signature verification."); dkim_disable_verify = TRUE; return; } dkim_collect_input = FALSE; /* Finish DKIM operation and fetch link to signatures chain */ if (pdkim_feed_finish(dkim_verify_ctx,&dkim_signatures) != PDKIM_OK) return; sig = dkim_signatures; while (sig != NULL) { int size = 0; int ptr = 0; /* Log a line for each signature */ uschar *logmsg = string_append(NULL, &size, &ptr, 5, string_sprintf( "d=%s s=%s c=%s/%s a=%s ", sig->domain, sig->selector, (sig->canon_headers == PDKIM_CANON_SIMPLE)?"simple":"relaxed", (sig->canon_body == PDKIM_CANON_SIMPLE)?"simple":"relaxed", (sig->algo == PDKIM_ALGO_RSA_SHA256)?"rsa-sha256":"rsa-sha1" ), ((sig->identity != NULL)? string_sprintf("i=%s ", sig->identity) : US"" ), ((sig->created > 0)? string_sprintf("t=%lu ", sig->created) : US"" ), ((sig->expires > 0)? string_sprintf("x=%lu ", sig->expires) : US"" ), ((sig->bodylength > -1)? string_sprintf("l=%lu ", sig->bodylength) : US"" ) ); switch(sig->verify_status) { case PDKIM_VERIFY_NONE: logmsg = string_append(logmsg, &size, &ptr, 1, "[not verified]"); break; case PDKIM_VERIFY_INVALID: logmsg = string_append(logmsg, &size, &ptr, 1, "[invalid - "); switch (sig->verify_ext_status) { case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: logmsg = string_append(logmsg, &size, &ptr, 1, "public key record (currently?) unavailable]"); break; case PDKIM_VERIFY_INVALID_BUFFER_SIZE: logmsg = string_append(logmsg, &size, &ptr, 1, "overlong public key record]"); break; case PDKIM_VERIFY_INVALID_PUBKEY_PARSING: logmsg = string_append(logmsg, &size, &ptr, 1, "syntax error in public key record]"); break; default: logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified problem]"); } break; case PDKIM_VERIFY_FAIL: logmsg = string_append(logmsg, &size, &ptr, 1, "[verification failed - "); switch (sig->verify_ext_status) { case PDKIM_VERIFY_FAIL_BODY: logmsg = string_append(logmsg, &size, &ptr, 1, "body hash mismatch (body probably modified in transit)]"); break; case PDKIM_VERIFY_FAIL_MESSAGE: logmsg = string_append(logmsg, &size, &ptr, 1, "signature did not verify (headers probably modified in transit)]"); break; default: logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]"); } break; case PDKIM_VERIFY_PASS: logmsg = string_append(logmsg, &size, &ptr, 1, "[verification succeeded]"); break; } logmsg[ptr] = '\0'; log_write(0, LOG_MAIN, "DKIM: %s", logmsg); /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */ dkim_signers = string_append(dkim_signers, &dkim_signers_size, &dkim_signers_ptr, 2, sig->domain, ":" ); if (sig->identity != NULL) { dkim_signers = string_append(dkim_signers, &dkim_signers_size, &dkim_signers_ptr, 2, sig->identity, ":" ); } /* Process next signature */ sig = sig->next; } /* NULL-terminate and chop the last colon from the domain list */ if (dkim_signers != NULL) { dkim_signers[dkim_signers_ptr] = '\0'; if (Ustrlen(dkim_signers) > 0) dkim_signers[Ustrlen(dkim_signers)-1] = '\0'; } }
int main(int argc, char *argv[]) { FILE *debug; int i; pdkim_ctx *ctx; pdkim_signature *signature; /* pdkim_ctx *pdkim_init_sign(int mode, * char *domain, * char *selector, * char *rsa_privkey) * * Initialize context for signing. * * int mode * PDKIM_INPUT_NORMAL or PDKIM_INPUT_SMTP. When SMTP * input is used, the lib will deflate double-dots at * the start of atline to a single dot, and it will * stop processing input when a line with and single * dot is received (Excess input will simply be ignored). * * char *domain * The domain to sign as. This value will land in the * d= tag of the signature. * * char *selector * The selector string to use. This value will land in * the s= tag of the signature. * * char *rsa_privkey * The private RSA key, in ASCII armor. It MUST NOT be * encrypted. * * Returns: A pointer to a freshly allocated pdkim_ctx * context. */ ctx = pdkim_init_sign(PDKIM_INPUT_NORMAL, /* Input type */ DOMAIN, /* Domain */ SELECTOR, /* Selector */ RSA_PRIVKEY /* Private RSA key */ ); /* void pdkim_set_debug_stream(pdkim_ctx *ctx, * FILE *debug) * * Set up debugging stream. * * When PDKIM was compiled with DEBUG defined (which is the * recommended default), you can set up a stream where it * sends debug output to. In this example, we simply use * STDERR (fd 2) for that purpose. If you don't set a debug * stream, no debug output is generated. */ debug = fdopen(2,"a"); pdkim_set_debug_stream(ctx,debug); /* int pdkim_set_optional(pdkim_ctx *ctx, * char *sign_headers, * char *identity, * int canon_headers, * int canon_body, * long bodylength, * int algo, * unsigned long created, * unsigned long expires) * * OPTIONAL: Set additional optional signing options. If you do * not use this function, sensible defaults (see below) are used. * Any strings you pass in are dup'ed, so you can safely release * your copy even before calling pdkim_free() on your context. * * char *sign_headers (default NULL) * Colon-separated list of header names. Headers with * a name matching the list will be included in the * signature. When this is NULL, the list of headers * recommended in RFC4781 will be used. * * char *identity (default NULL) * An identity string as described in RFC4781. It will * be put into the i= tag of the signature. * * int canon_headers (default PDKIM_CANON_SIMPLE) * Canonicalization algorithm to use for headers. One * of PDKIM_CANON_SIMPLE or PDKIM_CANON_RELAXED. * * int canon_body (default PDKIM_CANON_SIMPLE) * Canonicalization algorithm to use for the body. One * of PDKIM_CANON_SIMPLE or PDKIM_CANON_RELAXED. * * long bodylength (default -1) * Amount of canonicalized body bytes to include in * the body hash calculation. A value of 0 means that * the body is not included in the signature. A value * of -1 (the default) means that there is no limit. * * int algo (default PDKIM_ALGO_RSA_SHA256) * One of PDKIM_ALGO_RSA_SHA256 or PDKIM_ALGO_RSA_SHA1. * * unsigned long created (default 0) * Seconds since the epoch, describing when the signature * was created. This is copied to the t= tag of the * signature. Setting a value of 0 (the default) omits * the tag from the signature. * * unsigned long expires (default 0) * Seconds since the epoch, describing when the signature * expires. This is copied to the x= tag of the * signature. Setting a value of 0 (the default) omits * the tag from the signature. * * Returns: 0 (PDKIM_OK) for success or a PDKIM_ERR_* constant */ pdkim_set_optional(ctx, NULL, NULL, PDKIM_CANON_SIMPLE, PDKIM_CANON_SIMPLE, -1, PDKIM_ALGO_RSA_SHA256, 0, 0); /* int pdkim_feed(pdkim_ctx *ctx, * char *data, * int data_len) * * (Repeatedly) feed data to the signing algorithm. The message * data MUST use CRLF line endings (like SMTP uses on the * wire). The data chunks do not need to be a "line" - you * can split chunks at arbitrary locations. * * char *data * Pointer to data to feed. Please note that despite * the example given below, this is not necessarily a * C string. * * int data_len * Length of data being fed, in bytes. * * Returns: 0 (PDKIM_OK) for success or a PDKIM_ERR_* constant */ i = 0; while (test_message[i] != NULL) { if (pdkim_feed(ctx, test_message[i], strlen(test_message[i])) != PDKIM_OK) { printf("pdkim_feed() error\n"); goto BAIL; } i++; } /* int pdkim_feed_finish(pdkim_ctx *ctx, * pdkim_signature **signature, * * Signal end-of-message and retrieve the signature block. * * pdkim_signature **signature * Pass in a pointer to a pdkim_signature pointer. * If the function returns PDKIM_OK, it will be set * up to point to a freshly allocated pdkim_signature * block. See pdkim.h for documentation on what that * block contains. Hint: Most implementations will * simply want to retrieve a ready-to-use * DKIM-Signature header, which can be found in * *signature->signature_header. See the code below. * * Returns: 0 (PDKIM_OK) for success or a PDKIM_ERR_* constant */ if (pdkim_feed_finish(ctx,&signature) == PDKIM_OK) { /* Print signature to STDOUT, followed by the original * message. We can then pipe the output directly to * test_verify.c. */ printf(signature->signature_header); printf("\r\n"); i = 0; while (test_message[i] != NULL) { printf(test_message[i]); i++; } } BAIL: /* void pdkim_free_ctx(pdkim_ctx *ctx) * * Free all allocated memory blocks referenced from * the context, as well as the context itself. */ pdkim_free_ctx(ctx); fclose(debug); }