Skip to content

Commit 42ca523

Browse files
committed
libopendkim: don't error on missing CRLF when l= body length tag is used
When a DKIM signature includes the l= body length tag, the MTA delivers only that many bytes of body and OpenDKIM returns SMFIS_SKIP. The truncated body may not end with CRLF, which is expected: RFC 6376 section 3.7 specifies that body canonicalization appends CRLF to a non-empty body that doesn't already end with one. Treating this as a syntax error was a false positive. When canon_remain != -1 (i.e. l= was used), always take the append-CRLF path in dkim_canon_closebody() rather than erroring. The error is still raised when no body length limit was in use, consistent with the original intent. Fixes #45
1 parent fc78c00 commit 42ca523

4 files changed

Lines changed: 302 additions & 1 deletion

File tree

libopendkim/dkim-canon.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1938,7 +1938,8 @@ dkim_canon_closebody(DKIM *dkim)
19381938
/* handle unprocessed content */
19391939
if (dkim_dstring_len(cur->canon_buf) > 0)
19401940
{
1941-
if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_FIXCRLF) != 0)
1941+
if ((dkim->dkim_libhandle->dkiml_flags & DKIM_LIBFLAGS_FIXCRLF) != 0 ||
1942+
cur->canon_remain != (ssize_t) -1)
19421943
{
19431944
dkim_canon_buffer(cur,
19441945
dkim_dstring_get(cur->canon_buf),

libopendkim/tests/Makefile.am

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ check_PROGRAMS = t-setup t-test00 t-test01 t-test02 t-test03 t-test04 \
4444
t-test157 t-test158 t-test159 t-test160 \
4545
t-test200 t-test201 t-test202 t-test203 \
4646
t-test204 t-test205 \
47+
t-test206 t-test207 \
4748
t-signperf t-verifyperf
4849
check_SCRIPTS = t-signperf-sha1 t-signperf-relaxed-relaxed \
4950
t-signperf-simple-simple \
@@ -234,6 +235,8 @@ t_test202_SOURCES = t-test202.c t-testdata.h
234235
t_test203_SOURCES = t-test203.c t-testdata.h
235236
t_test204_SOURCES = t-test204.c t-testdata.h
236237
t_test205_SOURCES = t-test205.c t-testdata.h
238+
t_test206_SOURCES = t-test206.c t-testdata.h
239+
t_test207_SOURCES = t-test207.c t-testdata.h
237240

238241
MOSTLYCLEANFILES=
239242

libopendkim/tests/t-test206.c

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
** Copyright (c) 2005-2008 Sendmail, Inc. and its suppliers.
3+
** All rights reserved.
4+
**
5+
** Copyright (c) 2009, 2011-2013, 2026, The Trusted Domain Project.
6+
** All rights reserved.
7+
*/
8+
9+
/*
10+
** t-test206 -- sign+verify with l= where body does not end in CRLF should
11+
** succeed; verifies fix for issue #45 ("CRLF at end of body
12+
** missing" false positive when l= truncation is in use)
13+
*/
14+
15+
#include "build-config.h"
16+
17+
/* system includes */
18+
#include <sys/types.h>
19+
#include <assert.h>
20+
#include <string.h>
21+
#include <stdio.h>
22+
23+
#ifdef USE_GNUTLS
24+
# include <gnutls/gnutls.h>
25+
#endif /* USE_GNUTLS */
26+
27+
/* libopendkim includes */
28+
#include "../dkim.h"
29+
#include "t-testdata.h"
30+
31+
#define MAXHEADER 4096
32+
33+
/* body content that does not end in CRLF */
34+
#define TBODY_NOCRLF "Body without CRLF"
35+
#define TBODY_NOCRLF_LEN 17
36+
37+
static DKIM_STAT
38+
key_lookup(DKIM *dkim, DKIM_SIGINFO *sig, unsigned char *buf, size_t buflen)
39+
{
40+
assert(buf != NULL);
41+
memset(buf, '\0', buflen);
42+
strncpy((char *) buf, PUBLICKEY, buflen - 1);
43+
return DKIM_STAT_OK;
44+
}
45+
46+
int
47+
main(int argc, char **argv)
48+
{
49+
#ifdef TEST_KEEP_FILES
50+
u_int flags;
51+
#endif /* TEST_KEEP_FILES */
52+
DKIM_STAT status;
53+
DKIM *dkim;
54+
DKIM_LIB *lib;
55+
dkim_sigkey_t key;
56+
unsigned char sig_hdr[MAXHEADER + 1];
57+
unsigned char hdr[MAXHEADER + 1];
58+
59+
printf("*** l= truncation: body without CRLF does not trigger syntax error\n");
60+
61+
#ifdef USE_GNUTLS
62+
(void) gnutls_global_init();
63+
#endif /* USE_GNUTLS */
64+
65+
lib = dkim_init(NULL, NULL);
66+
assert(lib != NULL);
67+
68+
#ifdef TEST_KEEP_FILES
69+
flags = (DKIM_LIBFLAGS_TMPFILES|DKIM_LIBFLAGS_KEEPFILES);
70+
(void) dkim_options(lib, DKIM_OP_SETOPT, DKIM_OPTS_FLAGS, &flags,
71+
sizeof flags);
72+
#endif /* TEST_KEEP_FILES */
73+
74+
/*
75+
** Sign with an explicit body length (produces l= in the signature).
76+
** The body fed here does not end in CRLF, which is the scenario that
77+
** previously triggered "CRLF at end of body missing" during sign too.
78+
*/
79+
key = KEY;
80+
dkim = dkim_sign(lib, JOBID, NULL, key, SELECTOR, DOMAIN,
81+
DKIM_CANON_RELAXED, DKIM_CANON_RELAXED,
82+
DKIM_SIGN_RSASHA256,
83+
(ssize_t) TBODY_NOCRLF_LEN, &status);
84+
assert(dkim != NULL);
85+
86+
status = dkim_header(dkim, HEADER05, strlen(HEADER05));
87+
assert(status == DKIM_STAT_OK);
88+
status = dkim_header(dkim, HEADER06, strlen(HEADER06));
89+
assert(status == DKIM_STAT_OK);
90+
status = dkim_header(dkim, HEADER07, strlen(HEADER07));
91+
assert(status == DKIM_STAT_OK);
92+
status = dkim_header(dkim, HEADER08, strlen(HEADER08));
93+
assert(status == DKIM_STAT_OK);
94+
status = dkim_eoh(dkim);
95+
assert(status == DKIM_STAT_OK);
96+
97+
status = dkim_body(dkim, (u_char *) TBODY_NOCRLF, TBODY_NOCRLF_LEN);
98+
assert(status == DKIM_STAT_OK);
99+
100+
/* sign eom must not fail with DKIM_STAT_SYNTAX */
101+
status = dkim_eom(dkim, NULL);
102+
assert(status == DKIM_STAT_OK);
103+
104+
memset(sig_hdr, '\0', sizeof sig_hdr);
105+
status = dkim_getsighdr(dkim, sig_hdr, sizeof sig_hdr,
106+
strlen(DKIM_SIGNHEADER) + 2);
107+
assert(status == DKIM_STAT_OK);
108+
109+
status = dkim_free(dkim);
110+
assert(status == DKIM_STAT_OK);
111+
112+
/*
113+
** Verify: feed the same truncated body without CRLF.
114+
** The verifier sees l= in the signature (canon_remain != -1) and must
115+
** canonicalize the truncated body by appending CRLF rather than
116+
** returning DKIM_STAT_SYNTAX.
117+
*/
118+
status = dkim_set_key_lookup(lib, key_lookup);
119+
assert(status == DKIM_STAT_OK);
120+
121+
dkim = dkim_verify(lib, JOBID, NULL, &status);
122+
assert(dkim != NULL);
123+
124+
snprintf((char *) hdr, sizeof hdr, "%s: %s", DKIM_SIGNHEADER, sig_hdr);
125+
status = dkim_header(dkim, hdr, strlen((char *) hdr));
126+
assert(status == DKIM_STAT_OK);
127+
128+
status = dkim_header(dkim, HEADER05, strlen(HEADER05));
129+
assert(status == DKIM_STAT_OK);
130+
status = dkim_header(dkim, HEADER06, strlen(HEADER06));
131+
assert(status == DKIM_STAT_OK);
132+
status = dkim_header(dkim, HEADER07, strlen(HEADER07));
133+
assert(status == DKIM_STAT_OK);
134+
status = dkim_header(dkim, HEADER08, strlen(HEADER08));
135+
assert(status == DKIM_STAT_OK);
136+
status = dkim_eoh(dkim);
137+
assert(status == DKIM_STAT_OK);
138+
139+
status = dkim_body(dkim, (u_char *) TBODY_NOCRLF, TBODY_NOCRLF_LEN);
140+
assert(status == DKIM_STAT_OK);
141+
142+
/* verify eom must not fail with DKIM_STAT_SYNTAX */
143+
status = dkim_eom(dkim, NULL);
144+
assert(status == DKIM_STAT_OK);
145+
146+
status = dkim_free(dkim);
147+
assert(status == DKIM_STAT_OK);
148+
149+
dkim_close(lib);
150+
151+
return 0;
152+
}

libopendkim/tests/t-test207.c

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
** Copyright (c) 2005-2008 Sendmail, Inc. and its suppliers.
3+
** All rights reserved.
4+
**
5+
** Copyright (c) 2009, 2011-2013, 2026, The Trusted Domain Project.
6+
** All rights reserved.
7+
*/
8+
9+
/*
10+
** t-test207 -- regression: body without trailing CRLF and no l= tag still
11+
** raises DKIM_STAT_SYNTAX (issue #45)
12+
*/
13+
14+
#include "build-config.h"
15+
16+
/* system includes */
17+
#include <sys/types.h>
18+
#include <assert.h>
19+
#include <string.h>
20+
#include <stdio.h>
21+
22+
#ifdef USE_GNUTLS
23+
# include <gnutls/gnutls.h>
24+
#endif /* USE_GNUTLS */
25+
26+
/* libopendkim includes */
27+
#include "../dkim.h"
28+
#include "t-testdata.h"
29+
30+
#define MAXHEADER 4096
31+
32+
/* body content that does not end in CRLF */
33+
#define TBODY_NOCRLF "Body without CRLF"
34+
#define TBODY_NOCRLF_LEN 17
35+
36+
static DKIM_STAT
37+
key_lookup(DKIM *dkim, DKIM_SIGINFO *sig, unsigned char *buf, size_t buflen)
38+
{
39+
assert(buf != NULL);
40+
memset(buf, '\0', buflen);
41+
strncpy((char *) buf, PUBLICKEY, buflen - 1);
42+
return DKIM_STAT_OK;
43+
}
44+
45+
int
46+
main(int argc, char **argv)
47+
{
48+
#ifdef TEST_KEEP_FILES
49+
u_int flags;
50+
#endif /* TEST_KEEP_FILES */
51+
DKIM_STAT status;
52+
DKIM *dkim;
53+
DKIM_LIB *lib;
54+
dkim_sigkey_t key;
55+
unsigned char sig_hdr[MAXHEADER + 1];
56+
unsigned char hdr[MAXHEADER + 1];
57+
58+
printf("*** no l=: body without CRLF still raises syntax error\n");
59+
60+
#ifdef USE_GNUTLS
61+
(void) gnutls_global_init();
62+
#endif /* USE_GNUTLS */
63+
64+
lib = dkim_init(NULL, NULL);
65+
assert(lib != NULL);
66+
67+
#ifdef TEST_KEEP_FILES
68+
flags = (DKIM_LIBFLAGS_TMPFILES|DKIM_LIBFLAGS_KEEPFILES);
69+
(void) dkim_options(lib, DKIM_OP_SETOPT, DKIM_OPTS_FLAGS, &flags,
70+
sizeof flags);
71+
#endif /* TEST_KEEP_FILES */
72+
73+
/* sign without body length limit (no l= in signature) */
74+
key = KEY;
75+
dkim = dkim_sign(lib, JOBID, NULL, key, SELECTOR, DOMAIN,
76+
DKIM_CANON_RELAXED, DKIM_CANON_RELAXED,
77+
DKIM_SIGN_RSASHA256, -1L, &status);
78+
assert(dkim != NULL);
79+
80+
status = dkim_header(dkim, HEADER05, strlen(HEADER05));
81+
assert(status == DKIM_STAT_OK);
82+
status = dkim_header(dkim, HEADER06, strlen(HEADER06));
83+
assert(status == DKIM_STAT_OK);
84+
status = dkim_header(dkim, HEADER07, strlen(HEADER07));
85+
assert(status == DKIM_STAT_OK);
86+
status = dkim_header(dkim, HEADER08, strlen(HEADER08));
87+
assert(status == DKIM_STAT_OK);
88+
status = dkim_eoh(dkim);
89+
assert(status == DKIM_STAT_OK);
90+
91+
/* sign with a proper CRLF-terminated body to get a valid signature */
92+
status = dkim_body(dkim, BODY00, strlen(BODY00));
93+
assert(status == DKIM_STAT_OK);
94+
status = dkim_eom(dkim, NULL);
95+
assert(status == DKIM_STAT_OK);
96+
97+
memset(sig_hdr, '\0', sizeof sig_hdr);
98+
status = dkim_getsighdr(dkim, sig_hdr, sizeof sig_hdr,
99+
strlen(DKIM_SIGNHEADER) + 2);
100+
assert(status == DKIM_STAT_OK);
101+
102+
status = dkim_free(dkim);
103+
assert(status == DKIM_STAT_OK);
104+
105+
/*
106+
** Verify: feed a body without trailing CRLF. Since the signature has
107+
** no l= tag (canon_remain == -1), this must still raise DKIM_STAT_SYNTAX
108+
** and not silently accept the malformed input.
109+
*/
110+
status = dkim_set_key_lookup(lib, key_lookup);
111+
assert(status == DKIM_STAT_OK);
112+
113+
dkim = dkim_verify(lib, JOBID, NULL, &status);
114+
assert(dkim != NULL);
115+
116+
snprintf((char *) hdr, sizeof hdr, "%s: %s", DKIM_SIGNHEADER, sig_hdr);
117+
status = dkim_header(dkim, hdr, strlen((char *) hdr));
118+
assert(status == DKIM_STAT_OK);
119+
120+
status = dkim_header(dkim, HEADER05, strlen(HEADER05));
121+
assert(status == DKIM_STAT_OK);
122+
status = dkim_header(dkim, HEADER06, strlen(HEADER06));
123+
assert(status == DKIM_STAT_OK);
124+
status = dkim_header(dkim, HEADER07, strlen(HEADER07));
125+
assert(status == DKIM_STAT_OK);
126+
status = dkim_header(dkim, HEADER08, strlen(HEADER08));
127+
assert(status == DKIM_STAT_OK);
128+
status = dkim_eoh(dkim);
129+
assert(status == DKIM_STAT_OK);
130+
131+
/* feed body WITHOUT trailing CRLF */
132+
status = dkim_body(dkim, (u_char *) TBODY_NOCRLF, TBODY_NOCRLF_LEN);
133+
assert(status == DKIM_STAT_OK);
134+
135+
/* without l=, missing CRLF must still be a syntax error */
136+
status = dkim_eom(dkim, NULL);
137+
assert(status == DKIM_STAT_SYNTAX);
138+
139+
status = dkim_free(dkim);
140+
assert(status == DKIM_STAT_OK);
141+
142+
dkim_close(lib);
143+
144+
return 0;
145+
}

0 commit comments

Comments
 (0)