More DKIM wip. I now have a plan, and we are slowly getting there ...
[users/jgh/exim.git] / src / src / dkim.c
1 /* $Cambridge: exim/src/src/dkim.c,v 1.1.2.12 2009/05/20 14:30:14 tom Exp $ */
2
3 /*************************************************
4 *     Exim - an Internet mail transport agent    *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge 2009 */
8 /* See the file NOTICE for conditions of use and distribution. */
9
10 /* Code for DKIM support. Other DKIM relevant code is in
11    receive.c, transport.c and transports/smtp.c */
12
13 #include "exim.h"
14
15 #ifndef DISABLE_DKIM
16
17 #include "pdkim/pdkim.h"
18
19 pdkim_ctx       *dkim_verify_ctx = NULL;
20 pdkim_signature *dkim_signatures = NULL;
21
22 int dkim_exim_query_dns_txt(char *name, char *answer) {
23   dns_answer dnsa;
24   dns_scan   dnss;
25   dns_record *rr;
26
27   if (dns_lookup(&dnsa, (uschar *)name, T_TXT, NULL) != DNS_SUCCEED) return PDKIM_FAIL;
28
29   /* Search for TXT record */
30   for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
31        rr != NULL;
32        rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
33     if (rr->type == T_TXT) break;
34
35   /* Copy record content to the answer buffer */
36   if (rr != NULL) {
37     int rr_offset = 0;
38     int answer_offset = 0;
39     while (rr_offset < rr->size) {
40       uschar len = (rr->data)[rr_offset++];
41       snprintf(answer+(answer_offset),
42                PDKIM_DNS_TXT_MAX_RECLEN-(answer_offset),
43                "%.*s", (int)len, (char *)((rr->data)+rr_offset));
44       rr_offset+=len;
45       answer_offset+=len;
46     }
47   }
48   else return PDKIM_FAIL;
49
50   return PDKIM_OK;
51 }
52
53
54 void dkim_exim_verify_init(void) {
55
56   /* Free previous context if there is one */
57   if (dkim_verify_ctx) pdkim_free_ctx(dkim_verify_ctx);
58
59   /* Create new context */
60   dkim_verify_ctx = pdkim_init_verify(PDKIM_INPUT_SMTP,
61                                       &dkim_exim_query_dns_txt
62                                      );
63
64   if (dkim_verify_ctx != NULL) {
65     dkim_collect_input = TRUE;
66     pdkim_set_debug_stream(dkim_verify_ctx,debug_file);
67   }
68   else dkim_collect_input = FALSE;
69
70 }
71
72
73 void dkim_exim_verify_feed(uschar *data, int len) {
74   if (dkim_collect_input &&
75       pdkim_feed(dkim_verify_ctx,
76                  (char *)data,
77                  len) != PDKIM_OK) dkim_collect_input = FALSE;
78 }
79
80
81 void dkim_exim_verify_finish(void) {
82
83   /* Delete eventual previous signature chain */
84   dkim_signatures = NULL;
85
86   /* If we have arrived here with dkim_collect_input == FALSE, it
87      means there was a processing error somewhere along the way.
88      Log the incident and disable futher verification. */
89   if (!dkim_collect_input) {
90     log_write(0, LOG_MAIN|LOG_PANIC, "DKIM: Error while running this message through validation, disabling signature verification.");
91     dkim_disable_verify = TRUE;
92     return;
93   }
94   dkim_collect_input = FALSE;
95
96   /* Finish DKIM operation and fetch link to signatures chain */
97   if (pdkim_feed_finish(dkim_verify_ctx,&dkim_signatures) != PDKIM_OK) return;
98
99   /* Log a line for each signature */
100   while (dkim_signatures != NULL) {
101     int size = 0;
102     int ptr = 0;
103     uschar *logmsg = string_append(NULL, &size, &ptr, 5,
104
105       string_sprintf( "DKIM: d=%s s=%s c=%s/%s a=%s ",
106                       dkim_signatures->domain,
107                       dkim_signatures->selector,
108                       (dkim_signatures->canon_headers == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
109                       (dkim_signatures->canon_body    == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
110                       (dkim_signatures->algo          == PDKIM_ALGO_RSA_SHA256)?"rsa-sha256":"rsa-sha1"
111                     ),
112
113       ((dkim_signatures->identity != NULL)?
114         string_sprintf("i=%s ", dkim_signatures->identity)
115         :
116         US""
117       ),
118       ((dkim_signatures->created > 0)?
119         string_sprintf("t=%lu ", dkim_signatures->created)
120         :
121         US""
122       ),
123       ((dkim_signatures->expires > 0)?
124         string_sprintf("x=%lu ", dkim_signatures->expires)
125         :
126         US""
127       ),
128       ((dkim_signatures->bodylength > -1)?
129         string_sprintf("l=%lu ", dkim_signatures->bodylength)
130         :
131         US""
132       )
133     );
134
135     switch(dkim_signatures->verify_status) {
136       case PDKIM_VERIFY_NONE:
137         logmsg = string_append(logmsg, &size, &ptr, 1, "[not verified]");
138       break;
139       case PDKIM_VERIFY_INVALID:
140         logmsg = string_append(logmsg, &size, &ptr, 1, "[invalid - ");
141         switch (dkim_signatures->verify_ext_status) {
142           case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
143             logmsg = string_append(logmsg, &size, &ptr, 1, "public key record (currently?) unavailable]");
144           break;
145           case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
146             logmsg = string_append(logmsg, &size, &ptr, 1, "overlong public key record]");
147           break;
148           case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
149             logmsg = string_append(logmsg, &size, &ptr, 1, "syntax error in public key record]");
150           break;
151           default:
152             logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified problem]");
153         }
154       break;
155       case PDKIM_VERIFY_FAIL:
156         logmsg = string_append(logmsg, &size, &ptr, 1, "[verification failed - ");
157         switch (dkim_signatures->verify_ext_status) {
158           case PDKIM_VERIFY_FAIL_BODY:
159             logmsg = string_append(logmsg, &size, &ptr, 1, "body hash mismatch (body probably modified in transit)]");
160           break;
161           case PDKIM_VERIFY_FAIL_MESSAGE:
162             logmsg = string_append(logmsg, &size, &ptr, 1, "signature did not verify (headers probably modified in transit)]");
163           break;
164           default:
165             logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
166         }
167       break;
168       case PDKIM_VERIFY_PASS:
169         logmsg = string_append(logmsg, &size, &ptr, 1, "[verification succeeded]");
170       break;
171     }
172
173     logmsg[ptr] = '\0';
174     log_write(0, LOG_MAIN, (char *)logmsg);
175
176     /* Log next signature */
177     dkim_signatures = dkim_signatures->next;
178   }
179 }
180
181
182 void dkim_exim_verify_result(uschar *domain, uschar **result, uschar **error) {
183   if (dkim_verify_ctx) {
184
185   }
186 }
187
188
189 uschar *dkim_exim_sign(int dkim_fd,
190                        uschar *dkim_private_key,
191                        uschar *dkim_domain,
192                        uschar *dkim_selector,
193                        uschar *dkim_canon,
194                        uschar *dkim_sign_headers) {
195   pdkim_ctx *ctx = NULL;
196   uschar *rc = NULL;
197   pdkim_signature *signature;
198   int pdkim_canon;
199   int sread;
200   char buf[4096];
201   int save_errno = 0;
202   int old_pool = store_pool;
203
204   dkim_domain = expand_string(dkim_domain);
205   if (dkim_domain == NULL) {
206     /* expansion error, do not send message. */
207     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
208           "dkim_domain: %s", expand_string_message);
209     rc = NULL;
210     goto CLEANUP;
211   }
212   /* Set up $dkim_domain expansion variable. */
213   dkim_signing_domain = dkim_domain;
214
215   /* Get selector to use. */
216   dkim_selector = expand_string(dkim_selector);
217   if (dkim_selector == NULL) {
218     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
219       "dkim_selector: %s", expand_string_message);
220     rc = NULL;
221     goto CLEANUP;
222   }
223   /* Set up $dkim_selector expansion variable. */
224   dkim_signing_selector = dkim_selector;
225
226   /* Get canonicalization to use */
227   dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
228   if (dkim_canon == NULL) {
229     /* expansion error, do not send message. */
230     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
231           "dkim_canon: %s", expand_string_message);
232     rc = NULL;
233     goto CLEANUP;
234   }
235   if (Ustrcmp(dkim_canon, "relaxed") == 0)
236     pdkim_canon = PDKIM_CANON_RELAXED;
237   else if (Ustrcmp(dkim_canon, "simple") == 0)
238     pdkim_canon = PDKIM_CANON_RELAXED;
239   else {
240     log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
241     pdkim_canon = PDKIM_CANON_RELAXED;
242   }
243
244   /* Expand signing headers once */
245   if (dkim_sign_headers != NULL) {
246     dkim_sign_headers = expand_string(dkim_sign_headers);
247     if (dkim_sign_headers == NULL) {
248       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
249         "dkim_sign_headers: %s", expand_string_message);
250       rc = NULL;
251       goto CLEANUP;
252     }
253   }
254
255   /* Get private key to use. */
256   dkim_private_key = expand_string(dkim_private_key);
257   if (dkim_private_key == NULL) {
258     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
259       "dkim_private_key: %s", expand_string_message);
260     rc = NULL;
261     goto CLEANUP;
262   }
263   if ( (Ustrlen(dkim_private_key) == 0) ||
264        (Ustrcmp(dkim_private_key,"0") == 0) ||
265        (Ustrcmp(dkim_private_key,"false") == 0) ) {
266     /* don't sign, but no error */
267     rc = US"";
268     goto CLEANUP;
269   }
270
271   if (dkim_private_key[0] == '/') {
272     int privkey_fd = 0;
273     /* Looks like a filename, load the private key. */
274     memset(big_buffer,0,big_buffer_size);
275     privkey_fd = open(CS dkim_private_key,O_RDONLY);
276     (void)read(privkey_fd,big_buffer,16383);
277     (void)close(privkey_fd);
278     dkim_private_key = big_buffer;
279   }
280
281   ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
282                         (char *)dkim_signing_domain,
283                         (char *)dkim_signing_selector,
284                         (char *)dkim_private_key
285                        );
286
287   pdkim_set_debug_stream(ctx,debug_file);
288
289   pdkim_set_optional(ctx,
290                      (char *)dkim_sign_headers,
291                      NULL,
292                      pdkim_canon,
293                      pdkim_canon,
294                      -1,
295                      PDKIM_ALGO_RSA_SHA256,
296                      0,
297                      0);
298
299   while((sread = read(dkim_fd,&buf,4096)) > 0) {
300     if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
301       rc = NULL;
302       goto CLEANUP;
303     }
304   }
305   /* Handle failed read above. */
306   if (sread == -1) {
307     debug_printf("DKIM: Error reading -K file.\n");
308     save_errno = errno;
309     rc = NULL;
310     goto CLEANUP;
311   }
312
313   if (pdkim_feed_finish(ctx,&signature) != PDKIM_OK)
314     goto CLEANUP;
315
316   rc = store_get(strlen(signature->signature_header)+3);
317   Ustrcpy(rc,US signature->signature_header);
318   Ustrcat(rc,US"\r\n");
319
320   CLEANUP:
321   if (ctx != NULL) {
322     pdkim_free_ctx(ctx);
323   }
324   store_pool = old_pool;
325   errno = save_errno;
326   return rc;
327 };
328
329 #endif