Skip to content

Commit

Permalink
feat: [CO 621] Allow LE certificates to be generated from Mailbox end…
Browse files Browse the repository at this point in the history
…point (#175)
  • Loading branch information
aheeva-yuliya authored Apr 6, 2023
1 parent 2bf4e11 commit 6f797aa
Show file tree
Hide file tree
Showing 14 changed files with 893 additions and 234 deletions.
141 changes: 116 additions & 25 deletions store/src/main/java/com/zimbra/cs/mailbox/MailSender.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package com.zimbra.cs.mailbox;

import com.zimbra.cs.mailclient.smtp.SmtpTransport;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -25,9 +26,11 @@

import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.SendFailedException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.URLName;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

Expand Down Expand Up @@ -246,6 +249,29 @@ public MailSender setSession(Account account) throws ServiceException {
return this;
}

/**
* Sets an alternate JavaMail {@link javax.mail.Session} and SMTP hosts
* that will be used to send the message based on the domain values.
* The default behavior is to use SMTP settings from
* the {@link javax.mail.Session} on the {@link MimeMessage}.
*
* @param domain {@link com.zimbra.cs.account.Domain}
* @throws ServiceException if not able to get SMTP session for the domain
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
public MailSender setSession(Domain domain) throws ServiceException {
try {
mSession = JMSession.getSmtpSession(domain);
} catch (MessagingException e) {
throw ServiceException.FAILURE("Unable to get SMTP session for " + domain, e);
}
mSmtpHosts.clear();
mSmtpHosts.addAll(JMSession.getSmtpHosts(domain));
return this;
}

/**
* Sets JavaMail <tt>Session</tt> using the SMTP settings associated with the data source.
* @throws ServiceException
Expand Down Expand Up @@ -282,6 +308,17 @@ public MailSender setEnvelopeFrom(String address) {
return this;
}

/**
* Returns the current session {@link javax.mail.Session}.
*
* @return {@link #mSession} value
* @author Yuliya Aheeva
* @since 23.5.0
*/
public Session getCurrentSession() {
return this.mSession;
}

public static int getSentFolderId(Mailbox mbox, Identity identity) throws ServiceException {
int folderId = Mailbox.ID_FOLDER_SENT;
String sentFolder = identity.getAttr(Provisioning.A_zimbraPrefSentMailFolder, null);
Expand Down Expand Up @@ -394,30 +431,6 @@ public static enum ReplyForwardType {
FORWARD // Forwarding another message
}

/**
* Sets member variables and sends the message.
*/
public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, MimeMessage mm, List<Upload> uploads,
ItemId origMsgId, String replyType, String identityId, boolean replyToSender) throws ServiceException {
return sendMimeMessage(octxt, mbox, mm, uploads, origMsgId, replyType, identityId, replyToSender, null);
}

/**
* Sets member variables and sends the message.
*/
public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, MimeMessage mm, List<Upload> uploads,
ItemId origMsgId, String replyType, String identityId, boolean replyToSender, MimeProcessor mimeProc) throws ServiceException {
Account authuser = octxt == null ? null : octxt.getAuthenticatedUser();
if (authuser == null) {
authuser = mbox.getAccount();
}
Identity identity = null;
if (identityId != null) {
identity = Provisioning.getInstance().get(authuser, Key.IdentityBy.id, identityId);
}
return sendMimeMessage(octxt, mbox, null, mm, uploads, origMsgId, replyType, identity, replyToSender, mimeProc);
}

public ItemId sendDataSourceMimeMessage(OperationContext octxt, Mailbox mbox, MimeMessage mm, List<Upload> uploads,
ItemId origMsgId, String replyType) throws ServiceException {
return sendDataSourceMimeMessage(octxt, mbox, mm, uploads, origMsgId, replyType, null);
Expand Down Expand Up @@ -460,6 +473,62 @@ public void rollback() {
}
}

/**
* Sends a list of messages {@link javax.mail.internet.MimeMessage}.
*
* Tries to find the account by name (if unable to find will set account from the mailbox later
* with the {@link #sendMimeMessage(OperationContext, Mailbox, MimeMessage)} method)
* and provide the proper operational context.
*
* @param mbox object of {@link com.zimbra.cs.mailbox.Mailbox}
* @param mimeMessageList a list of {@link javax.mail.internet.MimeMessage} to be sent
* @throws ServiceException if sender is not set for the specific
* {@link javax.mail.internet.MimeMessage}
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
public void sendMimeMessageList(Mailbox mbox, List<MimeMessage> mimeMessageList)
throws ServiceException {

Provisioning prov = Provisioning.getInstance();

try {
for (MimeMessage mm: mimeMessageList) {
Account account = prov.get(AccountBy.name, mm.getSender().toString());
OperationContext operationContext = new OperationContext(account);

sendMimeMessage(operationContext, mbox, mm);
}
} catch (MessagingException e) {
throw ServiceException.FAILURE("Unable to get sender account: " + e.getMessage());
}
}

/**
* Sets member variables and sends the message.
*/
public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, MimeMessage mm, List<Upload> uploads,
ItemId origMsgId, String replyType, String identityId, boolean replyToSender) throws ServiceException {
return sendMimeMessage(octxt, mbox, mm, uploads, origMsgId, replyType, identityId, replyToSender, null);
}

/**
* Sets member variables and sends the message.
*/
public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, MimeMessage mm, List<Upload> uploads,
ItemId origMsgId, String replyType, String identityId, boolean replyToSender, MimeProcessor mimeProc) throws ServiceException {
Account authuser = octxt == null ? null : octxt.getAuthenticatedUser();
if (authuser == null) {
authuser = mbox.getAccount();
}
Identity identity = null;
if (identityId != null) {
identity = Provisioning.getInstance().get(authuser, Key.IdentityBy.id, identityId);
}
return sendMimeMessage(octxt, mbox, null, mm, uploads, origMsgId, replyType, identity, replyToSender, mimeProc);
}

/**
* Sets member variables and sends the message.
*/
Expand All @@ -476,6 +545,9 @@ public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, Boolean save
return sendMimeMessage(octxt, mbox, mm);
}

/**
* Sets member variables and sends the message.
*/
public ItemId sendMimeMessage(OperationContext octxt, Mailbox mbox, Boolean saveToSent, MimeMessage mm,
Collection<Upload> uploads, ItemId origMsgId, String replyType, Identity identity, boolean replyToSender)
throws ServiceException {
Expand Down Expand Up @@ -1231,7 +1303,9 @@ private void sendMessageToHost(String hostname, MimeMessage mm, Address[] rcptAd
}
ZimbraLog.smtp.debug("Sending message %s to SMTP host %s with properties: %s",
mm.getMessageID(), hostname, mSession.getProperties());
Transport transport = mSession.getTransport("smtp");

Transport transport = getTransport();

try {
transport.connect();
transport.sendMessage(mm, rcptAddresses);
Expand All @@ -1240,6 +1314,23 @@ private void sendMessageToHost(String hostname, MimeMessage mm, Address[] rcptAd
}
}

/**
* Initializes a new SMTP transport if not able to get one from the {@link #mSession}.
*
* @return SMTP transport
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
private Transport getTransport() {
try {
return mSession.getTransport("smtp");
} catch (NoSuchProviderException e) {
URLName urlName = new URLName("smtp", null, -1, null, null, null);
return new SmtpTransport(mSession, urlName);
}
}

private void checkMTAConnectionToHost(String hostname) throws MessagingException {
mSession.getProperties().setProperty("mail.smtp.host", hostname);
if (mEnvelopeFrom != null) {
Expand Down
19 changes: 19 additions & 0 deletions store/src/main/java/com/zimbra/cs/mailbox/Mailbox.java
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,25 @@ public MailSender getMailSender() throws ServiceException {
return sender;
}

/**
* Returns a {@link MailSender} object based on specific domain properties
* that can be used to send mails.
*
* @param domain {@link com.zimbra.cs.account.Domain} to get needed properties
* @return {@link MailSender} object
* @throws ServiceException if unable to get SMTP session for the current domain
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
public MailSender getMailSender(Domain domain) throws ServiceException {
MailSender sender = new MailSender();
sender.setTrackBadHosts(true);
sender.setSession(domain);

return sender;
}

/**
* Returns the list of all <code>Mailbox</code> listeners of a given type. Returns all listeners
* when the passed-in type is <tt>null</tt>.
Expand Down
52 changes: 43 additions & 9 deletions store/src/main/java/com/zimbra/cs/rmgmt/RemoteCertbot.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.zimbra.cs.rmgmt;

import com.zimbra.common.service.ServiceException;
import com.zimbra.cs.service.admin.CertificateNotificationManager;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
* RemoteCertbot class interacts with "Certbot" - an acme client for managing Let’s Encrypt
Expand Down Expand Up @@ -30,27 +32,42 @@ public class RemoteCertbot {
private final RemoteManager remoteManager;
private StringBuilder stringBuilder;

public RemoteCertbot(RemoteManager remoteManager) {
private RemoteCertbot(RemoteManager remoteManager) {
this.remoteManager = remoteManager;
}

/**
* Instantiates a RemoteCertbot object.
*
* @param remoteManager {@link com.zimbra.cs.rmgmt.RemoteManager} which will be used to execute
* remote commands with.
* @return an instantiated object
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
public static RemoteCertbot getRemoteCertbot(RemoteManager remoteManager) {
return new RemoteCertbot(remoteManager);
}

/**
* Creates a command to be executed by the Certbot acme client.
* E.g. certbot certonly --agree-tos --email admin@test.com -n --webroot -w /opt/zextras
*
* <p>E.g. certbot certonly --agree-tos --email admin@test.com -n --webroot -w /opt/zextras
* --cert-name demo.zextras.io -d acme.demo.zextras.io -d webmail-acme.demo.zextras.io
*
* @param remoteCommand {@link com.zimbra.cs.rmgmt.RemoteCommands}
* @param email domain admin email who tries to execute a command (should be agreed to the
* ACME server's Subscriber Agreement)
* @param email domain admin email who tries to execute a command (should be agreed to the ACME
* server's Subscriber Agreement)
* @param chain long (default) or short (should be specified by domain admin in {@link
* com.zimbra.soap.admin.message.IssueCertRequest} request with the key word "short")
* com.zimbra.soap.admin.message.IssueCertRequest} request with the key word "short")
* @param domainName a value of domain attribute zimbraDomainName
* @param publicServiceHostName a value of domain attribute zimbraPublicServiceHostname
* @param virtualHosts a value/ values of domain attribute zimbraVirtualHostname
* @return created command
*/
public String createCommand(String remoteCommand, String email, String chain,
String domainName, String publicServiceHostName, String[] virtualHosts) {
public String createCommand(String remoteCommand, String email, String chain, String domainName,
String publicServiceHostName, String[] virtualHosts) {

this.stringBuilder = new StringBuilder();

Expand All @@ -69,11 +86,28 @@ public String createCommand(String remoteCommand, String email, String chain,
return stringBuilder.toString();
}

/**
* Executes a command asynchronously and notifies global and domain recipients about the command
* execution using {@link com.zimbra.cs.service.admin.CertificateNotificationManager}.
*
* @param notificationManager an object of {@link com.zimbra.cs.service.admin.CertificateNotificationManager}
* @param command a Certbot command to be executed remotely
*
* @author Yuliya Aheeva
* @since 23.5.0
*/
public void supplyAsync(CertificateNotificationManager notificationManager, String command) {
CompletableFuture.supplyAsync(() -> execute(command))
.thenApply(notificationManager::createIssueCertNotificationMap)
.thenAccept(notificationManager::notify);
}

/**
* Executes a command using {@link com.zimbra.cs.rmgmt.RemoteManager}.
*
* @param command a command to be executed
* @return a sting message of successful remote execution or detailed exception message
* in case of failure.
* @return a sting message of successful remote execution or detailed exception message in case of
* failure.
*/
public String execute(String command) {
try {
Expand Down
2 changes: 1 addition & 1 deletion store/src/main/java/com/zimbra/cs/rmgmt/RemoteManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public synchronized RemoteResult execute(String command) throws ServiceException
}
} catch (Exception e) {
throw ServiceException.FAILURE(
"exception executing command: " + command + " with " + this + " " + e);
"exception executing command " + command + " with " + this + " " + e);
}
return result;
}
Expand Down
Loading

0 comments on commit 6f797aa

Please sign in to comment.