@Override protected Email createFolderElement(File emailFile) throws Exception { InputStream emailStream = null; try { emailStream = new BufferedInputStream(new EncryptedInputStream(new FileInputStream(emailFile), passwordHolder)); InputStream metadataStream = null; File metadataFile = getMetadataFile(emailFile); if (metadataFile.exists()) metadataStream = new BufferedInputStream(new EncryptedInputStream(new FileInputStream(metadataFile), passwordHolder)); Email email = new Email(emailStream, metadataStream, passwordHolder); String messageIdString = emailFile.getName().substring(0, 44); email.setMessageID(messageIdString); return email; } finally { if (emailStream != null) emailStream.close(); } } }
@Override public String toString() { StringBuilder result = new StringBuilder("MsgId: ").append(getMessageID()); try { result = result.append("From: ").append(getOneFromAddress()); result = result.append("Recipients: "); for (Address recipient: getAllRecipients()) { if (result.length() > 1000) { result = result.append("..."); break; } if (result.length() > 0) result = result.append(", "); String recipientAddress = recipient.toString(); if (recipientAddress.length() > 20) result = result.append(recipientAddress).append("..."); else result = result.append(recipientAddress); } } catch (MessagingException e) { log.error("Error getting sender or recipients."); result.append("#Error#"); } return result.toString(); } }
String[] bccHeaders = null; try { bccHeaders = getHeader("BCC"); saveChanges(); if (bccToKeep!=null && isBCC(bccToKeep)) setHeader("BCC", bccToKeep); // set bccToKeep and remove any other existing BCC addresses else removeHeader("BCC"); if (!isAnonymous()) sign(senderIdentity, keyUpdateHandler); compressTo(outputStream); } catch (IOException e) { throw new MessagingException("Can't write the email to an OutputStream.", e); } finally { removeHeader("BCC"); if (bccHeaders != null) for (String bccAddress: bccHeaders) addHeader("BCC", bccAddress);
/** * Verifies the signature and sets the <code>SIGNATURE_VALID_HEADER</code> * header field accordingly. */ public void setSignatureFlag() { try { removeHeader(SIGNATURE_VALID_HEADER); // remove the signature validity flag before verifying boolean valid = verifySignature(); setHeader(SIGNATURE_VALID_HEADER, String.valueOf(valid)); } catch (MessagingException e) { log.error("Cannot get header field: " + SIGNATURE_VALID_HEADER, e); } }
public void removeBoteSuffixes() throws MessagingException { Address[] from = getFrom(); if (from != null) { removeBoteSuffixes(from); setFrom(from[0]); if (from.length > 1) addFrom(Arrays.copyOfRange(from, 1, from.length)); Address sender = getSender(); if (sender != null) { removeBoteSuffix((InternetAddress) sender); setSender(sender); Address[] to = getRecipients(RecipientType.TO); if (to != null) { removeBoteSuffixes(to); setRecipients(RecipientType.TO, to); Address[] cc = getRecipients(RecipientType.CC); if (cc != null) { removeBoteSuffixes(cc); setRecipients(RecipientType.CC, cc); Address[] bcc = getRecipients(RecipientType.BCC); if (bcc != null) { removeBoteSuffixes(bcc); setRecipients(RecipientType.BCC, bcc); Address[] replyTo = getReplyToAddresses(); if (replyTo != null) {
public Collection<Address> getAllAddresses(boolean includeFrom) throws MessagingException { Collection<Address> addresses = new ArrayList<Address>(); // If we want to check validity, fetch these separately // (because these can contain 'anonymous'). if (includeFrom) { addresses.addAll(getAllFromAddresses()); } Address[] recipients = getAllRecipients(); if (recipients != null) addresses.addAll(Arrays.asList(recipients)); // Reply-To should not be anonymous, check with recipients Address[] replyTo = getReplyToAddresses(); if (replyTo != null) addresses.addAll(Arrays.asList(replyTo)); return addresses; }
@Test public void testSendAnonymousEmail() throws Exception { testEmail = new Email(true); testEmail.setFrom(new InternetAddress("anonymous")); testEmail.addRecipient(RecipientType.TO, new InternetAddress("Erika Mustermann <m-5~1dZ0MrGdyAWu-C2ecNAB5LCCsHQpeSfjn-r~mqMfNvroR98~BRmReUDmb0la-r-pBHLMtflrJE7aTrGwDTBm5~AJFEm-9SJPZnyGs-ed5pOj4Db65yJml1y1n77qr1~mM4GITl6KuIoxg8YwvPrCIlXe2hiiDCoC-uY9-np9UY>")); testEmail.setSubject("Test", "UTF-8"); testEmail.setText("foobar"); op.sendEmail(testEmail); ArgumentCaptor<DhtStorablePacket> arg = ArgumentCaptor.forClass(DhtStorablePacket.class); verify(dht, times(2)).store(arg.capture()); List<DhtStorablePacket> values = arg.getAllValues(); assertTrue(values.get(0) instanceof EncryptedEmailPacket); assertTrue(values.get(1) instanceof IndexPacket); assertTrue(((IndexPacket)values.get(1)).contains(((EncryptedEmailPacket)values.get(0)).getDhtKey())); } }
private void testAddEmail(String mailContent, int expectedNumPackets) throws Exception { Email email = new Email(true); String recipient = "test@bote.i2p"; email.addRecipient(RecipientType.TO, new InternetAddress(recipient)); email.setText(mailContent); EmailIdentity identity = new EmailIdentity("DVkhqF6R9SHB5svViGtqRYZO7oI-0-omnIFtae29fNnNtTTH2j37Fr5fWp4t6rseTjiJ8gwg08DnbA4qP72aSQcDQPSErOELOMSU5BUTtsT8hnv1-DKdhIn~1qoIjxzIFHbxT3xnR3nFI7lKd6couscilzPBCjoFDUKb5ds2u23RO29K7~EKxU1O7Ltu6sT5etXkJkhAziOcuyfZyxJXqH1caYX5e2aWIhY3D2ESfy4nMK66r5KcDVQOPTzCkJq6d1FFOmnDGrlJjN~HgHmfUCtLbO~TLugWx9FCiDGfPkBb-3ODYTDaUR1zobOj1tiffV3Nm73PsYddRt84emLKzIRsC77JJpflw~h8UIRYJ29vJDf4VQ54BhZcelmN192sIrWr2nKN8n6PpSP4LI4RAuG2UvLytnDYzFM7O9WcnFP2-Qs3t1lD9aF72JVTYTpH5PZupnB1cglSsdRg8RmtRa41Fseyx8D3EdH~DCdpMGmfupaWp9~dKpFMleqk9scRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABTjDxn3wEOjCjJ4APg~2IGpqWwy2Hw728aZ3eCC5l0MP913BLdIfSUiXPbs6sN9A2"); KeyUpdateHandler keyUpdateHandler = TestUtil.createDummyKeyUpdateHandler(); Collection<UnencryptedEmailPacket> packets = email.createEmailPackets(identity, keyUpdateHandler, recipient, I2PBotePacket.MAX_DATAGRAM_SIZE); assertTrue("Expected " + expectedNumPackets + " email packets, got " + packets.size(), packets.size() == expectedNumPackets); assertTrue("The inbox should be empty at this point!", inbox.getElements().size() == 0); for (UnencryptedEmailPacket emailPacket: packets) incompleteFolder.addEmailPacket(emailPacket); assertTrue("The incomplete emails folder is not empty!", incompleteFolder.getElements().size() == 0); assertTrue("Expected: one email in the inbox, actual number = " + inbox.getElements().size(), inbox.getElements().size() == 1); // Verify that the original email and the email in the folder are the same except for the signature header Email storedEmail = inbox.getElements().iterator().next(); storedEmail.removeHeader("X-I2PBote-Sig-Valid"); TestUtil.assertEquals("Stored email differs from original email!", email, storedEmail); } }
@Test public void testDefaultDateIsUTC() throws Exception { Email email = new Email(true); assertNull(email.getSentDate()); email.updateHeaders(); assertNotNull(email.getSentDate()); assertThat(email.getHeader("Date", null), endsWith("+0000 (GMT)")); }
value1 = email1.getSentDate(); if (value1 == null) value1 = email1.getReceivedDate(); value2 = email2.getSentDate(); if (value2 == null) value2 = email2.getReceivedDate(); break; case FROM: value1 = displayFilter.getNameAndDestination(email1.getOneFromAddress()); value2 = displayFilter.getNameAndDestination(email2.getOneFromAddress()); break; case TO: value1 = displayFilter.getNameAndDestination(email1.getOneRecipient()); value2 = displayFilter.getNameAndDestination(email2.getOneRecipient()); break; case CREATE_TIME: value1 = email1.getCreateTime(); value2 = email2.getCreateTime(); break; case SUBJECT: value1 = email1.getSubject(); value2 = email2.getSubject(); break; case DELIVERED: value1 = email1.getDeliveryPercentage(); value2 = email2.getDeliveryPercentage(); break; default:
String[] signatureHeaders; try { signatureHeaders = getHeader(SIGNATURE_HEADER); } catch (MessagingException e) { log.error("Cannot get header field: " + SIGNATURE_HEADER, e); return false; String signatureHeader = signatureHeaders[0]; String unfoldedSignatureHeader = unfoldSignature(signatureHeader); removeHeader(SIGNATURE_HEADER); // remove the signature before verifying byte[] signature = Base64.decode(base64Signature); EmailDestination senderDestination = new EmailDestination(getOneFromAddress()); return cryptoImpl.verify(toByteArray(), signature, senderDestination.getPublicSigningKey()); } catch (Exception e) { log.error("Cannot verify email signature. Email: [" + this + "]", e); } finally { try { setHeader(SIGNATURE_HEADER, signatureHeader); } catch (MessagingException e) { log.error("Cannot set signature header field.", e);
@Test public void testHeaderRemoval() throws MessagingException, IOException, GeneralSecurityException, PasswordException { Email newEmail; Collection<UnencryptedEmailPacket> packets; ByteArrayOutputStream outputStream; KeyUpdateHandler keyUpdateHandler = TestUtil.createDummyKeyUpdateHandler(); // verify that all BCC addresses are removed when sending to a TO: address EmailIdentity identity2 = identities.get(bccEmail); packets = bccEmail.createEmailPackets(identity2, keyUpdateHandler, null, I2PBotePacket.MAX_DATAGRAM_SIZE); outputStream = new ByteArrayOutputStream(); for (UnencryptedEmailPacket packet: packets) outputStream.write(packet.getContent()); newEmail = new Email(outputStream.toByteArray()); assertNull("BCC headers were not removed!", newEmail.getHeader("BCC")); assertEquals(3, newEmail.getAllRecipients().length); // verify that the recipient is not removed if it is a BCC: addresses packets = bccEmail.createEmailPackets(bccIdentity, keyUpdateHandler, bccEmailDestination, I2PBotePacket.MAX_DATAGRAM_SIZE); // use the plain email dest because that is what the Email class compares against outputStream = new ByteArrayOutputStream(); for (UnencryptedEmailPacket packet: packets) outputStream.write(packet.getContent()); newEmail = new Email(outputStream.toByteArray()); assertNotNull("BCC header expected!", newEmail.getHeader("BCC")); assertEquals("One BCC header expected!", 1, newEmail.getHeader("BCC").length); assertEquals(4, newEmail.getAllRecipients().length); }
/** * Removes all headers that are not on the whitelist, and initializes some * basic header fields.<br/> * Called by {@link #saveChanges()}, see JavaMail JavaDoc. * @throws MessagingException */ @Override public void updateHeaders() throws MessagingException { super.updateHeaders(); scrubHeaders(); removeRecipientNames(); // Depending on includeSendTime, set the send time or remove the send time field if (includeSendTime) { // Ensure the "Date" field is set in UTC time, using the English locale. MailDateFormat formatter = new MailDateFormat(); formatter.setTimeZone(TimeZone.getTimeZone("GMT")); // always use UTC for outgoing mail if (getSentDate() == null) setHeader("Date", formatter.format(new Date())); else setHeader("Date", formatter.format(getSentDate())); } else removeHeader("Date"); }
for (UnencryptedEmailPacket packet: packets) outputStream.write(packet.getContent()); Email email = new Email(outputStream.toByteArray()); email.setMessageID(packets[0].getMessageId()); // all packets in the array have the same message ID email.setSignatureFlag(); // incoming emails have no signature flag, so set it now; if it exists, don't trust but overwrite email.getMetadata().setReceivedDate(new Date()); inbox.add(email); listener.emailReceived(email.getMessageID());
@Test public void testSign() throws MessagingException, SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, IOException, GeneralSecurityException, PasswordException { assertEquals(emails.size()-1, identities.size()); // -1 for the anonymous email that was not added to the map for (Email email: emails) { EmailIdentity identity = identities.get(email); if (identity == null) continue; // sign and verify signature email.sign(identity, TestUtil.createDummyKeyUpdateHandler()); assertTrue(email.isSignatureValid()); // write the email to a byte array, make a new email from the byte array, and verify the signature ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); KeyUpdateHandler keyUpdateHandler = TestUtil.createDummyKeyUpdateHandler(); for (UnencryptedEmailPacket packet: email.createEmailPackets(identity, keyUpdateHandler, null, I2PBotePacket.MAX_DATAGRAM_SIZE)) outputStream.write(packet.getContent()); email = new Email(outputStream.toByteArray()); assertTrue(email.isSignatureValid()); } }
@Test public void testCompression() throws MessagingException, GeneralSecurityException, PasswordException { // create a 500,000-char string that should compress to one packet StringBuilder stringBuilder = new StringBuilder(); for (int i=0; i<50000; i++) stringBuilder.append("0123456789"); Email newEmail = new Email(true); newEmail.setText(stringBuilder.toString()); KeyUpdateHandler keyUpdateHandler = TestUtil.createDummyKeyUpdateHandler(); Collection<UnencryptedEmailPacket> packets = newEmail.createEmailPackets(bccIdentity, keyUpdateHandler, null, I2PBotePacket.MAX_DATAGRAM_SIZE); assertEquals("The email was not compressed into one email packet.", 1, packets.size()); }
@Test public void testCreateEmailPackets() throws MessagingException, IOException, GeneralSecurityException, SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException, PasswordException { // convert an email to a byte array, convert back, and compare with the original email for (Email email: emails) { EmailIdentity identity = identities.get(email); KeyUpdateHandler keyUpdateHandler = TestUtil.createDummyKeyUpdateHandler(); Collection<UnencryptedEmailPacket> packets = email.createEmailPackets(identity, keyUpdateHandler, null, I2PBotePacket.MAX_DATAGRAM_SIZE); assertTrue("Expected more email packets. #packets = " + packets.size(), packets.size() > email.getText().length()/I2PBotePacket.MAX_DATAGRAM_SIZE/2); // the emails are somewhat compressible, but definitely not by 50% ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (UnencryptedEmailPacket packet: packets) outputStream.write(packet.getContent()); Email newEmail = new Email(outputStream.toByteArray()); assertEquals(email.getContent(), newEmail.getContent()); // check packet sizes against size limit for (UnencryptedEmailPacket packet: packets) assertTrue("Email packet exceeds max size!", packet.toByteArray().length <= I2PBotePacket.MAX_DATAGRAM_SIZE); } }
/** * Updates in-memory flags from stored metadata and returns them. */ @Override public Flags getFlags() throws MessagingException { super.setFlag(Flag.RECENT, isRecent()); super.setFlag(Flag.SEEN, !isUnread()); super.setFlag(Flag.ANSWERED, isReplied()); super.setFlag(Flag.DELETED, isDeleted()); return super.getFlags(); }
logSuffix = "Recipient = '" + recipient + "' Message ID = '" + email.getMessageID() + "'"; log.info("Sending email: " + logSuffix); EmailDestination recipientDest = new EmailDestination(recipient); if (email.isAnonymous()) identityConfig = configuration; else Collection<UnencryptedEmailPacket> emailPackets = email.createEmailPackets(senderIdentity, identities, recipient, maxPacketSize); EmailMetadata metadata = email.getMetadata(); for (UnencryptedEmailPacket unencryptedPacket: emailPackets) { EncryptedEmailPacket emailPacket = new EncryptedEmailPacket(unencryptedPacket, recipientDest);
/** * Creates a digital signature of the email and stores it in the * <code>SIGNATURE_HEADER</code> header field. It also removes the * <code>SIGNATURE_VALID_HEADER</code> header. If there is a signature * already, it is replaced.<br/> * The signature is computed over the stream representation of the * email, minus the signature header if it is present.<br/> * The signature includes the ID number of the {@link CryptoImplementation} * used (signature lengths can be different for the same algorithm). * @param senderIdentity * @param keyUpdateHandler Needed for updating the signature key after signing (see {@link CryptoImplementation#sign(byte[], PrivateKey, KeyUpdateHandler)}) * @throws MessagingException * @throws GeneralSecurityException * @throws PasswordException */ public void sign(EmailIdentity senderIdentity, KeyUpdateHandler keyUpdateHandler) throws MessagingException, GeneralSecurityException, PasswordException { removeHeader(SIGNATURE_HEADER); // make sure there is no existing signature which would make the new signature invalid removeHeader(SIGNATURE_VALID_HEADER); // remove the signature validity flag before signing CryptoImplementation cryptoImpl = senderIdentity.getCryptoImpl(); PrivateKey privateSigningKey = senderIdentity.getPrivateSigningKey(); byte[] signature = cryptoImpl.sign(toByteArray(), privateSigningKey, keyUpdateHandler); String foldedSignature = foldSignature(cryptoImpl.getId() + "_" + Base64.encode(signature)); setHeader(SIGNATURE_HEADER, foldedSignature); }