Add some more glue code for the DKIM acl
[users/jgh/exim.git] / src / src / dkim.c
1 /* $Cambridge: exim/src/src/dkim.c,v 1.1.2.13 2009/05/27 17:26:54 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   int dkim_signing_domains_size = 0;
83   int dkim_signing_domains_ptr = 0;
84   dkim_signing_domains = NULL;
85
86   /* Delete eventual previous signature chain */
87   dkim_signatures = NULL;
88
89   /* If we have arrived here with dkim_collect_input == FALSE, it
90      means there was a processing error somewhere along the way.
91      Log the incident and disable futher verification. */
92   if (!dkim_collect_input) {
93     log_write(0, LOG_MAIN|LOG_PANIC, "DKIM: Error while running this message through validation, disabling signature verification.");
94     dkim_disable_verify = TRUE;
95     return;
96   }
97   dkim_collect_input = FALSE;
98
99   /* Finish DKIM operation and fetch link to signatures chain */
100   if (pdkim_feed_finish(dkim_verify_ctx,&dkim_signatures) != PDKIM_OK) return;
101
102
103   while (dkim_signatures != NULL) {
104     int size = 0;
105     int ptr = 0;
106     /* Log a line for each signature */
107     uschar *logmsg = string_append(NULL, &size, &ptr, 5,
108
109       string_sprintf( "DKIM: d=%s s=%s c=%s/%s a=%s ",
110                       dkim_signatures->domain,
111                       dkim_signatures->selector,
112                       (dkim_signatures->canon_headers == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
113                       (dkim_signatures->canon_body    == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
114                       (dkim_signatures->algo          == PDKIM_ALGO_RSA_SHA256)?"rsa-sha256":"rsa-sha1"
115                     ),
116       ((dkim_signatures->identity != NULL)?
117         string_sprintf("i=%s ", dkim_signatures->identity)
118         :
119         US""
120       ),
121       ((dkim_signatures->created > 0)?
122         string_sprintf("t=%lu ", dkim_signatures->created)
123         :
124         US""
125       ),
126       ((dkim_signatures->expires > 0)?
127         string_sprintf("x=%lu ", dkim_signatures->expires)
128         :
129         US""
130       ),
131       ((dkim_signatures->bodylength > -1)?
132         string_sprintf("l=%lu ", dkim_signatures->bodylength)
133         :
134         US""
135       )
136     );
137
138     switch(dkim_signatures->verify_status) {
139       case PDKIM_VERIFY_NONE:
140         logmsg = string_append(logmsg, &size, &ptr, 1, "[not verified]");
141       break;
142       case PDKIM_VERIFY_INVALID:
143         logmsg = string_append(logmsg, &size, &ptr, 1, "[invalid - ");
144         switch (dkim_signatures->verify_ext_status) {
145           case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
146             logmsg = string_append(logmsg, &size, &ptr, 1, "public key record (currently?) unavailable]");
147           break;
148           case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
149             logmsg = string_append(logmsg, &size, &ptr, 1, "overlong public key record]");
150           break;
151           case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
152             logmsg = string_append(logmsg, &size, &ptr, 1, "syntax error in public key record]");
153           break;
154           default:
155             logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified problem]");
156         }
157       break;
158       case PDKIM_VERIFY_FAIL:
159         logmsg = string_append(logmsg, &size, &ptr, 1, "[verification failed - ");
160         switch (dkim_signatures->verify_ext_status) {
161           case PDKIM_VERIFY_FAIL_BODY:
162             logmsg = string_append(logmsg, &size, &ptr, 1, "body hash mismatch (body probably modified in transit)]");
163           break;
164           case PDKIM_VERIFY_FAIL_MESSAGE:
165             logmsg = string_append(logmsg, &size, &ptr, 1, "signature did not verify (headers probably modified in transit)]");
166           break;
167           default:
168             logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
169         }
170       break;
171       case PDKIM_VERIFY_PASS:
172         logmsg = string_append(logmsg, &size, &ptr, 1, "[verification succeeded]");
173       break;
174     }
175
176     logmsg[ptr] = '\0';
177     log_write(0, LOG_MAIN, (char *)logmsg);
178
179     /* Build a colon-separated list of signing domains in dkim_signing_domains */
180     dkim_signing_domains = string_append(dkim_signing_domains,
181                                          &dkim_signing_domains_size,
182                                          &dkim_signing_domains_ptr,
183                                          2,
184                                          dkim_signatures->domain,
185                                          ":")
186                                         );
187
188     /* Process next signature */
189     dkim_signatures = dkim_signatures->next;
190   }
191
192   /* Chop the last colon from the domain list */
193   if ((dkim_signing_domains != NULL) &&
194       (Ustrlen(dkim_signing_domains) > 0))
195     dkim_signing_domains[strlen(dkim_signing_domains)-1] = '\0';
196 }
197
198
199 void dkim_exim_verify_result(uschar *domain, uschar **result, uschar **error) {
200   if (dkim_verify_ctx) {
201
202   }
203 }
204
205
206 uschar *dkim_exim_sign(int dkim_fd,
207                        uschar *dkim_private_key,
208                        uschar *dkim_domain,
209                        uschar *dkim_selector,
210                        uschar *dkim_canon,
211                        uschar *dkim_sign_headers) {
212   pdkim_ctx *ctx = NULL;
213   uschar *rc = NULL;
214   pdkim_signature *signature;
215   int pdkim_canon;
216   int sread;
217   char buf[4096];
218   int save_errno = 0;
219   int old_pool = store_pool;
220
221   dkim_domain = expand_string(dkim_domain);
222   if (dkim_domain == NULL) {
223     /* expansion error, do not send message. */
224     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
225           "dkim_domain: %s", expand_string_message);
226     rc = NULL;
227     goto CLEANUP;
228   }
229   /* Set up $dkim_domain expansion variable. */
230   dkim_signing_domain = dkim_domain;
231
232   /* Get selector to use. */
233   dkim_selector = expand_string(dkim_selector);
234   if (dkim_selector == NULL) {
235     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
236       "dkim_selector: %s", expand_string_message);
237     rc = NULL;
238     goto CLEANUP;
239   }
240   /* Set up $dkim_selector expansion variable. */
241   dkim_signing_selector = dkim_selector;
242
243   /* Get canonicalization to use */
244   dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
245   if (dkim_canon == NULL) {
246     /* expansion error, do not send message. */
247     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
248           "dkim_canon: %s", expand_string_message);
249     rc = NULL;
250     goto CLEANUP;
251   }
252   if (Ustrcmp(dkim_canon, "relaxed") == 0)
253     pdkim_canon = PDKIM_CANON_RELAXED;
254   else if (Ustrcmp(dkim_canon, "simple") == 0)
255     pdkim_canon = PDKIM_CANON_RELAXED;
256   else {
257     log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
258     pdkim_canon = PDKIM_CANON_RELAXED;
259   }
260
261   /* Expand signing headers once */
262   if (dkim_sign_headers != NULL) {
263     dkim_sign_headers = expand_string(dkim_sign_headers);
264     if (dkim_sign_headers == NULL) {
265       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
266         "dkim_sign_headers: %s", expand_string_message);
267       rc = NULL;
268       goto CLEANUP;
269     }
270   }
271
272   /* Get private key to use. */
273   dkim_private_key = expand_string(dkim_private_key);
274   if (dkim_private_key == NULL) {
275     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
276       "dkim_private_key: %s", expand_string_message);
277     rc = NULL;
278     goto CLEANUP;
279   }
280   if ( (Ustrlen(dkim_private_key) == 0) ||
281        (Ustrcmp(dkim_private_key,"0") == 0) ||
282        (Ustrcmp(dkim_private_key,"false") == 0) ) {
283     /* don't sign, but no error */
284     rc = US"";
285     goto CLEANUP;
286   }
287
288   if (dkim_private_key[0] == '/') {
289     int privkey_fd = 0;
290     /* Looks like a filename, load the private key. */
291     memset(big_buffer,0,big_buffer_size);
292     privkey_fd = open(CS dkim_private_key,O_RDONLY);
293     (void)read(privkey_fd,big_buffer,16383);
294     (void)close(privkey_fd);
295     dkim_private_key = big_buffer;
296   }
297
298   ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
299                         (char *)dkim_signing_domain,
300                         (char *)dkim_signing_selector,
301                         (char *)dkim_private_key
302                        );
303
304   pdkim_set_debug_stream(ctx,debug_file);
305
306   pdkim_set_optional(ctx,
307                      (char *)dkim_sign_headers,
308                      NULL,
309                      pdkim_canon,
310                      pdkim_canon,
311                      -1,
312                      PDKIM_ALGO_RSA_SHA256,
313                      0,
314                      0);
315
316   while((sread = read(dkim_fd,&buf,4096)) > 0) {
317     if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
318       rc = NULL;
319       goto CLEANUP;
320     }
321   }
322   /* Handle failed read above. */
323   if (sread == -1) {
324     debug_printf("DKIM: Error reading -K file.\n");
325     save_errno = errno;
326     rc = NULL;
327     goto CLEANUP;
328   }
329
330   if (pdkim_feed_finish(ctx,&signature) != PDKIM_OK)
331     goto CLEANUP;
332
333   rc = store_get(strlen(signature->signature_header)+3);
334   Ustrcpy(rc,US signature->signature_header);
335   Ustrcat(rc,US"\r\n");
336
337   CLEANUP:
338   if (ctx != NULL) {
339     pdkim_free_ctx(ctx);
340   }
341   store_pool = old_pool;
342   errno = save_errno;
343   return rc;
344 };
345
346 #endif