package hudson.plugins.emailext;
import hudson.Extension;
import hudson.matrix.MatrixProject;
import hudson.model.AbstractProject;
import hudson.plugins.emailext.plugins.EmailTriggerDescriptor;
import hudson.plugins.emailext.plugins.trigger.FailureTrigger;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Mailer;
import hudson.tasks.Publisher;
import hudson.util.FormValidation;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import jenkins.model.JenkinsLocationConfiguration;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.scriptsecurity.scripts.ApprovalContext;
import org.jenkinsci.plugins.scriptsecurity.scripts.ClasspathEntry;
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* These settings are global configurations
*/
@Extension
public final class ExtendedEmailPublisherDescriptor extends BuildStepDescriptor<Publisher> {
public static final Logger LOGGER = Logger.getLogger(ExtendedEmailPublisherDescriptor.class.getName());
/**
* The default e-mail address suffix appended to the user name found from
* changelog, to send e-mails. Null if not configured.
*/
private String defaultSuffix;
/**
* Jenkins's own URL, to put into the e-mail.
*/
private transient String hudsonUrl;
/**
* o
* If non-null, use SMTP-AUTH
*/
private String smtpAuthUsername;
private Secret smtpAuthPassword;
/**
* The e-mail address that Jenkins puts to "From:" field in outgoing
* e-mails. Null if not configured.
*/
private transient String adminAddress;
/**
* The SMTP server to use for sending e-mail. Null for default to the
* environment, which is usually <tt>localhost</tt>.
*/
private String smtpHost;
/**
* If true use SSL on port 465 (standard SMTPS) unless <code>smtpPort</code>
* is set.
*/
private boolean useSsl;
/**
* The SMTP port to use for sending e-mail. Null for default to the
* environment, which is usually <tt>25</tt>.
*/
private String smtpPort;
private String charset;
/**
* This is a global default content type (mime type) for emails.
*/
private String defaultContentType;
/**
* This is a global default subject line for sending emails.
*/
private String defaultSubject;
/**
* This is a global default body for sending emails.
*/
private String defaultBody;
/**
* This is the global default pre-send script.
*/
private String defaultPresendScript = "";
/**
* This is the global default post-send script.
*/
private String defaultPostsendScript = "";
private List<GroovyScriptPath> defaultClasspath = new ArrayList<>();
private transient List<EmailTriggerDescriptor> defaultTriggers = new ArrayList<>();
private List<String> defaultTriggerIds = new ArrayList<>();
/**
* This is the global emergency email address
*/
private String emergencyReroute;
/**
* The maximum size of all the attachments (in bytes)
*/
private long maxAttachmentSize = -1;
/*
* This is a global default recipient list for sending emails.
*/
private String recipientList = "";
/*
* The default Reply-To header value
*/
private String defaultReplyTo = "";
/*
* This is a global excluded committers list for not sending commit emails.
*/
private String excludedCommitters = "";
private boolean overrideGlobalSettings;
/**
* If non-null, set a List-ID email header.
*/
private String listId;
private boolean precedenceBulk;
private boolean debugMode = false;
private transient boolean enableSecurity = false;
/**
* If true, then the 'Email Template Testing' link will only be displayed
* for users with ADMINISTER privileges.
*/
private boolean requireAdminForTemplateTesting = false;
/**
* Enables the "Watch This Job" feature
*/
private boolean enableWatching;
public ExtendedEmailPublisherDescriptor() {
super(ExtendedEmailPublisher.class);
load();
if (defaultBody == null && defaultSubject == null && emergencyReroute == null) {
defaultBody = ExtendedEmailPublisher.DEFAULT_BODY_TEXT;
defaultSubject = ExtendedEmailPublisher.DEFAULT_SUBJECT_TEXT;
emergencyReroute = ExtendedEmailPublisher.DEFAULT_EMERGENCY_REROUTE_TEXT;
}
if (Jenkins.getActiveInstance().isUseSecurity()
&& (!StringUtils.isBlank(this.defaultPostsendScript)) || !StringUtils.isBlank(this.defaultPresendScript)) {
setDefaultPostsendScript(this.defaultPostsendScript);
setDefaultPresendScript(this.defaultPresendScript);
try {
setDefaultClasspath(this.defaultClasspath);
} catch (FormException e) {
//Some of the old configured classpaths probably used some environment variable, let's clean those out
List<GroovyScriptPath> newList = new ArrayList<>();
for (GroovyScriptPath path : defaultClasspath) {
URL u = path.asURL();
if (u != null) {
try {
new ClasspathEntry(u.toString());
newList.add(path);
} catch (MalformedURLException mfue) {
LOGGER.log(Level.WARNING, "The default classpath contained a malformed url, will be ignored.", mfue);
}
}
}
try {
setDefaultClasspath(newList);
} catch (FormException e1) {
assert false : e1;
}
}
}
}
@Override
public String getDisplayName() {
return Messages.ExtendedEmailPublisherDescriptor_DisplayName();
}
public String getAdminAddress() {
JenkinsLocationConfiguration configuration = JenkinsLocationConfiguration.get();
assert configuration != null;
return configuration.getAdminAddress();
}
public String getDefaultSuffix() {
return defaultSuffix;
}
public void setDefaultSuffix(String defaultSuffix) {
this.defaultSuffix = defaultSuffix;
}
public Session createSession() {
Properties props = new Properties(System.getProperties());
if (smtpHost != null) {
props.put("mail.smtp.host", smtpHost);
}
if (smtpPort != null) {
props.put("mail.smtp.port", smtpPort);
}
if (useSsl) {
/* This allows the user to override settings by setting system properties but
* also allows us to use the default SMTPs port of 465 if no port is already set.
* It would be cleaner to use smtps, but that's done by calling session.getTransport()...
* and thats done in mail sender, and it would be a bit of a hack to get it all to
* coordinate, and we can make it work through setting mail.smtp properties.
*/
if (props.getProperty("mail.smtp.socketFactory.port") == null) {
String port = smtpPort == null ? "465" : smtpPort;
props.put("mail.smtp.port", port);
props.put("mail.smtp.socketFactory.port", port);
}
if (props.getProperty("mail.smtp.socketFactory.class") == null) {
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
}
props.put("mail.smtp.socketFactory.fallback", "false");
}
if (smtpAuthUsername != null) {
props.put("mail.smtp.auth", "true");
}
// avoid hang by setting some timeout.
props.put("mail.smtp.timeout", "60000");
props.put("mail.smtp.connectiontimeout", "60000");
return Session.getInstance(props, getAuthenticator());
}
private Authenticator getAuthenticator() {
final String un = getSmtpAuthUsername();
if (un == null) {
return null;
}
return new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(getSmtpAuthUsername(), getSmtpAuthPassword());
}
};
}
public String getHudsonUrl() {
return Jenkins.getActiveInstance().getRootUrl();
}
public String getSmtpServer() {
return smtpHost;
}
public void setSmtpServer(String smtpServer) {
this.smtpHost = smtpServer;
}
public String getSmtpAuthUsername() {
return smtpAuthUsername;
}
@SuppressWarnings("unused")
public void setSmtpAuthUsername(String username) {
this.smtpAuthUsername = username;
}
public String getSmtpAuthPassword() {
return Secret.toString(smtpAuthPassword);
}
@SuppressWarnings("unused")
public void setSmtpAuthPassword(String password) {
this.smtpAuthPassword = Secret.fromString(password);
}
// Make API match Mailer plugin
@SuppressWarnings("unused")
public void setSmtpAuth(String userName, String password) {
setSmtpAuthUsername(userName);
setSmtpAuthPassword(password);
}
public boolean getUseSsl() {
return useSsl;
}
@SuppressWarnings("unused")
public void setUseSsl(boolean useSsl) {
this.useSsl = useSsl;
}
public String getSmtpPort() {
return smtpPort;
}
@SuppressWarnings("unused")
public void setSmtpPort(String port) {
this.smtpPort = nullify(port);
}
public String getCharset() {
String c = charset;
if (StringUtils.isBlank(c)) {
c = "UTF-8";
}
return c;
}
@SuppressWarnings("unused")
public void setCharset(String charset) {
this.charset = charset;
}
public String getDefaultContentType() {
return defaultContentType;
}
@SuppressWarnings("unused")
public void setDefaultContentType(String contentType) {
if (StringUtils.isBlank(contentType)) {
this.defaultContentType = "text/plain";
} else {
this.defaultContentType = contentType;
}
}
public String getDefaultSubject() {
return defaultSubject;
}
@SuppressWarnings("unused")
public void setDefaultSubject(String subject) {
if (subject == null) {
this.defaultSubject = ExtendedEmailPublisher.DEFAULT_SUBJECT_TEXT;
} else {
this.defaultSubject = subject;
}
}
public String getDefaultBody() {
return defaultBody;
}
@SuppressWarnings("unused")
public void setDefaultBody(String body) {
if (body == null) {
this.defaultBody = ExtendedEmailPublisher.DEFAULT_BODY_TEXT;
} else {
this.defaultBody = body;
}
}
public String getEmergencyReroute() {
return emergencyReroute;
}
protected void setEmergencyReroute(String emergencyReroute) {
if (emergencyReroute == null) {
this.emergencyReroute = ExtendedEmailPublisher.DEFAULT_EMERGENCY_REROUTE_TEXT;
} else {
this.emergencyReroute = emergencyReroute;
}
}
public long getMaxAttachmentSize() {
return maxAttachmentSize;
}
public void setMaxAttachmentSize(long bytes) {
if (bytes < 0) {
bytes = -1; // set to default "empty" value
}
this.maxAttachmentSize = bytes;
}
public long getMaxAttachmentSizeMb() {
return maxAttachmentSize / (1024 * 1024);
}
@SuppressWarnings("unused")
public void setMaxAttachmentSizeMb(long mb) {
setMaxAttachmentSize(mb * (1024 * 1024));
}
public String getDefaultRecipients() {
return recipientList;
}
@SuppressWarnings("unused")
public void setDefaultRecipients(String recipients) {
this.recipientList = ((recipients == null) ? "" : recipients);
}
public String getExcludedCommitters() {
return excludedCommitters;
}
@SuppressWarnings("unused")
public void setExcludedCommitters(String excluded) {
this.excludedCommitters = ((excluded == null) ? "" : excluded);
}
public boolean getOverrideGlobalSettings() {
return overrideGlobalSettings;
}
public String getListId() {
return listId;
}
@SuppressWarnings("unused")
public void setListId(String id) {
this.listId = nullify(id);
}
public boolean getPrecedenceBulk() {
return precedenceBulk;
}
@SuppressWarnings("unused")
public void setPrecedenceBulk(boolean bulk) {
this.precedenceBulk = bulk;
}
public String getDefaultReplyTo() {
return defaultReplyTo;
}
@SuppressWarnings("unused")
public void setDefaultReplyTo(String to) {
this.defaultReplyTo = ((to == null) ? "" : to);
}
public boolean isSecurityEnabled() {
return false;
}
public boolean isAdminRequiredForTemplateTesting() {
return requireAdminForTemplateTesting;
}
@SuppressWarnings("unused")
public void setAdminRequiredForTemplateTesting(boolean requireAdmin) {
this.requireAdminForTemplateTesting = requireAdmin;
}
public boolean isWatchingEnabled() {
return enableWatching;
}
@SuppressWarnings("unused")
public void setWatchingEnabled(boolean enabled) {
this.enableWatching = enabled;
}
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}
public String getDefaultPresendScript() {
return defaultPresendScript;
}
@SuppressWarnings("unused")
public void setDefaultPresendScript(String script) {
script = StringUtils.trim(script);
this.defaultPresendScript = ScriptApproval.get().configuring(((script == null) ? "" : script),
GroovyLanguage.get(),
ApprovalContext.create().withCurrentUser());
}
public String getDefaultPostsendScript() {
return defaultPostsendScript;
}
@SuppressWarnings("unused")
public void setDefaultPostsendScript(String script) {
script = StringUtils.trim(script);
this.defaultPostsendScript = ScriptApproval.get().configuring(((script == null) ? "" : script),
GroovyLanguage.get(),
ApprovalContext.create().withCurrentUser());
}
public List<GroovyScriptPath> getDefaultClasspath() {
return defaultClasspath;
}
public void setDefaultClasspath(List<GroovyScriptPath> defaultClasspath) throws FormException {
if (Jenkins.getActiveInstance().isUseSecurity()) {
ScriptApproval approval = ScriptApproval.get();
ApprovalContext context = ApprovalContext.create().withCurrentUser();
for (GroovyScriptPath path : defaultClasspath) {
URL u = path.asURL();
if (u != null) {
try {
approval.configuring(new ClasspathEntry(u.toString()), context);
} catch (MalformedURLException e) {
throw new FormException(e, "ext_mailer_default_classpath");
}
}
}
}
this.defaultClasspath = defaultClasspath;
}
public List<String> getDefaultTriggerIds() {
if (defaultTriggerIds.isEmpty()) {
if (!defaultTriggers.isEmpty()) {
defaultTriggerIds.clear();
for(EmailTriggerDescriptor t : this.defaultTriggers) {
// we have to do the below because a bunch of stuff is not serialized for the Descriptor
EmailTriggerDescriptor d = Jenkins.getActiveInstance().getDescriptorByType(t.getClass());
if(!defaultTriggerIds.contains(d.getId())) {
defaultTriggerIds.add(d.getId());
}
}
} else {
defaultTriggerIds.add(Jenkins.getActiveInstance().getDescriptor(FailureTrigger.class).getId());
}
save();
}
return defaultTriggerIds;
}
@Override
public boolean configure(StaplerRequest req, JSONObject formData)
throws FormException {
// Configure the smtp server
smtpHost = nullify(req.getParameter("ext_mailer_smtp_server"));
defaultSuffix = nullify(req.getParameter("ext_mailer_default_suffix"));
// specify authentication information
if (req.hasParameter("ext_mailer_use_smtp_auth")) {
smtpAuthUsername = nullify(req.getParameter("ext_mailer_smtp_username"));
smtpAuthPassword = Secret.fromString(nullify(req.getParameter("ext_mailer_smtp_password")));
} else {
smtpAuthUsername = null;
smtpAuthPassword = null;
}
// specify if the mail server uses ssl for authentication
useSsl = req.hasParameter("ext_mailer_smtp_use_ssl");
// specify custom smtp port
smtpPort = nullify(req.getParameter("ext_mailer_smtp_port"));
charset = nullify(req.getParameter("ext_mailer_charset"));
defaultContentType = nullify(req.getParameter("ext_mailer_default_content_type"));
// Allow global defaults to be set for the subject and body of the email
defaultSubject = nullify(req.getParameter("ext_mailer_default_subject"));
defaultBody = nullify(req.getParameter("ext_mailer_default_body"));
emergencyReroute = nullify(req.getParameter("ext_mailer_emergency_reroute"));
defaultReplyTo = nullify(req.getParameter("ext_mailer_default_replyto")) != null
? req.getParameter("ext_mailer_default_replyto") : "";
setDefaultPresendScript(nullify(req.getParameter("ext_mailer_default_presend_script")) != null
? req.getParameter("ext_mailer_default_presend_script") : "");
setDefaultPostsendScript(nullify(req.getParameter("ext_mailer_default_postsend_script")) != null
? req.getParameter("ext_mailer_default_postsend_script") : "");
if (req.hasParameter("ext_mailer_default_classpath")) {
List<GroovyScriptPath> cp = new ArrayList<>();
for (String s : req.getParameterValues("ext_mailer_default_classpath")) {
cp.add(new GroovyScriptPath(s));
}
setDefaultClasspath(cp);
}
debugMode = req.hasParameter("ext_mailer_debug_mode");
// convert the value into megabytes (1024 * 1024 bytes)
maxAttachmentSize = nullify(req.getParameter("ext_mailer_max_attachment_size")) != null
? Long.parseLong(req.getParameter("ext_mailer_max_attachment_size")) * 1024 * 1024 : -1;
recipientList = nullify(req.getParameter("ext_mailer_default_recipients")) != null
? req.getParameter("ext_mailer_default_recipients") : "";
precedenceBulk = req.hasParameter("ext_mailer_add_precedence_bulk");
excludedCommitters = req.getParameter("ext_mailer_excluded_committers");
requireAdminForTemplateTesting = req.hasParameter("ext_mailer_require_admin_for_template_testing");
enableWatching = req.hasParameter("ext_mailer_watching_enabled");
// specify List-ID information
if (req.hasParameter("ext_mailer_use_list_id")) {
listId = nullify(req.getParameter("ext_mailer_list_id"));
} else {
listId = null;
}
List<String> ids = new ArrayList<>();
if(formData.optJSONArray("defaultTriggers") != null) {
for(Object id : formData.getJSONArray("defaultTriggers")) {
ids.add(id.toString());
}
} else if(StringUtils.isNotEmpty(formData.optString("defaultTriggers"))) {
ids.add(formData.getString("defaultTriggers"));
}
if(!ids.isEmpty()) {
defaultTriggerIds.clear();
for(String id : ids) {
EmailTriggerDescriptor d = (EmailTriggerDescriptor)Jenkins.getActiveInstance().getDescriptor(id);
if(d != null) {
defaultTriggerIds.add(id);
}
}
}
if(!overrideGlobalSettings) {
upgradeFromMailer();
}
save();
return super.configure(req, formData);
}
private String nullify(String v) {
if (v != null && v.length() == 0) {
v = null;
}
return v;
}
void upgradeFromMailer() {
// get the data from Mailer and then set override to true
this.defaultSuffix = Mailer.descriptor().getDefaultSuffix();
this.defaultReplyTo = Mailer.descriptor().getReplyToAddress();
this.useSsl = Mailer.descriptor().getUseSsl();
if (StringUtils.isNotBlank(Mailer.descriptor().getSmtpAuthUserName())) {
this.smtpAuthPassword = Secret.fromString(Mailer.descriptor().getSmtpAuthPassword());
this.smtpAuthUsername = Mailer.descriptor().getSmtpAuthUserName();
}
this.smtpPort = Mailer.descriptor().getSmtpPort();
this.smtpHost = Mailer.descriptor().getSmtpServer();
this.charset = Mailer.descriptor().getCharset();
this.overrideGlobalSettings = true;
}
@Override
public String getHelpFile() {
return "/plugin/email-ext/help/main.html";
}
public FormValidation doAddressCheck(@QueryParameter final String value)
throws IOException, ServletException {
try {
new InternetAddress(value);
return FormValidation.ok();
} catch (AddressException e) {
return FormValidation.error(e.getMessage());
}
}
public FormValidation doRecipientListRecipientsCheck(@QueryParameter final String value)
throws IOException, ServletException {
return new EmailRecipientUtils().validateFormRecipientList(value);
}
public FormValidation doMaxAttachmentSizeCheck(@QueryParameter final String value)
throws IOException, ServletException {
try {
String testValue = value.trim();
// we support an empty value (which means default)
// or a number
if (testValue.length() > 0) {
Long.parseLong(testValue);
}
return FormValidation.ok();
} catch (Exception e) {
return FormValidation.error(e.getMessage());
}
}
public boolean isMatrixProject(Object project) {
return project instanceof MatrixProject;
}
public boolean isDebugMode() {
return debugMode;
}
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
}
public void debug(PrintStream logger, String format, Object... args) {
if (debugMode) {
logger.format(format, args);
logger.println();
}
}
}