dd8334b634423d221014cbeb5b2a914ccc4d5ee7
[users/jgh/exim.git] / src / src / dkim.c
1 /* $Cambridge: exim/src/src/dkim.c,v 1.1.2.11 2009/05/19 09:49: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 1;
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 1;
49
50   return PDKIM_OK;
51 }
52
53
54 int 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 = 1;
66     pdkim_set_debug_stream(dkim_verify_ctx,debug_file);
67     return 1;
68   }
69   else {
70     dkim_collect_input = 0;
71     return 0;
72   }
73 }
74
75
76 int dkim_exim_verify_feed(uschar *data, int len) {
77   if (pdkim_feed(dkim_verify_ctx,
78                  (char *)data,
79                  len) != PDKIM_OK) return 0;
80   return 1;
81 }
82
83
84 int dkim_exim_verify_finish(void) {
85   dkim_signatures = NULL;
86   dkim_collect_input = 0;
87   if (pdkim_feed_finish(dkim_verify_ctx,&dkim_signatures) != PDKIM_OK) return 0;
88
89   while (dkim_signatures != NULL) {
90     int size = 0;
91     int ptr = 0;
92     uschar *logmsg = string_append(NULL, &size, &ptr, 5,
93
94       string_sprintf( "DKIM: v=%u d=%s s=%s c=%s/%s a=%s ",
95                       dkim_signatures->version,
96                       dkim_signatures->domain,
97                       dkim_signatures->selector,
98                       (dkim_signatures->canon_headers == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
99                       (dkim_signatures->canon_body    == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
100                       (dkim_signatures->algo          == PDKIM_ALGO_RSA_SHA256)?"rsa-sha256":"rsa-sha1"
101                     ),
102
103       ((dkim_signatures->identity != NULL)?
104         string_sprintf("i=%s ", dkim_signatures->identity)
105         :
106         US""
107       ),
108       ((dkim_signatures->created > 0)?
109         string_sprintf("t=%lu ", dkim_signatures->created)
110         :
111         US""
112       ),
113       ((dkim_signatures->expires > 0)?
114         string_sprintf("x=%lu ", dkim_signatures->expires)
115         :
116         US""
117       ),
118       ((dkim_signatures->bodylength > -1)?
119         string_sprintf("l=%lu ", dkim_signatures->bodylength)
120         :
121         US""
122       )
123     );
124
125     switch(dkim_signatures->verify_status) {
126       case PDKIM_VERIFY_NONE:
127         logmsg = string_append(logmsg, &size, &ptr, 1, "[not verified]");
128       break;
129       case PDKIM_VERIFY_INVALID:
130         logmsg = string_append(logmsg, &size, &ptr, 1, "[invalid - ");
131         switch (dkim_signatures->verify_ext_status) {
132           case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
133             logmsg = string_append(logmsg, &size, &ptr, 1, "public key record (currently?) unavailable]");
134           break;
135           case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
136             logmsg = string_append(logmsg, &size, &ptr, 1, "overlong public key record]");
137           break;
138           case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
139             logmsg = string_append(logmsg, &size, &ptr, 1, "syntax error in public key record]");
140           break;
141           default:
142             logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified problem]");
143         }
144       break;
145       case PDKIM_VERIFY_FAIL:
146         logmsg = string_append(logmsg, &size, &ptr, 1, "[verification failed - ");
147         switch (dkim_signatures->verify_ext_status) {
148           case PDKIM_VERIFY_FAIL_BODY:
149             logmsg = string_append(logmsg, &size, &ptr, 1, "body hash mismatch (body probably modified in transit)]");
150           break;
151           case PDKIM_VERIFY_FAIL_MESSAGE:
152             logmsg = string_append(logmsg, &size, &ptr, 1, "signature did not verify (headers probably modified in transit)]");
153           break;
154           default:
155             logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
156         }
157       break;
158       case PDKIM_VERIFY_PASS:
159         logmsg = string_append(logmsg, &size, &ptr, 1, "[verification succeeded]");
160       break;
161     }
162
163     logmsg[ptr] = '\0';
164     log_write(0, LOG_MAIN, (char *)logmsg);
165
166     /* Try next signature */
167     dkim_signatures = dkim_signatures->next;
168   }
169
170   return dkim_signatures?1:0;
171 }
172
173
174 int dkim_exim_verify_result(uschar *domain, uschar **result, uschar **error) {
175
176   if (dkim_verify_ctx) {
177
178   }
179
180   return OK;
181 }
182
183
184 uschar *dkim_exim_sign(int dkim_fd,
185                        uschar *dkim_private_key,
186                        uschar *dkim_domain,
187                        uschar *dkim_selector,
188                        uschar *dkim_canon,
189                        uschar *dkim_sign_headers) {
190   pdkim_ctx *ctx = NULL;
191   uschar *rc = NULL;
192   pdkim_signature *signature;
193   int pdkim_canon;
194   int sread;
195   char buf[4096];
196   int save_errno = 0;
197   int old_pool = store_pool;
198
199   dkim_domain = expand_string(dkim_domain);
200   if (dkim_domain == NULL) {
201     /* expansion error, do not send message. */
202     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
203           "dkim_domain: %s", expand_string_message);
204     rc = NULL;
205     goto CLEANUP;
206   }
207   /* Set up $dkim_domain expansion variable. */
208   dkim_signing_domain = dkim_domain;
209
210   /* Get selector to use. */
211   dkim_selector = expand_string(dkim_selector);
212   if (dkim_selector == NULL) {
213     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
214       "dkim_selector: %s", expand_string_message);
215     rc = NULL;
216     goto CLEANUP;
217   }
218   /* Set up $dkim_selector expansion variable. */
219   dkim_signing_selector = dkim_selector;
220
221   /* Get canonicalization to use */
222   dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
223   if (dkim_canon == NULL) {
224     /* expansion error, do not send message. */
225     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
226           "dkim_canon: %s", expand_string_message);
227     rc = NULL;
228     goto CLEANUP;
229   }
230   if (Ustrcmp(dkim_canon, "relaxed") == 0)
231     pdkim_canon = PDKIM_CANON_RELAXED;
232   else if (Ustrcmp(dkim_canon, "simple") == 0)
233     pdkim_canon = PDKIM_CANON_RELAXED;
234   else {
235     log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
236     pdkim_canon = PDKIM_CANON_RELAXED;
237   }
238
239   /* Expand signing headers once */
240   if (dkim_sign_headers != NULL) {
241     dkim_sign_headers = expand_string(dkim_sign_headers);
242     if (dkim_sign_headers == NULL) {
243       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
244         "dkim_sign_headers: %s", expand_string_message);
245       rc = NULL;
246       goto CLEANUP;
247     }
248   }
249
250   /* Get private key to use. */
251   dkim_private_key = expand_string(dkim_private_key);
252   if (dkim_private_key == NULL) {
253     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
254       "dkim_private_key: %s", expand_string_message);
255     rc = NULL;
256     goto CLEANUP;
257   }
258   if ( (Ustrlen(dkim_private_key) == 0) ||
259        (Ustrcmp(dkim_private_key,"0") == 0) ||
260        (Ustrcmp(dkim_private_key,"false") == 0) ) {
261     /* don't sign, but no error */
262     rc = US"";
263     goto CLEANUP;
264   }
265
266   if (dkim_private_key[0] == '/') {
267     int privkey_fd = 0;
268     /* Looks like a filename, load the private key. */
269     memset(big_buffer,0,big_buffer_size);
270     privkey_fd = open(CS dkim_private_key,O_RDONLY);
271     (void)read(privkey_fd,big_buffer,16383);
272     (void)close(privkey_fd);
273     dkim_private_key = big_buffer;
274   }
275
276   ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
277                         (char *)dkim_signing_domain,
278                         (char *)dkim_signing_selector,
279                         (char *)dkim_private_key
280                        );
281
282   pdkim_set_debug_stream(ctx,debug_file);
283
284   pdkim_set_optional(ctx,
285                      (char *)dkim_sign_headers,
286                      NULL,
287                      pdkim_canon,
288                      pdkim_canon,
289                      -1,
290                      PDKIM_ALGO_RSA_SHA256,
291                      0,
292                      0);
293
294   while((sread = read(dkim_fd,&buf,4096)) > 0) {
295     if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
296       rc = NULL;
297       goto CLEANUP;
298     }
299   }
300   /* Handle failed read above. */
301   if (sread == -1) {
302     debug_printf("DKIM: Error reading -K file.\n");
303     save_errno = errno;
304     rc = NULL;
305     goto CLEANUP;
306   }
307
308   if (pdkim_feed_finish(ctx,&signature) != PDKIM_OK)
309     goto CLEANUP;
310
311   rc = store_get(strlen(signature->signature_header)+3);
312   Ustrcpy(rc,US signature->signature_header);
313   Ustrcat(rc,US"\r\n");
314
315   CLEANUP:
316   if (ctx != NULL) {
317     pdkim_free_ctx(ctx);
318   }
319   store_pool = old_pool;
320   errno = save_errno;
321   return rc;
322 };
323
324 #endif