├── .classpath
├── .gitignore
├── .project
├── DKIMforJavaMail.jar
├── LICENSE
├── README.md
├── build.xml
├── keys
├── default.pub.pem
├── private.key.der
└── private.key.pem
├── lib
├── activation.jar
├── dsn.jar
├── imap.jar
├── mail.jar
├── mailapi.jar
├── pop3.jar
└── smtp.jar
├── src
└── de
│ └── agitos
│ └── dkim
│ ├── Canonicalization.java
│ ├── DKIMSigner.java
│ ├── DKIMSignerException.java
│ ├── DKIMUtil.java
│ ├── SMTPDKIMMessage.java
│ └── SigningAlgorithm.java
├── test.properties.template
└── test
└── de
└── agitos
└── dkim
├── FullExample.java
├── MimeMailExample.java
├── MultipleMailExample.java
├── SimpleExample.java
└── TestUtil.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Mobile Tools for Java (J2ME)
4 | .mtj.tmp/
5 |
6 | # Package Files #
7 | *.war
8 | *.ear
9 |
10 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
11 | hs_err_pid*
12 | /bin/
13 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | DKIMforJavaMail
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/DKIMforJavaMail.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/DKIMforJavaMail.jar
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DKIM-for-JavaMail
2 |
3 | DKIM for JavaMail is a small JAR that can be easily implemented in existing Java projects using JavaMail for sending DKIM signed emails.
4 |
5 | This github repository replaces the original repository at http://dkim-javamail.sourceforge.net
6 | See http://sourceforge.net/p/dkim-javamail/discussion/893011/ for old forum posts.
7 |
8 |
9 | ## Setup DKIM for JavaMail API
10 |
11 | What is required to use DKIM for JavaMail
12 | ---
13 |
14 | - a project using JavaMail: http://java.sun.com/products/javamail/
15 | (DKIM for JavaMail was tested with version JavaMail 1.4.1 but should be compatible with
16 | older versions too, see TODO below)
17 |
18 |
19 | - a DKIM key setup:
20 |
21 | There are test keys in the keys/ directory but be aware to use those only for your tests.
22 |
23 | You need:
24 |
25 | a) a private key on your hard disc (e.g. in keys/); you can generate a new key by
26 |
27 | > openssl genrsa -out private.key.pem
28 |
29 | DKIM for JavaMail needs the private key in DER format, you can transform a PEM key
30 | with openssl:
31 |
32 | > openssl pkcs8 -topk8 -nocrypt -in private.key.pem -out private.key.der -outform der
33 |
34 |
35 | b) a public key in your DNS; here is a sample ressource record with selector "default":
36 | default._domainkey IN TXT "v=DKIM1; g=*; k=rsa; p=MIG...the_public_key_here...AQAB"
37 | (see http://www.ietf.org/rfc/rfc4871.txt for details)
38 |
39 | You can use openssl to get a public key from the private key:
40 |
41 | > openssl rsa -inform PEM -in private.key.pem -pubout
42 |
43 |
44 |
45 | Run Tests
46 | ---
47 |
48 | - if you want to run the examples provided in DKIM for JavaMail you have to
49 |
50 | a) configure the config file test.properties (see comments inside)
51 |
52 | b) run `ant runTests`
53 |
54 |
55 | Implement DKIM for JavaMail into your code
56 | ---
57 |
58 | - add DKIMforJavaMail.jar to your classpath
59 |
60 | - see the example files in test/de/agitos/dkim/* : it's very simple to implement, enjoy!
61 |
62 |
63 | ## TODO
64 |
65 | - DKIM for JavaMail is in principal compatible with all libraries using JavaMail,
66 | an example for Apache Commons Email should be added to this project
67 |
68 | - test of DKIM for JavaMail with older JavaMail versions, maybe a JAR for Java 1.4;
69 | I will do this if needed upon request
70 |
71 | - verification functionality could be added; this has minor priority at time, I will
72 | do this if needed upon request (there will be other libraries doing this maybe)
73 |
74 | - the optional public key check before sending should be fully implemented; this is
75 | out of the DKIM basic functionality and therefore isn't mandatory
76 |
77 |
78 | ## RELEASE-HISTORY
79 |
80 | 1.0, 2008-11-30
81 |
82 | 1.1, 2009-04-13
83 | Bugfix line-encoding before body canonicalization on *nix systems
84 | Added partial line-folding of the DKIM-Signature header field
85 |
86 | 1.2, 2009-05-10
87 | Bugfix in signing mime messages that are loaded by an InputStream,
88 | Added a MimeMessageTest for testing the signing of loaded mime messages.
89 |
90 | 1.3, 2009-07-29
91 | Changed the insertion of the DKIM-Signature header to the top most position
92 | when sending mail. This is compatible with the Yahoo! DKIM verifier.
93 |
94 |
95 | ## Further DKIM Links
96 |
97 | http://www.dkim.org/deploy/ : DKIM implementations
98 | On this website you can find most MTA-DKIM implementations and complementary products
99 | and services.
100 |
101 | http://java.sun.com/products/javamail/ : Sun JavaMail API
102 |
103 | ---
104 |
105 | http://www.agitos.de/dkim-for-java-mail-open-source-library-2.html
106 | Florian Sager, sager@agitos.de, 22.11.2008
107 |
--------------------------------------------------------------------------------
/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | -----------------------------------------------------------------
22 | Build DKIMforJavaMail.jar
23 | -----------------------------------------------------------------
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | -----------------------------------------------------------------
39 | Run tests, be aware to set your settings in test.properties first
40 | -----------------------------------------------------------------
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | -----------------------------------------------------------------
54 | Sending 1 mail with SimpleExample
55 |
56 |
57 |
58 |
59 |
60 |
61 | Sending several mails with MultipleMailExample
62 |
63 |
64 |
65 |
66 |
67 |
68 | Sending 1 mail with FullExample
69 |
70 |
71 |
72 |
73 |
74 |
75 | Sending 1 mail with MimeMailExample
76 |
77 |
78 |
79 |
80 |
81 |
82 | -----------------------------------------------------------------
83 | If something failed, check your settings in the test.properties file
84 | -----------------------------------------------------------------
85 |
86 |
87 |
--------------------------------------------------------------------------------
/keys/default.pub.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPgwLEqUfo+oaSgYGqLg7U7rpdYM2JPE
3 | lJ4dQur+1BJz7Gm2JCTfmFxc3STeUSyTEtiGkowhdwl0UksU+K1laBMCAwEAAQ==
4 | -----END PUBLIC KEY-----
5 |
--------------------------------------------------------------------------------
/keys/private.key.der:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/keys/private.key.der
--------------------------------------------------------------------------------
/keys/private.key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIBOwIBAAJBAPgwLEqUfo+oaSgYGqLg7U7rpdYM2JPElJ4dQur+1BJz7Gm2JCTf
3 | mFxc3STeUSyTEtiGkowhdwl0UksU+K1laBMCAwEAAQJBAPGaQ6Oehs3mTeff/x4M
4 | BDfNojSLcNH4jgyVw6zrwRrrn7GGOufOAZpBIiY+59JGteOiSXqcARLhASXVihk9
5 | b1ECIQD+0UYpT5jhiSyJzZd9VyWW7N+e7S4V06WlpVI+PH45BwIhAPlXBfSY5/IG
6 | 2lct9UH8EUfHUBCg/b/t+vVD7dtPRVGVAiB027ZoZZd//u+M9tr9YUTc1R6sfKsA
7 | 2YIC2DFCV22vawIhAPfpyOgcqHKC0dFgxrzm7h+aU0dVazfMXXxEIFmwhdHJAiA7
8 | hfwWXz1j9IemT24+ukiggSRW+Rifzg20+rI6KDhr5A==
9 | -----END RSA PRIVATE KEY-----
10 |
--------------------------------------------------------------------------------
/lib/activation.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/activation.jar
--------------------------------------------------------------------------------
/lib/dsn.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/dsn.jar
--------------------------------------------------------------------------------
/lib/imap.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/imap.jar
--------------------------------------------------------------------------------
/lib/mail.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/mail.jar
--------------------------------------------------------------------------------
/lib/mailapi.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/mailapi.jar
--------------------------------------------------------------------------------
/lib/pop3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/pop3.jar
--------------------------------------------------------------------------------
/lib/smtp.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usrflo/DKIM-for-JavaMail/517c96b6bb9ef7e3e165d8acc0bdd1d989907adb/lib/smtp.jar
--------------------------------------------------------------------------------
/src/de/agitos/dkim/Canonicalization.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 The Apache Software Foundation or its licensors, as
3 | * applicable.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008
18 | */
19 | package de.agitos.dkim;
20 |
21 | import java.io.IOException;
22 |
23 | /*
24 | * Provides Simple and Relaxed Canonicalization according to DKIM RFC 4871.
25 | *
26 | * @author Florian Sager, http://www.agitos.de, 22.11.2008
27 | */
28 |
29 | public class Canonicalization {
30 |
31 | public static Canonicalization SIMPLE = new Canonicalization() {
32 |
33 | public String getType() {
34 |
35 | return "simple";
36 | }
37 |
38 | public String canonicalizeHeader(String name, String value) {
39 |
40 | return name+":"+value;
41 | }
42 |
43 | public String canonicalizeBody(String body) throws IOException {
44 |
45 | if (body == null || "".equals(body) ) {
46 | return "\r\n";
47 | }
48 |
49 | // The body must end with \r\n
50 | if (!"\r\n".equals(body.substring(body.length()-2, body.length()))) {
51 | return body+"\r\n";
52 | }
53 |
54 | // Remove trailing empty lines ...
55 | while ("\r\n\r\n".equals(body.substring(body.length()-4, body.length()))) {
56 | body = body.substring(0, body.length()-2);
57 | if (body.length() < 4) {
58 | break;
59 | }
60 | }
61 |
62 | return body;
63 | }
64 | };
65 |
66 | public static Canonicalization RELAXED = new Canonicalization() {
67 |
68 | public String getType() {
69 |
70 | return "relaxed";
71 | }
72 |
73 | public String canonicalizeHeader(String name, String value) {
74 |
75 | name = name.trim().toLowerCase();
76 | value = value.replaceAll("\\s+", " ").trim();
77 | return name+":"+value;
78 | }
79 |
80 | public String canonicalizeBody(String body) throws IOException {
81 |
82 | if (body == null || "".equals(body) ) {
83 | return "\r\n";
84 | }
85 |
86 | body = body.replaceAll("[ \\t\\x0B\\f]+", " ");
87 | body = body.replaceAll(" \r\n", "\r\n");
88 |
89 | // The body must end with \r\n
90 | if (!"\r\n".equals(body.substring(body.length()-2, body.length()))) {
91 | return body+"\r\n";
92 | }
93 |
94 | // Remove trailing empty lines ...
95 | while ("\r\n\r\n".equals(body.substring(body.length()-4, body.length()))) {
96 | body = body.substring(0, body.length()-2);
97 | if (body.length() < 4) {
98 | break;
99 | }
100 | }
101 |
102 | return body;
103 | }
104 | };
105 |
106 | public Canonicalization() { }
107 |
108 | public String getType() {
109 | return "unknown";
110 | }
111 |
112 | public String canonicalizeHeader(String name, String value) {
113 | return null;
114 | }
115 |
116 | public String canonicalizeBody(String body) throws IOException {
117 | return null;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/de/agitos/dkim/DKIMSigner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 The Apache Software Foundation or its licensors, as
3 | * applicable.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008
18 | */
19 |
20 | package de.agitos.dkim;
21 |
22 | import java.io.ByteArrayOutputStream;
23 | import java.io.DataInputStream;
24 | import java.io.File;
25 | import java.io.FileInputStream;
26 | import java.io.IOException;
27 | import java.security.InvalidKeyException;
28 | import java.security.KeyFactory;
29 | import java.security.MessageDigest;
30 | import java.security.NoSuchAlgorithmException;
31 | import java.security.PrivateKey;
32 | import java.security.Signature;
33 | import java.security.SignatureException;
34 | import java.security.interfaces.RSAPrivateKey;
35 | import java.security.spec.PKCS8EncodedKeySpec;
36 | import java.util.ArrayList;
37 | import java.util.Date;
38 | import java.util.Enumeration;
39 | import java.util.Iterator;
40 | import java.util.LinkedHashMap;
41 | import java.util.Map;
42 | import java.util.Set;
43 | import java.util.Map.Entry;
44 |
45 | import javax.mail.MessagingException;
46 |
47 | import com.sun.mail.util.CRLFOutputStream;
48 |
49 |
50 | /*
51 | * Main class providing a signature according to DKIM RFC 4871.
52 | *
53 | * @author Florian Sager, http://www.agitos.de, 15.10.2008
54 | */
55 |
56 | public class DKIMSigner {
57 |
58 | private static String DKIMSIGNATUREHEADER = "DKIM-Signature";
59 | private static int MAXHEADERLENGTH = 67;
60 |
61 | private static ArrayList minimumHeadersToSign = new ArrayList();
62 | static {
63 | minimumHeadersToSign.add("From");
64 | }
65 |
66 | private String[] defaultHeadersToSign = new String[]{
67 | "Content-Description","Content-ID","Content-Type","Content-Transfer-Encoding","Cc",
68 | "Date","From","In-Reply-To","List-Subscribe","List-Post","List-Owner","List-Id",
69 | "List-Archive","List-Help","List-Unsubscribe","MIME-Version","Message-ID","Resent-Sender",
70 | "Resent-Cc","Resent-Date","Resent-To","Reply-To","References","Resent-Message-ID",
71 | "Resent-From","Sender","Subject","To"};
72 |
73 | private SigningAlgorithm signingAlgorithm = SigningAlgorithm.SHA256withRSA; // use rsa-sha256 by default, see RFC 4871
74 | private Signature signatureService;
75 | private MessageDigest messageDigest;
76 | private String signingDomain;
77 | private String selector;
78 | private String identity = null;
79 | private boolean lengthParam = false;
80 | private boolean zParam = false;
81 | private Canonicalization headerCanonicalization = Canonicalization.RELAXED;
82 | private Canonicalization bodyCanonicalization = Canonicalization.SIMPLE;
83 | private PrivateKey privkey;
84 |
85 | public DKIMSigner(String signingDomain, String selector, PrivateKey privkey) throws Exception {
86 | initDKIMSigner(signingDomain, selector, privkey);
87 | }
88 |
89 | public DKIMSigner(String signingDomain, String selector, String privkeyFilename) throws Exception {
90 |
91 | File privKeyFile = new File(privkeyFilename);
92 |
93 | // read private key DER file
94 | DataInputStream dis = new DataInputStream(new FileInputStream(privKeyFile));
95 | byte[] privKeyBytes = new byte[(int) privKeyFile.length()];
96 | dis.read(privKeyBytes);
97 | dis.close();
98 |
99 | KeyFactory keyFactory = KeyFactory.getInstance("RSA");
100 |
101 | // decode private key
102 | PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privKeyBytes);
103 | RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(privSpec);
104 |
105 | initDKIMSigner(signingDomain, selector, privKey);
106 | }
107 |
108 | private void initDKIMSigner(String signingDomain, String selector, PrivateKey privkey) throws DKIMSignerException {
109 |
110 | if (!DKIMUtil.isValidDomain(signingDomain)) {
111 | throw new DKIMSignerException(signingDomain+" is an invalid signing domain");
112 | }
113 |
114 | this.signingDomain = signingDomain;
115 | this.selector = selector.trim();
116 | this.privkey = privkey;
117 | this.setSigningAlgorithm(this.signingAlgorithm);
118 | }
119 |
120 | public String getIdentity() {
121 | return identity;
122 | }
123 |
124 | public void setIdentity(String identity) throws DKIMSignerException {
125 |
126 | if (identity!=null) {
127 | identity = identity.trim();
128 | if (!identity.endsWith("@"+signingDomain) && !identity.endsWith("."+signingDomain)) {
129 | throw new DKIMSignerException("The domain part of "+identity+" has to be "+signingDomain+" or its subdomain");
130 | }
131 | }
132 |
133 | this.identity = identity;
134 | }
135 |
136 | public Canonicalization getBodyCanonicalization() {
137 | return bodyCanonicalization;
138 | }
139 |
140 | public void setBodyCanonicalization(Canonicalization bodyCanonicalization) throws DKIMSignerException {
141 | this.bodyCanonicalization = bodyCanonicalization;
142 | }
143 |
144 | public Canonicalization getHeaderCanonicalization() {
145 | return headerCanonicalization;
146 | }
147 |
148 | public void setHeaderCanonicalization(Canonicalization headerCanonicalization) throws DKIMSignerException {
149 | this.headerCanonicalization = headerCanonicalization;
150 | }
151 |
152 | public String[] getDefaultHeadersToSign() {
153 | return defaultHeadersToSign;
154 | }
155 |
156 | public void addHeaderToSign(String header) {
157 |
158 | if (header==null || "".equals(header)) return;
159 |
160 | int len = this.defaultHeadersToSign.length;
161 | String[] headersToSign = new String[len+1];
162 | for (int i=0; i dkimSignature) {
239 |
240 | Set> entries = dkimSignature.entrySet();
241 | StringBuffer buf = new StringBuffer(), fbuf;
242 | int pos = 0;
243 |
244 | Iterator> iter = entries.iterator();
245 | while (iter.hasNext()) {
246 | Entry entry = iter.next();
247 |
248 | // buf.append(entry.getKey()).append("=").append(entry.getValue()).append(";\t");
249 |
250 | fbuf = new StringBuffer();
251 | fbuf.append(entry.getKey()).append("=").append(entry.getValue()).append(";");
252 |
253 | if (pos + fbuf.length() + 1 > MAXHEADERLENGTH) {
254 |
255 | pos = fbuf.length();
256 |
257 | // line folding : this doesn't work "sometimes" --> maybe someone likes to debug this
258 | /* int i = 0;
259 | while (iMAXHEADERLENGTH) {
261 | buf.append("\r\n\t").append(fbuf.substring(i, i+MAXHEADERLENGTH));
262 | i += MAXHEADERLENGTH;
263 | } else {
264 | buf.append("\r\n\t").append(fbuf.substring(i));
265 | pos -= i;
266 | break;
267 | }
268 | } */
269 |
270 | buf.append("\r\n\t").append(fbuf);
271 |
272 | } else {
273 | buf.append(" ").append(fbuf);
274 | pos += fbuf.length() + 1;
275 | }
276 | }
277 |
278 | buf.append("\r\n\tb=");
279 |
280 | return buf.toString().trim();
281 | }
282 |
283 | private String foldSignedSignature(String s, int offset) {
284 |
285 | int i = 0;
286 | StringBuffer buf = new StringBuffer();
287 |
288 | while (true) {
289 | if (offset > 0 && s.substring(i).length()>MAXHEADERLENGTH - offset) {
290 | buf.append(s.substring(i, i + MAXHEADERLENGTH - offset));
291 | i += MAXHEADERLENGTH - offset;
292 | offset = 0;
293 | } else if (s.substring(i).length()>MAXHEADERLENGTH) {
294 | buf.append("\r\n\t").append(s.substring(i, i + MAXHEADERLENGTH));
295 | i += MAXHEADERLENGTH;
296 | } else {
297 | buf.append("\r\n\t").append(s.substring(i));
298 | break;
299 | }
300 | }
301 |
302 | return buf.toString();
303 | }
304 |
305 | public String sign(SMTPDKIMMessage message) throws DKIMSignerException, MessagingException {
306 |
307 | Map dkimSignature = new LinkedHashMap();
308 | dkimSignature.put("v", "1");
309 | dkimSignature.put("a", this.signingAlgorithm.getRfc4871Notation());
310 | dkimSignature.put("q", "dns/txt");
311 | dkimSignature.put("c", getHeaderCanonicalization().getType()+"/"+getBodyCanonicalization().getType());
312 | dkimSignature.put("t", ((long) new Date().getTime() / 1000)+"");
313 | dkimSignature.put("s", this.selector);
314 | dkimSignature.put("d", this.signingDomain);
315 |
316 | // set identity inside signature
317 | if (identity!=null) {
318 | dkimSignature.put("i", DKIMUtil.QuotedPrintable(identity));
319 | }
320 |
321 | // process header
322 | ArrayList assureHeaders = (ArrayList) minimumHeadersToSign.clone();
323 |
324 | // intersect defaultHeadersToSign with available headers
325 | StringBuffer headerList = new StringBuffer();
326 | StringBuffer headerContent = new StringBuffer();
327 | StringBuffer zParamString = new StringBuffer();
328 |
329 | Enumeration headerLines = message.getMatchingHeaderLines(defaultHeadersToSign);
330 | while (headerLines.hasMoreElements()) {
331 | String header = (String) headerLines.nextElement();
332 | String[] headerParts = DKIMUtil.splitHeader(header);
333 | headerList.append(headerParts[0]).append(":");
334 | headerContent.append(this.headerCanonicalization.canonicalizeHeader(headerParts[0], headerParts[1])).append("\r\n");
335 | assureHeaders.remove(headerParts[0]);
336 |
337 | // add optional z= header list, DKIM-Quoted-Printable
338 | if (this.zParam) {
339 | zParamString.append(headerParts[0]).append(":").append(DKIMUtil.QuotedPrintable(headerParts[1].trim()).replace("|", "=7C")).append("|");
340 | }
341 | }
342 |
343 | if (!assureHeaders.isEmpty()) {
344 | throw new DKIMSignerException("Could not find the header fields "+DKIMUtil.concatArray(assureHeaders, ", ")+" for signing");
345 | }
346 |
347 | dkimSignature.put("h", headerList.substring(0, headerList.length()-1));
348 |
349 | if (this.zParam) {
350 | String zParamTemp = zParamString.toString();
351 | dkimSignature.put("z", zParamTemp.substring(0, zParamTemp.length()-1));
352 | }
353 |
354 | // process body
355 | String body = message.getEncodedBody();
356 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
357 | CRLFOutputStream crlfos = new CRLFOutputStream(baos);
358 | try {
359 | crlfos.write(body.getBytes());
360 | } catch (IOException e) {
361 | throw new DKIMSignerException("The body conversion to MIME canonical CRLF line terminator failed", e);
362 | }
363 | body = baos.toString();
364 |
365 | try {
366 | body = this.bodyCanonicalization.canonicalizeBody(body);
367 | } catch (IOException ioe) {
368 | throw new DKIMSignerException("The body canonicalization failed", ioe);
369 | }
370 |
371 | if (this.lengthParam) {
372 | dkimSignature.put("l", body.length()+"");
373 | }
374 |
375 | // calculate and encode body hash
376 | dkimSignature.put("bh", DKIMUtil.base64Encode(this.messageDigest.digest(body.getBytes())));
377 |
378 | // create signature
379 | String serializedSignature = serializeDKIMSignature(dkimSignature);
380 |
381 | byte[] signedSignature;
382 | try {
383 | signatureService.update(headerContent.append(this.headerCanonicalization.canonicalizeHeader(DKIMSIGNATUREHEADER, " "+serializedSignature)).toString().getBytes());
384 | signedSignature = signatureService.sign();
385 | } catch (SignatureException se) {
386 | throw new DKIMSignerException("The signing operation by Java security failed", se);
387 | }
388 |
389 | return DKIMSIGNATUREHEADER + ": " + serializedSignature+foldSignedSignature(DKIMUtil.base64Encode(signedSignature), 3);
390 | }
391 | }
392 |
--------------------------------------------------------------------------------
/src/de/agitos/dkim/DKIMSignerException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 The Apache Software Foundation or its licensors, as
3 | * applicable.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008
18 | */
19 |
20 | package de.agitos.dkim;
21 |
22 | /*
23 | * @author Florian Sager, http://www.agitos.de, 15.11.2008
24 | */
25 |
26 | public class DKIMSignerException extends Exception {
27 |
28 | public DKIMSignerException(String message) {
29 | super(message);
30 | }
31 |
32 | public DKIMSignerException(String message, Exception e) {
33 | super(message, e);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/de/agitos/dkim/DKIMUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 The Apache Software Foundation or its licensors, as
3 | * applicable.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008
18 | */
19 |
20 | package de.agitos.dkim;
21 |
22 | import java.io.ByteArrayOutputStream;
23 | import java.io.IOException;
24 | import java.security.KeyFactory;
25 | import java.security.NoSuchAlgorithmException;
26 | import java.security.interfaces.RSAPrivateKey;
27 | import java.security.spec.InvalidKeySpecException;
28 | import java.security.spec.PKCS8EncodedKeySpec;
29 | import java.util.ArrayList;
30 | import java.util.Hashtable;
31 | import java.util.Iterator;
32 | import java.util.regex.Matcher;
33 | import java.util.regex.Pattern;
34 |
35 | import javax.naming.NamingException;
36 | import javax.naming.directory.DirContext;
37 | import javax.naming.directory.InitialDirContext;
38 |
39 | import sun.misc.BASE64Encoder;
40 |
41 | import com.sun.mail.util.QPEncoderStream;
42 |
43 | /*
44 | * @author Florian Sager, http://www.agitos.de, 22.11.2008
45 | */
46 |
47 | public class DKIMUtil {
48 |
49 | protected static String[] splitHeader(String header) throws DKIMSignerException {
50 | int colonPos = header.indexOf(':');
51 | if (colonPos==-1) {
52 | throw new DKIMSignerException("The header string "+header+" is no valid RFC 822 header-line");
53 | }
54 | return new String[]{header.substring(0, colonPos), header.substring(colonPos+1)};
55 | }
56 |
57 | protected static String concatArray(ArrayList l, String separator) {
58 | StringBuffer buf = new StringBuffer();
59 | Iterator iter = l.iterator();
60 | while (iter.hasNext()) {
61 | buf.append(iter.next()).append(separator);
62 | }
63 |
64 | return buf.substring(0, buf.length() - separator.length());
65 | }
66 |
67 | protected static boolean isValidDomain(String domainname) {
68 | Pattern pattern = Pattern.compile("(.+)\\.(.+)");
69 | Matcher matcher = pattern.matcher(domainname);
70 | return matcher.matches();
71 | }
72 |
73 | // FSTODO: converts to "platforms default encoding" might be wrong ?
74 | protected static String QuotedPrintable(String s) {
75 |
76 | try {
77 | ByteArrayOutputStream boas = new ByteArrayOutputStream();
78 | QPEncoderStream encodeStream = new QPEncoderStream(boas);
79 | encodeStream.write(s.getBytes());
80 |
81 | String encoded = boas.toString();
82 | encoded = encoded.replaceAll(";", "=3B");
83 | encoded = encoded.replaceAll(" ", "=20");
84 |
85 | return encoded;
86 |
87 | } catch (IOException ioe) {}
88 |
89 | return null;
90 | }
91 |
92 | protected static String base64Encode(byte[] b) {
93 | BASE64Encoder base64Enc = new BASE64Encoder();
94 | String encoded = base64Enc.encode(b);
95 | // remove unnecessary linefeeds after 76 characters
96 | encoded = encoded.replace("\n", ""); // Linux+Win
97 | return encoded.replace("\r", ""); // Win --> FSTODO: select Encoder without line termination
98 | }
99 |
100 | public boolean checkDNSForPublickey(String signingDomain, String selector) throws DKIMSignerException {
101 |
102 | Hashtable env = new Hashtable();
103 | env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
104 | String recordname = selector+"._domainkey."+signingDomain;
105 | String value = null;
106 |
107 | try {
108 | DirContext dnsContext = new InitialDirContext(env);
109 |
110 | javax.naming.directory.Attributes attribs = dnsContext.getAttributes(recordname, new String[] {"TXT"});
111 | javax.naming.directory.Attribute txtrecord = attribs.get("txt");
112 |
113 | if (txtrecord == null) {
114 | throw new DKIMSignerException("There is no TXT record available for "+recordname);
115 | }
116 |
117 | // "v=DKIM1; g=*; k=rsa; p=MIGfMA0G ..."
118 | value = (String) txtrecord.get();
119 |
120 | } catch (NamingException ne) {
121 | throw new DKIMSignerException("Selector lookup failed", ne);
122 | }
123 |
124 | if (value == null) {
125 | throw new DKIMSignerException("Value of RR "+recordname+" couldn't be retrieved");
126 | }
127 |
128 | // try to read public key from RR
129 | String[] tags = value.split(";");
130 | for (String tag : tags) {
131 | tag = tag.trim();
132 | if (tag.startsWith("p=")) {
133 |
134 | try {
135 | KeyFactory keyFactory = KeyFactory.getInstance("RSA");
136 |
137 | // decode public key, FSTODO: convert to DER format
138 | PKCS8EncodedKeySpec pubSpec = new PKCS8EncodedKeySpec(tag.substring(2).getBytes());
139 | RSAPrivateKey pubKey = (RSAPrivateKey) keyFactory.generatePublic(pubSpec);
140 | } catch (NoSuchAlgorithmException nsae) {
141 | throw new DKIMSignerException("RSA algorithm not found by JVM");
142 | } catch (InvalidKeySpecException ikse) {
143 | throw new DKIMSignerException("The public key "+tag+" in RR "+recordname+" couldn't be decoded.");
144 | }
145 |
146 | // FSTODO: create test signature with privKey and test validation with pubKey to check on a valid key pair
147 |
148 | return true;
149 | }
150 | }
151 |
152 | throw new DKIMSignerException("No public key available in "+recordname);
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/src/de/agitos/dkim/SMTPDKIMMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 The Apache Software Foundation or its licensors, as
3 | * applicable.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008
18 | */
19 |
20 | package de.agitos.dkim;
21 |
22 | import java.io.ByteArrayOutputStream;
23 | import java.io.IOException;
24 | import java.io.InputStream;
25 | import java.io.OutputStream;
26 | import java.util.Enumeration;
27 |
28 | import javax.mail.MessagingException;
29 | import javax.mail.Session;
30 | import javax.mail.internet.MimeMessage;
31 | import javax.mail.internet.MimeUtility;
32 |
33 | import com.sun.mail.smtp.SMTPMessage;
34 | import com.sun.mail.util.LineOutputStream;
35 |
36 | /*
37 | * Extension of SMTPMessage for the inclusion of a DKIM signature.
38 | *
39 | * @author Florian Sager, http://www.agitos.de, 22.11.2008
40 | */
41 |
42 | public class SMTPDKIMMessage extends SMTPMessage {
43 |
44 | private DKIMSigner signer;
45 | private String encodedBody;
46 |
47 | public SMTPDKIMMessage(Session session, DKIMSigner signer) {
48 | super(session);
49 | this.signer = signer;
50 | }
51 |
52 | public SMTPDKIMMessage(MimeMessage message, DKIMSigner signer) throws MessagingException {
53 | super(message);
54 | this.signer = signer;
55 | }
56 |
57 | public SMTPDKIMMessage(Session session, InputStream is, DKIMSigner signer) throws MessagingException {
58 | super(session, is);
59 | this.signer = signer;
60 | }
61 |
62 | /**
63 | * Output the message as an RFC 822 format stream, without
64 | * specified headers. If the saved
flag is not set,
65 | * the saveChanges
method is called.
66 | * If the modified
flag is not
67 | * set and the content
array is not null, the
68 | * content
array is written directly, after
69 | * writing the appropriate message headers.
70 | *
71 | * @exception javax.mail.MessagingException
72 | * @exception IOException if an error occurs writing to the stream
73 | * or if an error is generated by the
74 | * javax.activation layer.
75 | * @see javax.activation.DataHandler#writeTo
76 | *
77 | * This method enhances the JavaMail method MimeMessage.writeTo(OutputStream os String[] ignoreList);
78 | * See the according Sun Licence, this contribution is CDDL.
79 | */
80 | public void writeTo(OutputStream os, String[] ignoreList) throws IOException, MessagingException {
81 |
82 | ByteArrayOutputStream osBody = new ByteArrayOutputStream();
83 |
84 | // Inside saveChanges() it is assured that content encodings are set in all parts of the body
85 | if (!saved) {
86 | saveChanges();
87 | }
88 |
89 | // First, write out the body to the body buffer
90 | if (modified) {
91 | // Finally, the content. Encode if required.
92 | // XXX: May need to account for ESMTP ?
93 | OutputStream osEncoding = MimeUtility.encode(osBody, this.getEncoding());
94 | this.getDataHandler().writeTo(osEncoding);
95 | osEncoding.flush(); // Needed to complete encoding
96 | } else {
97 | // Else, the content is untouched, so we can just output it
98 | // Finally, the content.
99 | if (content == null) {
100 | // call getContentStream to give subclass a chance to
101 | // provide the data on demand
102 | InputStream is = getContentStream();
103 | // now copy the data to the output stream
104 | byte[] buf = new byte[8192];
105 | int len;
106 | while ((len = is.read(buf)) > 0)
107 | osBody.write(buf, 0, len);
108 | is.close();
109 | buf = null;
110 | } else {
111 | osBody.write(content);
112 | }
113 | osBody.flush();
114 | }
115 | encodedBody = osBody.toString();
116 |
117 | // Second, sign the message
118 | String signatureHeaderLine;
119 | try {
120 | signatureHeaderLine = signer.sign(this);
121 | } catch (Exception e) {
122 | throw new MessagingException(e.getLocalizedMessage(), e);
123 | }
124 |
125 | // Third, write out the header to the header buffer
126 | LineOutputStream los = new LineOutputStream(os);
127 |
128 | // set generated signature to the top
129 | los.writeln(signatureHeaderLine);
130 |
131 | Enumeration hdrLines = getNonMatchingHeaderLines(ignoreList);
132 | while (hdrLines.hasMoreElements()) {
133 | los.writeln((String) hdrLines.nextElement());
134 | }
135 |
136 | // The CRLF separator between header and content
137 | los.writeln();
138 |
139 | // Send signed mail to waiting DATA command
140 | os.write(osBody.toByteArray());
141 | os.flush();
142 | }
143 |
144 | public String getEncodedBody() {
145 | return encodedBody;
146 | }
147 |
148 | public void setEncodedBody(String encodedBody) {
149 | this.encodedBody = encodedBody;
150 | }
151 |
152 | // Don't allow to switch to 8-bit MIME, instead 7-bit ascii should be kept
153 | // 'cause in forwarding scenarios a change to Content-Transfer-Encoding
154 | // to 7-bit ascii breaks DKIM signatures
155 | public void setAllow8bitMIME(boolean allow) {
156 | // super.setAllow8bitMIME(false);
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/de/agitos/dkim/SigningAlgorithm.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 The Apache Software Foundation or its licensors, as
3 | * applicable.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008
18 | */
19 |
20 | package de.agitos.dkim;
21 |
22 | /*
23 | * Allowed signing algorithms by DKIM RFC 4871 with translation to different Java notations
24 | *
25 | * @author Florian Sager, http://www.agitos.de, 22.11.2008
26 | */
27 |
28 | public class SigningAlgorithm {
29 |
30 | public static SigningAlgorithm SHA256withRSA = new SigningAlgorithm("rsa-sha256", "SHA256withRSA", "sha-256");
31 | public static SigningAlgorithm SHA1withRSA = new SigningAlgorithm("rsa-sha1", "SHA1withRSA", "sha-1");
32 |
33 | private String rfc4871Notation;
34 | private String javaSecNotation;
35 | private String javaHashNotation;
36 |
37 | // 1. argument: RFC 4871 format, 2. argument: java representation, 3. argument: java hashing digest
38 | public SigningAlgorithm(String rfc4871Notation, String javaSecNotation, String javaHashNotation) {
39 | this.rfc4871Notation = rfc4871Notation;
40 | this.javaSecNotation = javaSecNotation;
41 | this.javaHashNotation = javaHashNotation;
42 | }
43 |
44 | public String getJavaHashNotation() {
45 | return javaHashNotation;
46 | }
47 |
48 | public String getJavaSecNotation() {
49 | return javaSecNotation;
50 | }
51 |
52 | public String getRfc4871Notation() {
53 | return rfc4871Notation;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/test.properties.template:
--------------------------------------------------------------------------------
1 | # your mail server
2 | mail.smtp.host=mail.myisp.com
3 |
4 | # set auth to true if SMTP-AUTH is needed
5 | mail.smtp.auth=true
6 |
7 | # your username for SMTP-AUTH
8 | mail.smtp.auth.user=myuser@mydomain.com
9 |
10 | # your password for SMTP-AUTH
11 | mail.smtp.auth.password=mypassword
12 |
13 | # the string to use in the From header of your test mails
14 | mail.smtp.from=myuser@mydomain.com
15 |
16 | # the address to which your test mails should be sent. not required if you have a CC address
17 | # mail.smtp.to=sa-test@sendmail.net --> get check results by this autoresponder address
18 | mail.smtp.to=myself@mydomain.com
19 |
20 | # the address to which your test mails should be CC'd, if desired. not requred if you have a To addess
21 | # mail.smtp.cc=sa-test@sendmail.net --> get check results by this autoresponder address
22 | #mail.smtp.cc=myself@mydomain.com
23 |
24 | # a file that can be sent in the attachment in FullExample
25 | mail.smtp.attachment=lib/dsn.jar
26 |
27 | # the signing domain whose zone contains the public key record
28 | mail.smtp.dkim.signingdomain=mydomain.com
29 |
30 | # the selector in the DNS zone, e.g. default._domainkey ...
31 | mail.smtp.dkim.selector=default
32 |
33 | # your private key
34 | mail.smtp.dkim.privatekey=keys/default.private.der
35 |
--------------------------------------------------------------------------------
/test/de/agitos/dkim/FullExample.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 The Apache Software Foundation or its licensors, as
3 | * applicable.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008
18 | */
19 |
20 | package de.agitos.dkim;
21 |
22 | import java.util.Properties;
23 |
24 | import javax.mail.Message;
25 | import javax.mail.Multipart;
26 | import javax.mail.Session;
27 | import javax.mail.Transport;
28 | import javax.mail.internet.InternetAddress;
29 | import javax.mail.internet.MimeBodyPart;
30 | import javax.mail.internet.MimeMultipart;
31 |
32 | /*
33 | * This example sends a DKIM signed email with standard signature configuration.
34 | * This version of DKIM for JavaMail was tested with JavaMail 1.4.1, downward compatibility with 1.3 is expected.
35 | *
36 | * @author Florian Sager, http://www.agitos.de, 22.11.2008
37 | */
38 |
39 | public class FullExample {
40 |
41 | public static void main(String args[]) throws Exception {
42 |
43 | // read test configuration from test.properties in your classpath
44 | Properties testProps = TestUtil.readProperties();
45 |
46 | // get a JavaMail Session object
47 | Session session = Session.getDefaultInstance(testProps, null);
48 |
49 |
50 |
51 | ///////// beginning of DKIM FOR JAVAMAIL stuff
52 |
53 | // get DKIMSigner object
54 | DKIMSigner dkimSigner = new DKIMSigner(
55 | testProps.getProperty("mail.smtp.dkim.signingdomain"),
56 | testProps.getProperty("mail.smtp.dkim.selector"),
57 | testProps.getProperty("mail.smtp.dkim.privatekey"));
58 |
59 | /* set an address or user-id of the user on behalf this message was signed;
60 | * this identity is up to you, except the domain part must be the signing domain
61 | * or a subdomain of the signing domain.
62 | */
63 | dkimSigner.setIdentity("fullexample@"+testProps.getProperty("mail.smtp.dkim.signingdomain"));
64 |
65 | // get default
66 | System.out.println("Default headers getting signed if available:");
67 | TestUtil.printArray(dkimSigner.getDefaultHeadersToSign());
68 |
69 | // the following header will be signed as well if available
70 | dkimSigner.addHeaderToSign("ASpecialHeader");
71 |
72 | // the following header won't be signed
73 | dkimSigner.removeHeaderToSign("Content-Type");
74 |
75 | // change default canonicalizations
76 | dkimSigner.setHeaderCanonicalization(Canonicalization.SIMPLE);
77 | dkimSigner.setBodyCanonicalization(Canonicalization.RELAXED);
78 |
79 | // add length param to the signature, see RFC 4871
80 | dkimSigner.setLengthParam(true);
81 |
82 | // change default signing algorithm
83 | dkimSigner.setSigningAlgorithm(SigningAlgorithm.SHA1withRSA);
84 |
85 | // add a list of header=value pairs to the signature for debugging reasons
86 | dkimSigner.setZParam(true);
87 |
88 | ///////// end of DKIM FOR JAVAMAIL stuff
89 |
90 |
91 |
92 |
93 | // construct the JavaMail message using the DKIM message type from DKIM for JavaMail
94 | Message msg = new SMTPDKIMMessage(session, dkimSigner);
95 | Multipart mp = new MimeMultipart();
96 | msg.setFrom(new InternetAddress(testProps.getProperty("mail.smtp.from")));
97 | if (testProps.getProperty("mail.smtp.to") != null) {
98 | msg.setRecipients(Message.RecipientType.TO,
99 | InternetAddress.parse(testProps.getProperty("mail.smtp.to"), false));
100 | }
101 | if (testProps.getProperty("mail.smtp.cc") != null) {
102 | msg.setRecipients(Message.RecipientType.CC,
103 | InternetAddress.parse(testProps.getProperty("mail.smtp.cc"), false));
104 | }
105 |
106 | msg.setSubject("DKIM for JavaMail: FullExample Testmessage");
107 |
108 | MimeBodyPart mbp_msgtext = new MimeBodyPart();
109 | mbp_msgtext.setText(TestUtil.bodyText);
110 | mp.addBodyPart(mbp_msgtext);
111 |
112 | TestUtil.addFileAttachment(mp, testProps.get("mail.smtp.attachment"));
113 |
114 | msg.setContent(mp);
115 |
116 | // send the message by JavaMail
117 | Transport transport = session.getTransport("smtp"); // or smtps ( = TLS)
118 | transport.connect(testProps.getProperty("mail.smtp.host"),
119 | testProps.getProperty("mail.smtp.auth.user"),
120 | testProps.getProperty("mail.smtp.auth.password"));
121 | transport.sendMessage(msg, msg.getAllRecipients());
122 | transport.close();
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/test/de/agitos/dkim/MimeMailExample.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 The Apache Software Foundation or its licensors, as
3 | * applicable.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008
18 | */
19 |
20 | package de.agitos.dkim;
21 |
22 | import java.io.ByteArrayInputStream;
23 | import java.util.Date;
24 | import java.util.Properties;
25 |
26 | import javax.mail.Message;
27 | import javax.mail.Session;
28 | import javax.mail.Transport;
29 | import javax.mail.internet.MailDateFormat;
30 |
31 | /*
32 | * This example signs and sends an existing email with standard DKIM signature configuration.
33 | * This version of DKIM for JavaMail was tested with JavaMail 1.4.1, downward compatibility with 1.3 is expected.
34 | *
35 | * @author Florian Sager, http://www.agitos.de, 10.05.2009
36 | */
37 |
38 | public class MimeMailExample {
39 |
40 | public static void main(String args[]) throws Exception {
41 |
42 | // read test configuration from test.properties in your classpath
43 | Properties testProps = TestUtil.readProperties();
44 |
45 | // generate string buffered test mail
46 | StringBuffer mimeMail = new StringBuffer();
47 | mimeMail.append("Date: ").append(new MailDateFormat().format(new Date())).append("\r\n");
48 | mimeMail.append("From: ").append(testProps.getProperty("mail.smtp.from")).append("\r\n");
49 | if (testProps.getProperty("mail.smtp.to") != null) {
50 | mimeMail.append("To: ").append(testProps.getProperty("mail.smtp.to")).append("\r\n");
51 | }
52 | if (testProps.getProperty("mail.smtp.cc") != null) {
53 | mimeMail.append("Cc: ").append(testProps.getProperty("mail.smtp.cc")).append("\r\n");
54 | }
55 | mimeMail.append("Subject: ").append("DKIM for JavaMail: MimeMailExample Testmessage").append("\r\n");
56 | mimeMail.append("\r\n");
57 | mimeMail.append(TestUtil.bodyText);
58 |
59 | // get a JavaMail Session object
60 | Session session = Session.getDefaultInstance(testProps, null);
61 |
62 |
63 |
64 | ///////// beginning of DKIM FOR JAVAMAIL stuff
65 |
66 | // get DKIMSigner object
67 | DKIMSigner dkimSigner = new DKIMSigner(
68 | testProps.getProperty("mail.smtp.dkim.signingdomain"),
69 | testProps.getProperty("mail.smtp.dkim.selector"),
70 | testProps.getProperty("mail.smtp.dkim.privatekey"));
71 |
72 | /* set an address or user-id of the user on behalf this message was signed;
73 | * this identity is up to you, except the domain part must be the signing domain
74 | * or a subdomain of the signing domain.
75 | */
76 | dkimSigner.setIdentity("mimemailexample@"+testProps.getProperty("mail.smtp.dkim.signingdomain"));
77 |
78 | // construct the JavaMail message using the DKIM message type from DKIM for JavaMail
79 | Message msg = new SMTPDKIMMessage(session, new ByteArrayInputStream(mimeMail.toString().getBytes()), dkimSigner);
80 |
81 | ///////// end of DKIM FOR JAVAMAIL stuff
82 |
83 | // send the message by JavaMail
84 | Transport transport = session.getTransport("smtp");
85 | transport.connect(testProps.getProperty("mail.smtp.host"),
86 | testProps.getProperty("mail.smtp.auth.user"),
87 | testProps.getProperty("mail.smtp.auth.password"));
88 | transport.sendMessage(msg, msg.getAllRecipients());
89 | transport.close();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/test/de/agitos/dkim/MultipleMailExample.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 The Apache Software Foundation or its licensors, as
3 | * applicable.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008
18 | */
19 |
20 | package de.agitos.dkim;
21 |
22 | import java.util.Properties;
23 |
24 | import javax.mail.Message;
25 | import javax.mail.Session;
26 | import javax.mail.Transport;
27 | import javax.mail.internet.InternetAddress;
28 |
29 | /*
30 | * This example sends multiple DKIM signed emails with standard signature configuration.
31 | * This version of DKIM for JavaMail was tested with JavaMail 1.4.1, downward compatibility with 1.3 is expected.
32 | *
33 | * @author Florian Sager, http://www.agitos.de, 22.11.2008
34 | */
35 |
36 | public class MultipleMailExample {
37 |
38 | public static void main(String args[]) throws Exception {
39 |
40 | // read test configuration from test.properties in your classpath
41 | Properties testProps = TestUtil.readProperties();
42 |
43 | // get a JavaMail Session object
44 | Session session = Session.getDefaultInstance(testProps, null);
45 |
46 | Transport transport = session.getTransport("smtp");
47 | transport.connect(testProps.getProperty("mail.smtp.host"),
48 | testProps.getProperty("mail.smtp.auth.user"),
49 | testProps.getProperty("mail.smtp.auth.password"));
50 |
51 |
52 |
53 | ///////// beginning of DKIM FOR JAVAMAIL stuff
54 |
55 | // get DKIMSigner object
56 | DKIMSigner dkimSigner = new DKIMSigner(
57 | testProps.getProperty("mail.smtp.dkim.signingdomain"),
58 | testProps.getProperty("mail.smtp.dkim.selector"),
59 | testProps.getProperty("mail.smtp.dkim.privatekey"));
60 |
61 | for (int i=0; i<3; i++) {
62 |
63 | /* set an address or user-id of the user on behalf this message was signed;
64 | * this identity is up to you, except the domain part must be the signing domain
65 | * or a subdomain of the signing domain.
66 | */
67 | dkimSigner.setIdentity("multipleexample"+i+"@"+testProps.getProperty("mail.smtp.dkim.signingdomain"));
68 |
69 | // construct the JavaMail message using the DKIM message type from DKIM for JavaMail
70 | Message msg = new SMTPDKIMMessage(session, dkimSigner);
71 |
72 | ///////// end of DKIM FOR JAVAMAIL stuff
73 |
74 |
75 |
76 | msg.setFrom(new InternetAddress(testProps.getProperty("mail.smtp.from")));
77 | if (testProps.getProperty("mail.smtp.to") != null) {
78 | msg.setRecipients(Message.RecipientType.TO,
79 | InternetAddress.parse(testProps.getProperty("mail.smtp.to"), false));
80 | }
81 | if (testProps.getProperty("mail.smtp.cc") != null) {
82 | msg.setRecipients(Message.RecipientType.CC,
83 | InternetAddress.parse(testProps.getProperty("mail.smtp.cc"), false));
84 | }
85 |
86 | msg.setSubject("DKIM for JavaMail: MultipleExample Testmessage "+i);
87 | msg.setText(TestUtil.bodyText);
88 |
89 | // send the message by JavaMail
90 | transport.sendMessage(msg, msg.getAllRecipients());
91 | }
92 |
93 | transport.close();
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/test/de/agitos/dkim/SimpleExample.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 The Apache Software Foundation or its licensors, as
3 | * applicable.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008
18 | */
19 |
20 | package de.agitos.dkim;
21 |
22 | import java.util.Properties;
23 |
24 | import javax.mail.Message;
25 | import javax.mail.Session;
26 | import javax.mail.Transport;
27 | import javax.mail.internet.InternetAddress;
28 |
29 | /*
30 | * This example sends a DKIM signed email with standard signature configuration.
31 | * This version of DKIM for JavaMail was tested with JavaMail 1.4.1, downward compatibility with 1.3 is expected.
32 | *
33 | * @author Florian Sager, http://www.agitos.de, 22.11.2008
34 | */
35 |
36 | public class SimpleExample {
37 |
38 | public static void main(String args[]) throws Exception {
39 |
40 | // read test configuration from test.properties in your classpath
41 | Properties testProps = TestUtil.readProperties();
42 |
43 | // get a JavaMail Session object
44 | Session session = Session.getDefaultInstance(testProps, null);
45 |
46 |
47 |
48 | ///////// beginning of DKIM FOR JAVAMAIL stuff
49 |
50 | // get DKIMSigner object
51 | DKIMSigner dkimSigner = new DKIMSigner(
52 | testProps.getProperty("mail.smtp.dkim.signingdomain"),
53 | testProps.getProperty("mail.smtp.dkim.selector"),
54 | testProps.getProperty("mail.smtp.dkim.privatekey"));
55 |
56 | /* set an address or user-id of the user on behalf this message was signed;
57 | * this identity is up to you, except the domain part must be the signing domain
58 | * or a subdomain of the signing domain.
59 | */
60 | dkimSigner.setIdentity("simpleexample@"+testProps.getProperty("mail.smtp.dkim.signingdomain"));
61 |
62 | // construct the JavaMail message using the DKIM message type from DKIM for JavaMail
63 | Message msg = new SMTPDKIMMessage(session, dkimSigner);
64 |
65 | ///////// end of DKIM FOR JAVAMAIL stuff
66 |
67 |
68 |
69 | msg.setFrom(new InternetAddress(testProps.getProperty("mail.smtp.from")));
70 | if (testProps.getProperty("mail.smtp.to") != null) {
71 | msg.setRecipients(Message.RecipientType.TO,
72 | InternetAddress.parse(testProps.getProperty("mail.smtp.to"), false));
73 | }
74 | if (testProps.getProperty("mail.smtp.cc") != null) {
75 | msg.setRecipients(Message.RecipientType.CC,
76 | InternetAddress.parse(testProps.getProperty("mail.smtp.cc"), false));
77 | }
78 |
79 | msg.setSubject("DKIM for JavaMail: SimpleExample Testmessage");
80 | msg.setText(TestUtil.bodyText);
81 |
82 | // send the message by JavaMail
83 | Transport transport = session.getTransport("smtp");
84 | transport.connect(testProps.getProperty("mail.smtp.host"),
85 | testProps.getProperty("mail.smtp.auth.user"),
86 | testProps.getProperty("mail.smtp.auth.password"));
87 | transport.sendMessage(msg, msg.getAllRecipients());
88 | transport.close();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/test/de/agitos/dkim/TestUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 The Apache Software Foundation or its licensors, as
3 | * applicable.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | * A licence was granted to the ASF by Florian Sager on 30 November 2008
18 | */
19 |
20 | package de.agitos.dkim;
21 |
22 | import java.io.File;
23 | import java.io.FileInputStream;
24 | import java.util.Properties;
25 |
26 | import javax.activation.DataHandler;
27 | import javax.activation.FileDataSource;
28 | import javax.mail.MessagingException;
29 | import javax.mail.Multipart;
30 | import javax.mail.internet.MimeBodyPart;
31 |
32 | /*
33 | * @author Florian Sager, http://www.agitos.de, 22.11.2008
34 | */
35 |
36 | public class TestUtil {
37 |
38 | public static String bodyText = "Hello,\r\n\r\nthis email was signed by the DKIM for JavaMail library.\r\n\r\nTo check the validity of DKIM signatures, send the generated emails directly to a DKIM test address like sa-test@sendmail.net with your personal email address in the From header.\r\n\r\nPromotional stuff: see www.dkim-reputation.org and consider using DKIM reputation for an improved spam filtering, especially for the reduction of false positives.\r\n\r\nRegards,\r\nFlorian Sager, www.agitos.de\r\n\r\n";
39 |
40 | public static Properties readProperties() {
41 |
42 | Properties props = new Properties();
43 | try {
44 | props.load(new FileInputStream("test.properties"));
45 | } catch (Exception e) {
46 | msgAndExit("Check if the test configuration file 'test.properties' is in your classpath and if it's readable");
47 | }
48 | return props;
49 | }
50 |
51 | public static void printArray(String[] a) {
52 |
53 | for (int i=0; i