1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.james.mailet.crypto.mailet;
23
24 import org.apache.james.mailet.crypto.KeyHolder;
25 import org.apache.james.mailet.crypto.SMIMEAttributeNames;
26 import org.apache.mailet.base.GenericMailet;
27 import org.apache.mailet.Mail;
28 import org.apache.mailet.MailAddress;
29 import org.apache.mailet.base.RFC2822Headers;
30
31 import javax.mail.MessagingException;
32 import javax.mail.Session;
33 import javax.mail.internet.InternetAddress;
34 import javax.mail.internet.MimeBodyPart;
35 import javax.mail.internet.MimeMessage;
36 import javax.mail.internet.MimeMultipart;
37 import javax.mail.internet.ParseException;
38
39 import java.io.IOException;
40 import java.util.Enumeration;
41 import java.lang.reflect.Constructor;
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101 public abstract class AbstractSign extends GenericMailet {
102
103 private static final String HEADERS_PATTERN = "[headers]";
104
105 private static final String SIGNER_NAME_PATTERN = "[signerName]";
106
107 private static final String SIGNER_ADDRESS_PATTERN = "[signerAddress]";
108
109 private static final String REVERSE_PATH_PATTERN = "[reversePath]";
110
111
112
113
114 private boolean debug;
115
116
117
118
119 private Class keyHolderClass;
120
121
122
123
124 private String explanationText;
125
126
127
128
129 private KeyHolder keyHolder;
130
131
132
133
134 private boolean postmasterSigns;
135
136
137
138
139 private boolean rebuildFrom;
140
141
142
143
144 private String signerName;
145
146
147
148
149
150 protected abstract String[] getAllowedInitParameters();
151
152
153
154
155
156
157
158
159 protected void initDebug() {
160 setDebug((getInitParameter("debug") == null) ? false : new Boolean(getInitParameter("debug")).booleanValue());
161 }
162
163
164
165
166
167 public boolean isDebug() {
168 return this.debug;
169 }
170
171
172
173
174
175 public void setDebug(boolean debug) {
176 this.debug = debug;
177 }
178
179
180
181
182 protected void initKeyHolderClass() throws MessagingException {
183 String keyHolderClassName = getInitParameter("keyHolderClass");
184 if (keyHolderClassName == null) {
185 throw new MessagingException("<keyHolderClass> parameter missing.");
186 }
187 try {
188 setKeyHolderClass(Class.forName(keyHolderClassName));
189 } catch (ClassNotFoundException cnfe) {
190 throw new MessagingException("The specified <keyHolderClass> does not exist: " + keyHolderClassName);
191 }
192 if (isDebug()) {
193 log("keyHolderClass: " + getKeyHolderClass());
194 }
195 }
196
197
198
199
200
201 public Class getKeyHolderClass() {
202 return this.keyHolderClass;
203 }
204
205
206
207
208
209 public void setKeyHolderClass(Class keyHolderClass) {
210 this.keyHolderClass = keyHolderClass;
211 }
212
213
214
215
216 protected void initExplanationText() {
217 setExplanationText(getInitParameter("explanationText"));
218 if (isDebug()) {
219 log("Explanation text:\r\n" + getExplanationText());
220 }
221 }
222
223
224
225
226
227
228 public String getExplanationText() {
229 return this.explanationText;
230 }
231
232
233
234
235
236 public void setExplanationText(String explanationText) {
237 this.explanationText = explanationText;
238 }
239
240
241
242
243 protected void initKeyHolder() throws Exception {
244 Constructor keyHolderConstructor = null;
245 try {
246 keyHolderConstructor = keyHolderClass.getConstructor(new Class[] {String.class, String.class, String.class, String.class, String.class});
247 } catch (NoSuchMethodException nsme) {
248 throw new MessagingException("The needed constructor does not exist: "
249 + keyHolderClass + "(String, String, String, String, String)");
250 }
251
252
253 String keyStoreFileName = getInitParameter("keyStoreFileName");
254 if (keyStoreFileName == null) {
255 throw new MessagingException("<keyStoreFileName> parameter missing.");
256 }
257
258 String keyStorePassword = getInitParameter("keyStorePassword");
259 if (keyStorePassword == null) {
260 throw new MessagingException("<keyStorePassword> parameter missing.");
261 }
262 String keyAliasPassword = getInitParameter("keyAliasPassword");
263 if (keyAliasPassword == null) {
264 keyAliasPassword = keyStorePassword;
265 if (isDebug()) {
266 log("<keyAliasPassword> parameter not specified: will default to the <keyStorePassword> parameter.");
267 }
268 }
269
270 String keyStoreType = getInitParameter("keyStoreType");
271 if (keyStoreType == null) {
272 if (isDebug()) {
273 log("<keyStoreType> parameter not specified: the default will be as appropriate to the keyStore requested.");
274 }
275 }
276
277 String keyAlias = getInitParameter("keyAlias");
278 if (keyAlias == null) {
279 if (isDebug()) {
280 log("<keyAlias> parameter not specified: will look for the first one in the keystore.");
281 }
282 }
283
284 if (isDebug()) {
285 StringBuffer logBuffer =
286 new StringBuffer(1024)
287 .append("KeyStore related parameters:")
288 .append(" keyStoreFileName=").append(keyStoreFileName)
289 .append(", keyStoreType=").append(keyStoreType)
290 .append(", keyAlias=").append(keyAlias)
291 .append(" ");
292 log(logBuffer.toString());
293 }
294
295
296 String[] parameters = {keyStoreFileName, keyStorePassword, keyAlias, keyAliasPassword, keyStoreType};
297 setKeyHolder((KeyHolder)keyHolderConstructor.newInstance(parameters));
298
299 if (isDebug()) {
300 log("Subject Distinguished Name: " + getKeyHolder().getSignerDistinguishedName());
301 }
302
303 if (getKeyHolder().getSignerAddress() == null) {
304 throw new MessagingException("Signer address missing in the certificate.");
305 }
306 }
307
308
309
310
311
312
313 protected KeyHolder getKeyHolder() {
314 return this.keyHolder;
315 }
316
317
318
319
320
321
322 protected void setKeyHolder(KeyHolder keyHolder) {
323 this.keyHolder = keyHolder;
324 }
325
326
327
328
329 protected void initPostmasterSigns() {
330 setPostmasterSigns((getInitParameter("postmasterSigns") == null) ? false : new Boolean(getInitParameter("postmasterSigns")).booleanValue());
331 }
332
333
334
335
336
337
338 public boolean isPostmasterSigns() {
339 return this.postmasterSigns;
340 }
341
342
343
344
345
346 public void setPostmasterSigns(boolean postmasterSigns) {
347 this.postmasterSigns = postmasterSigns;
348 }
349
350
351
352
353 protected void initRebuildFrom() throws MessagingException {
354 setRebuildFrom((getInitParameter("rebuildFrom") == null) ? false : new Boolean(getInitParameter("rebuildFrom")).booleanValue());
355 if (isDebug()) {
356 if (isRebuildFrom()) {
357 log("Will modify the \"From:\" header.");
358 } else {
359 log("Will leave the \"From:\" header unchanged.");
360 }
361 }
362 }
363
364
365
366
367
368
369
370
371
372
373
374
375
376 public boolean isRebuildFrom() {
377 return this.rebuildFrom;
378 }
379
380
381
382
383
384 public void setRebuildFrom(boolean rebuildFrom) {
385 this.rebuildFrom = rebuildFrom;
386 }
387
388
389
390
391 protected void initSignerName() {
392 setSignerName(getInitParameter("signerName"));
393 if (getSignerName() == null) {
394 if (getKeyHolder() == null) {
395 throw new RuntimeException("initKeyHolder() must be invoked before initSignerName()");
396 }
397 setSignerName(getKeyHolder().getSignerCN());
398 if (isDebug()) {
399 log("<signerName> parameter not specified: will use the certificate signer \"CN=\" attribute.");
400 }
401 }
402 }
403
404
405
406
407
408 public String getSignerName() {
409 return this.signerName;
410 }
411
412
413
414
415
416 public void setSignerName(String signerName) {
417 this.signerName = signerName;
418 }
419
420
421
422
423
424
425
426
427 public void init() throws MessagingException {
428
429
430 checkInitParameters(getAllowedInitParameters());
431
432 try {
433 initDebug();
434 if (isDebug()) {
435 log("Initializing");
436 }
437
438 initKeyHolderClass();
439 initKeyHolder();
440 initSignerName();
441 initPostmasterSigns();
442 initRebuildFrom();
443 initExplanationText();
444
445
446 } catch (MessagingException me) {
447 throw me;
448 } catch (Exception e) {
449 log("Exception thrown", e);
450 throw new MessagingException("Exception thrown", e);
451 } finally {
452 if (isDebug()) {
453 StringBuffer logBuffer =
454 new StringBuffer(1024)
455 .append("Other parameters:")
456 .append(", signerName=").append(getSignerName())
457 .append(", postmasterSigns=").append(postmasterSigns)
458 .append(", rebuildFrom=").append(rebuildFrom)
459 .append(" ");
460 log(logBuffer.toString());
461 }
462 }
463
464 }
465
466
467
468
469
470
471
472 public void service(Mail mail) throws MessagingException {
473
474 try {
475 if (!isOkToSign(mail)) {
476 return;
477 }
478
479 MimeBodyPart wrapperBodyPart = getWrapperBodyPart(mail);
480
481 MimeMessage originalMessage = mail.getMessage();
482
483
484 MimeMultipart signedMimeMultipart;
485 if (wrapperBodyPart != null) {
486 signedMimeMultipart = getKeyHolder().generate(wrapperBodyPart);
487 } else {
488 signedMimeMultipart = getKeyHolder().generate(originalMessage);
489 }
490
491 MimeMessage newMessage = new MimeMessage(Session.getDefaultInstance(System.getProperties(),
492 null));
493 Enumeration headerEnum = originalMessage.getAllHeaderLines();
494 while (headerEnum.hasMoreElements()) {
495 newMessage.addHeaderLine((String) headerEnum.nextElement());
496 }
497
498 newMessage.setSender(new InternetAddress(getKeyHolder().getSignerAddress(), getSignerName()));
499
500 if (isRebuildFrom()) {
501
502 InternetAddress modifiedFromIA = new InternetAddress(getKeyHolder().getSignerAddress(), mail.getSender().toString());
503 newMessage.setFrom(modifiedFromIA);
504
505
506 newMessage.setReplyTo(originalMessage.getReplyTo());
507 }
508
509 newMessage.setContent(signedMimeMultipart, signedMimeMultipart.getContentType());
510 String messageId = originalMessage.getMessageID();
511 newMessage.saveChanges();
512 if (messageId != null) {
513 newMessage.setHeader(RFC2822Headers.MESSAGE_ID, messageId);
514 }
515
516 mail.setMessage(newMessage);
517
518
519 mail.setAttribute(SMIMEAttributeNames.SMIME_SIGNING_MAILET, this.getClass().getName());
520
521 mail.setAttribute(SMIMEAttributeNames.SMIME_SIGNATURE_VALIDITY, "valid");
522
523
524
525 mail.setAttribute(SMIMEAttributeNames.SMIME_SIGNER_ADDRESS, getKeyHolder().getSignerAddress());
526
527 if (isDebug()) {
528 log("Message signed, reverse-path: " + mail.getSender() + ", Id: " + messageId);
529 }
530
531 } catch (MessagingException me) {
532 log("MessagingException found - could not sign!", me);
533 throw me;
534 } catch (Exception e) {
535 log("Exception found", e);
536 throw new MessagingException("Exception thrown - could not sign!", e);
537 }
538
539 }
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560 protected boolean isOkToSign(Mail mail) throws MessagingException {
561
562 MailAddress reversePath = mail.getSender();
563
564
565 if (reversePath == null) {
566 return false;
567 }
568
569 String authUser = (String) mail.getAttribute("org.apache.james.SMTPAuthUser");
570
571 if (authUser == null) {
572 return false;
573 }
574
575
576 if (getMailetContext().getPostmaster().equals(reversePath)) {
577
578 if (!isPostmasterSigns()) {
579 return false;
580 }
581 } else {
582
583 if (!reversePath.getUser().equals(authUser)) {
584 return false;
585 }
586
587 if (!fromAddressSameAsReverse(mail)) {
588 return false;
589 }
590 }
591
592
593
594 MimeMessage mimeMessage = mail.getMessage();
595 if (mimeMessage.isMimeType("multipart/signed")
596 || mimeMessage.isMimeType("application/pkcs7-mime")) {
597 return false;
598 }
599
600 return true;
601 }
602
603
604
605
606
607
608
609
610 protected abstract MimeBodyPart getWrapperBodyPart(Mail mail) throws MessagingException, IOException;
611
612
613
614
615
616
617
618 protected final boolean fromAddressSameAsReverse(Mail mail) {
619
620 MailAddress reversePath = mail.getSender();
621
622 if (reversePath == null) {
623 return false;
624 }
625
626 try {
627 InternetAddress[] fromArray = (InternetAddress[]) mail.getMessage().getFrom();
628 if (fromArray != null) {
629 for (int i = 0; i < fromArray.length; i++) {
630 MailAddress mailAddress = null;
631 try {
632 mailAddress = new MailAddress(fromArray[i]);
633 } catch (ParseException pe) {
634 log("Unable to parse a \"FROM\" header address: " + fromArray[i].toString() + "; ignoring.");
635 continue;
636 }
637 if (mailAddress.equals(reversePath)) {
638 return true;
639 }
640 }
641 }
642 } catch (MessagingException me) {
643 log("Unable to parse the \"FROM\" header; ignoring.");
644 }
645
646 return false;
647
648 }
649
650
651
652
653
654
655 protected final String getMessageHeaders(MimeMessage message) throws MessagingException {
656 Enumeration heads = message.getAllHeaderLines();
657 StringBuffer headBuffer = new StringBuffer(1024);
658 while(heads.hasMoreElements()) {
659 headBuffer.append(heads.nextElement().toString()).append("\r\n");
660 }
661 return headBuffer.toString();
662 }
663
664
665
666
667
668
669
670
671
672
673
674
675 protected final String getReplacedExplanationText(String explanationText, String signerName,
676 String signerAddress, String reversePath, String headers) {
677
678 String replacedExplanationText = explanationText;
679
680 replacedExplanationText = getReplacedString(replacedExplanationText, SIGNER_NAME_PATTERN, signerName);
681 replacedExplanationText = getReplacedString(replacedExplanationText, SIGNER_ADDRESS_PATTERN, signerAddress);
682 replacedExplanationText = getReplacedString(replacedExplanationText, REVERSE_PATH_PATTERN, reversePath);
683 replacedExplanationText = getReplacedString(replacedExplanationText, HEADERS_PATTERN, headers);
684
685 return replacedExplanationText;
686 }
687
688
689
690
691
692
693
694
695 private String getReplacedString(String template, String pattern, String actual) {
696 if (actual != null) {
697 StringBuffer sb = new StringBuffer(template.length());
698 int fromIndex = 0;
699 int index;
700 while ((index = template.indexOf(pattern, fromIndex)) >= 0) {
701 sb.append(template.substring(fromIndex, index));
702 sb.append(actual);
703 fromIndex = index + pattern.length();
704 }
705 if (fromIndex < template.length()){
706 sb.append(template.substring(fromIndex));
707 }
708 return sb.toString();
709 } else {
710 return new String(template);
711 }
712 }
713
714 }
715