All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hudson.tasks.Mailer Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 *
 * Copyright (c) 2004-2011 Oracle Corporation.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *
 *    Kohsuke Kawaguchi,   Bruce Chapman, Erik Ramfelt, Jean-Baptiste Quenot, Luca Domenico Milanesio, Anton Kozak
 *
 *
 *******************************************************************************/ 

package hudson.tasks;

import com.thoughtworks.xstream.converters.UnmarshallingContext;
import hudson.EnvVars;
import hudson.Extension;
import hudson.Functions;
import hudson.Launcher;
import hudson.RestrictedSince;
import hudson.Util;
import hudson.diagnosis.OldDataMonitor;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Hudson;
import hudson.model.User;
import hudson.model.UserPropertyDescriptor;
import hudson.util.FormValidation;
import hudson.util.Secret;
import hudson.util.XStream2;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Properties;
import java.util.logging.Logger;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.ServletException;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.Exported;

import static hudson.Util.fixEmptyAndTrim;

/**
 * {@link Publisher} that sends the build result in e-mail.
 *
 * @author Kohsuke Kawaguchi
 */
public class Mailer extends Notifier {

    protected static final Logger LOGGER = Logger.getLogger(Mailer.class.getName());
    /**
     * Whitespace-separated list of e-mail addresses that represent recipients.
     */
    //TODO: review and check whether we can do it private
    public String recipients;
    /**
     * If true, only the first unstable build will be reported.
     */
    //TODO: review and check whether we can do it private
    public boolean dontNotifyEveryUnstableBuild;
    /**
     * If true, individuals will receive e-mails regarding who broke the build.
     */
    //TODO: review and check whether we can do it private
    public boolean sendToIndividuals;

    public String getRecipients() {
        return recipients;
    }

    public boolean isDontNotifyEveryUnstableBuild() {
        return dontNotifyEveryUnstableBuild;
    }

    public boolean isSendToIndividuals() {
        return sendToIndividuals;
    }
    // TODO: left so that XStream won't get angry. figure out how to set the error handling behavior
    // in XStream.  Deprecated since 2005-04-23.
    private transient String from;
    private transient String subject;
    private transient boolean failureOnly;
    private transient String charset;

    @Override
    public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener)
            throws IOException, InterruptedException {
        if (debug) {
            listener.getLogger().println("Running mailer");
        }
        // substitute build parameters
        EnvVars env = build.getEnvironment(listener);
        String recip = env.expand(recipients);

        return new MailSender(recip, dontNotifyEveryUnstableBuild, sendToIndividuals,
                descriptor().getCharset()).execute(build, listener);
    }

    /**
     * This class does explicit check pointing.
     */
    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.NONE;
    }
    /**
     * @deprecated as of 1.286 Use {@link #descriptor()} to obtain the current
     * instance.
     */
    @Deprecated
    @RestrictedSince("1.355")
    public static DescriptorImpl DESCRIPTOR;

    public static DescriptorImpl descriptor() {
        return Hudson.getInstance().getDescriptorByType(Mailer.DescriptorImpl.class);
    }

    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor {

        /**
         * 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;
        /**
         * Hudson's own URL, to put into the e-mail.
         */
        private String hudsonUrl;
        /**
         * If non-null, use SMTP-AUTH with these information.
         */
        private String smtpAuthUsername;
        private Secret smtpAuthPassword;
        /**
         * The e-mail address that Hudson puts to "From:" field in outgoing
         * e-mails. Null if not configured.
         */
        private String adminAddress;
        /**
         * The SMTP server to use for sending e-mail. Null for default to the
         * environment, which is usually localhost.
         */
        private String smtpHost;
        /**
         * If true use SSL on port 465 (standard SMTPS) unless
         * smtpPort is set.
         */
        private boolean useSsl;
        /**
         * The SMTP port to use for sending e-mail. Null for default to the
         * environment, which is usually 25.
         */
        private String smtpPort;
        /**
         * The charset to use for the text and subject.
         */
        private String charset;
        /**
         * Used to keep track of number test e-mails.
         */
        private static transient int testEmailCount = 0;

        public DescriptorImpl() {
            load();
            DESCRIPTOR = this;
        }

        public String getDisplayName() {
            return Messages.Mailer_DisplayName();
        }

        @Override
        public String getHelpFile() {
            return "/help/project-config/mailer.html";
        }

        public String getDefaultSuffix() {
            return defaultSuffix;
        }

        /**
         * JavaMail session.
         */
        public Session createSession() {
            return createSession(smtpHost, smtpPort, useSsl, smtpAuthUsername, smtpAuthPassword);
        }

        private static Session createSession(String smtpHost, String smtpPort, boolean useSsl, String smtpAuthUserName, Secret smtpAuthPassword) {
            smtpPort = fixEmptyAndTrim(smtpPort);
            smtpAuthUserName = fixEmptyAndTrim(smtpAuthUserName);

            Properties props = new Properties(System.getProperties());
            props.put("mail.transport.protocol", "smtp");
            if (fixEmptyAndTrim(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 work around 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(smtpAuthUserName, Secret.toString(smtpAuthPassword)));
        }

        private static Authenticator getAuthenticator(final String smtpAuthUserName, final String smtpAuthPassword) {
            if (smtpAuthUserName == null) {
                return null;
            }
            return new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(smtpAuthUserName, smtpAuthPassword);
                }
            };
        }

        @Override
        public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
            // this code is brain dead
            smtpHost = nullify(json.getString("smtpServer"));
            setAdminAddress(json.getString("adminAddress"));

            defaultSuffix = nullify(json.getString("defaultSuffix"));
            String url = nullify(json.getString("url"));
            if (url != null && !url.endsWith("/")) {
                url += '/';
            }
            hudsonUrl = url;

            if (json.has("useSMTPAuth")) {
                JSONObject auth = json.getJSONObject("useSMTPAuth");
                smtpAuthUsername = nullify(auth.getString("smtpAuthUserName"));
                smtpAuthPassword = Secret.fromString(nullify(auth.getString("smtpAuthPassword")));
            } else {
                smtpAuthUsername = null;
                smtpAuthPassword = null;
            }
            smtpPort = nullify(json.getString("smtpPort"));
            useSsl = json.getBoolean("useSsl");
            charset = json.getString("charset");
            if (charset == null || charset.length() == 0) {
                charset = "UTF-8";
            }

            save();
            return true;
        }

        private String nullify(String v) {
            if (v != null && v.length() == 0) {
                v = null;
            }
            return v;
        }

        public String getSmtpServer() {
            return smtpHost;
        }

        public String getAdminAddress() {
            String v = adminAddress;
            if (v == null) {
                v = Messages.Mailer_Address_Not_Configured();
            }
            return v;
        }

        public String getUrl() {
            return hudsonUrl;
        }

        public String getSmtpAuthUserName() {
            return smtpAuthUsername;
        }

        public String getSmtpAuthPassword() {
            if (smtpAuthPassword == null) {
                return null;
            }
            return Secret.toString(smtpAuthPassword);
        }

        public boolean getUseSsl() {
            return useSsl;
        }

        public String getSmtpPort() {
            return smtpPort;
        }

        public String getCharset() {
            String c = charset;
            if (c == null || c.length() == 0) {
                c = "UTF-8";
            }
            return c;
        }

        public void setDefaultSuffix(String defaultSuffix) {
            this.defaultSuffix = defaultSuffix;
        }

        public void setHudsonUrl(String hudsonUrl) {
            this.hudsonUrl = hudsonUrl;
        }

        public void setAdminAddress(String adminAddress) {
            if (adminAddress.startsWith("\"") && adminAddress.endsWith("\"")) {
                // some users apparently quote the whole thing. Don't konw why
                // anyone does this, but it's a machine's job to forgive human mistake
                adminAddress = adminAddress.substring(1, adminAddress.length() - 1);
            }
            this.adminAddress = adminAddress;
        }

        public void setSmtpHost(String smtpHost) {
            this.smtpHost = smtpHost;
        }

        public void setUseSsl(boolean useSsl) {
            this.useSsl = useSsl;
        }

        public void setSmtpPort(String smtpPort) {
            this.smtpPort = smtpPort;
        }

        public void setCharset(String chaset) {
            this.charset = chaset;
        }

        public void setSmtpAuth(String userName, String password) {
            this.smtpAuthUsername = userName;
            this.smtpAuthPassword = Secret.fromString(password);
        }

        @Override
        public Publisher newInstance(StaplerRequest req, JSONObject formData) {
            Mailer m = new Mailer();
            req.bindParameters(m, "mailer_");
            m.dontNotifyEveryUnstableBuild = req.getParameter("mailer_notifyEveryUnstableBuild") == null;

            if (hudsonUrl == null) {
                // if Hudson URL is not configured yet, infer some default
                hudsonUrl = Functions.inferHudsonURL(req);
                save();
            }

            return m;
        }

        /**
         * Checks the URL in global.jelly
         */
        public FormValidation doCheckUrl(@QueryParameter String value) {
            if (value.startsWith("http://localhost")) {
                return FormValidation.warning(Messages.Mailer_Localhost_Error());
            }
            return FormValidation.ok();
        }

        public FormValidation doAddressCheck(@QueryParameter String value) {
            try {
                new InternetAddress(value);
                return FormValidation.ok();
            } catch (AddressException e) {
                return FormValidation.error(e.getMessage());
            }
        }

        public FormValidation doCheckSmtpServer(@QueryParameter String value) {
            try {
                if (fixEmptyAndTrim(value) != null) {
                    InetAddress.getByName(value);
                }
                return FormValidation.ok();
            } catch (UnknownHostException e) {
                return FormValidation.error(Messages.Mailer_Unknown_Host_Name() + value);
            }
        }

        public FormValidation doCheckAdminAddress(@QueryParameter String value) {
            return doAddressCheck(value);
        }

        public FormValidation doCheckDefaultSuffix(@QueryParameter String value) {
            if (value.matches("@[A-Za-z0-9.\\-]+") || fixEmptyAndTrim(value) == null) {
                return FormValidation.ok();
            } else {
                return FormValidation.error(Messages.Mailer_Suffix_Error());
            }
        }

        /**
         * Send an email to the admin address
         *
         * @throws IOException
         * @throws ServletException
         * @throws InterruptedException
         */
        public FormValidation doSendTestMail(
                @QueryParameter String smtpServer, @QueryParameter String adminAddress, @QueryParameter boolean useSMTPAuth,
                @QueryParameter String smtpAuthUserName, @QueryParameter String smtpAuthPassword,
                @QueryParameter boolean useSsl, @QueryParameter String smtpPort) throws IOException, ServletException, InterruptedException {
            try {
                if (!useSMTPAuth) {
                    smtpAuthUserName = smtpAuthPassword = null;
                }

                Session session = createSession(smtpServer, smtpPort, useSsl, smtpAuthUserName,
                        Secret.fromString(smtpAuthPassword));
                MimeMessage msg = new HudsonMimeMessage(session);
                msg.setSubject("Test email #" + ++testEmailCount);
                msg.setContent("This is test email #" + testEmailCount + " sent from Hudson Continuous Integration server.", "text/plain");
                msg.setFrom(new InternetAddress(adminAddress));
                msg.setSentDate(new Date());
                msg.setRecipient(Message.RecipientType.TO, new InternetAddress(adminAddress));

                //See http://issues.hudson-ci.org/browse/HUDSON-7426 and
                //http://www.oracle.com/technetwork/java/faq-135477.html#smtpauth
                send(smtpServer, smtpAuthUserName, smtpAuthPassword, smtpPort, (HudsonMimeMessage) msg);

                return FormValidation.ok("Email was successfully sent");
            } catch (MessagingException e) {
                return FormValidation.errorWithMarkup("

Failed to send out e-mail

" + Util.escape(Functions.printThrowable(e)) + "
"); } } /** * Sends message * * @param msg {@link MimeMessage} * @throws MessagingException if any. */ public void send(HudsonMimeMessage msg) throws MessagingException { send(smtpHost, smtpAuthUsername, Secret.toString(smtpAuthPassword), smtpPort, msg); } /** * Wrap {@link Transport#send(javax.mail.Message)} method. Based on javax.mail * recommendations and fix HUDSON-7426 * * @param smtpServer smtp server * @param smtpAuthUserName username * @param smtpAuthPassword password. * @param smtpPort port. * @param msg {@link MimeMessage} * @throws MessagingException if any. * @see * {@link #createSession(String, String, boolean, String, hudson.util.Secret)} */ public static void send(String smtpServer, String smtpAuthUserName, String smtpAuthPassword, String smtpPort, HudsonMimeMessage msg) throws MessagingException { if (null != msg && null != msg.getSession()) { Session session = msg.getSession(); Transport t = null != session.getProperty("mail.transport.protocol") ? session.getTransport() : session.getTransport("smtp"); smtpPort = fixEmptyAndTrim(smtpPort); int port = -1; if (StringUtils.isNumeric(smtpPort)) { port = Integer.parseInt(smtpPort); } // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=399631 if (smtpAuthPassword != null && smtpAuthPassword.trim().length() == 0) { smtpAuthPassword = null; } t.connect(smtpServer, port, smtpAuthUserName, smtpAuthPassword); msg.saveChanges(); t.sendMessage(msg, msg.getAllRecipients()); t.close(); } } public boolean isApplicable(Class jobType) { return true; } } /** * Per user property that is e-mail address. */ public static class UserProperty extends hudson.model.UserProperty { /** * The user's e-mail address. Null to leave it to default. */ private final String emailAddress; public UserProperty(String emailAddress) { this.emailAddress = emailAddress; } @Exported public String getAddress() { if (emailAddress != null) { return emailAddress; } // try the inference logic return MailAddressResolver.resolve(user); } @Extension public static final class DescriptorImpl extends UserPropertyDescriptor { public String getDisplayName() { return Messages.Mailer_UserProperty_DisplayName(); } public UserProperty newInstance(User user) { return new UserProperty(null); } @Override public UserProperty newInstance(StaplerRequest req, JSONObject formData) throws FormException { return new UserProperty(req.getParameter("email.address")); } } } /** * Debug probe point to be activated by the scripting console. */ public static boolean debug = false; public static class ConverterImpl extends XStream2.PassthruConverter { public ConverterImpl(XStream2 xstream) { super(xstream); } @Override protected void callback(Mailer m, UnmarshallingContext context) { if (m.from != null || m.subject != null || m.failureOnly || m.charset != null) { OldDataMonitor.report(context, "1.10"); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy