1   package com.atlassian.mail.server.impl;
2   
3   import com.atlassian.mail.Email;
4   import com.atlassian.mail.MailConstants;
5   import com.atlassian.mail.MailException;
6   import com.atlassian.mail.MailProtocol;
7   import com.atlassian.mail.server.AbstractMailServer;
8   import com.atlassian.mail.server.MailServerManager;
9   import com.atlassian.mail.server.SMTPMailServer;
10  import com.atlassian.mail.server.impl.util.MessageCreator;
11  import org.apache.commons.lang.builder.ToStringBuilder;
12  
13  import javax.mail.Authenticator;
14  import javax.mail.MessagingException;
15  import javax.mail.PasswordAuthentication;
16  import javax.mail.Session;
17  import javax.mail.Transport;
18  import javax.mail.URLName;
19  import javax.mail.internet.MimeMessage;
20  import javax.naming.Context;
21  import javax.naming.InitialContext;
22  import javax.naming.NamingException;
23  import java.io.UnsupportedEncodingException;
24  import java.util.Properties;
25  
26  import static com.atlassian.mail.MailConstants.DEFAULT_SMTP_PROTOCOL;
27  import static com.atlassian.mail.MailConstants.DEFAULT_TIMEOUT;
28  
29  public class SMTPMailServerImpl extends AbstractMailServer implements SMTPMailServer
30  {
31      private boolean isSessionServer;
32      private String defaultFrom;
33      private String prefix;
34      private String jndiLocation;
35      private boolean removePrecedence;
36      private transient Session session;
37  
38      public SMTPMailServerImpl()
39      {
40      }
41  
42      public SMTPMailServerImpl(Long id, String name, String description, String from, String prefix, boolean isSession, String location, String username, String password)
43      {
44          this(id, name, description, from, prefix, isSession, DEFAULT_SMTP_PROTOCOL, location, MailConstants.DEFAULT_SMTP_PORT, false, username, password);
45      }
46  
47      public SMTPMailServerImpl(Long id, String name, String description, String from, String prefix, boolean isSession, MailProtocol protocol, String location, String smtpPort, boolean tlsRequired, String username, String password)
48      {
49          this(id, name, description, from, prefix, isSession, false, protocol, location, smtpPort, tlsRequired, username, password, DEFAULT_TIMEOUT);
50      }
51  
52      public SMTPMailServerImpl(Long id, String name, String description, String from, String prefix, boolean isSession, MailProtocol protocol, String location, String smtpPort, boolean tlsRequired, String username, String password, long timeout)
53      {
54          this(id, name, description, from, prefix, isSession, false, protocol, location, smtpPort, tlsRequired, username, password, timeout);
55      }
56  
57      public SMTPMailServerImpl(Long id, String name, String description, String from, String prefix, boolean isSession, MailProtocol protocol, String location, String smtpPort, boolean tlsRequired, String username, String password, long timeout, String socksHost, String socksPort)
58      {
59          this(id, name, description, from, prefix, isSession, false, protocol, location, smtpPort, tlsRequired, username, password, timeout, socksHost, socksPort);
60      }
61  
62      public SMTPMailServerImpl(Long id, String name, String description, String from, String prefix, boolean isSession, boolean removePrecedence, MailProtocol protocol, String location, String smtpPort, boolean tlsRequired, String username, String password, long timeout)
63      {
64          this(id, name, description, from, prefix, isSession, removePrecedence, protocol, location, smtpPort, tlsRequired, username, password, timeout, null, null);
65      }
66  
67      public SMTPMailServerImpl(Long id, String name, String description, String from, String prefix, boolean isSession, boolean removePrecedence, MailProtocol protocol, String location, String smtpPort, boolean tlsRequired, String username, String password, long timeout, String socksHost, String socksPort)
68      {
69          super(id, name, description, protocol, location, smtpPort, username, password, timeout, socksHost, socksPort);
70          setDefaultFrom(from);
71          setPrefix(prefix);
72          setSessionServer(isSession);
73          setRemovePrecedence(removePrecedence);
74          setTlsRequired(tlsRequired);
75  
76          if (isSession)
77          {
78              setJndiLocation(location);
79              setHostname(null);
80          }
81      }
82  
83      @Override
84      public String getJndiLocation()
85      {
86          return jndiLocation;
87      }
88  
89      @Override
90      public void setJndiLocation(String jndiLocation)
91      {
92          this.jndiLocation = jndiLocation;
93          propertyChanged();
94      }
95  
96      @Override
97      protected Authenticator getAuthenticator()
98      {
99          return new MyAuthenticator();
100     }
101 
102     /**
103      * get the mail session
104      */
105     @Override
106     public Session getSession() throws NamingException, MailException
107     {
108         if (session == null)
109 
110         {
111             if (isSessionServer())
112             {
113                 log.debug("Getting session from JNDI");
114                 Object jndiSession = getJndiSession();
115                 if (jndiSession instanceof javax.mail.Session)
116                 {
117                     session =  (Session) jndiSession;
118                 }
119                 else
120                 {
121                     log.error("Mail server at location [" + getJndiLocation() + "] is not of required type javax.mail.Session, or is in different classloader. " +
122                             "It is of type '" + (jndiSession != null ? jndiSession.getClass().getName() : null) + "' in classloader '"+jndiSession.getClass().getClassLoader()+"' instead");
123                     throw new IllegalArgumentException("Mail server at location [" + getJndiLocation() + "] is not of required type javax.mail.Session. ");
124                 }
125             }
126             else
127             {
128                 final Properties props = loadSystemProperties(getProperties());
129                 final Authenticator auth = isAuthenticating ? getAuthenticator() : null;
130                 session = getSessionFromServerManager(props, auth);
131                 if  (auth != null)
132                 {
133                     session.setPasswordAuthentication(new URLName(getMailProtocol().getProtocol(), getHostname(), Integer.parseInt(getPort()),null, null, null), new PasswordAuthentication(getUsername(),getPassword()));
134                 }
135             }
136         }
137         return session;
138     }
139 
140     protected Object getJndiSession() throws NamingException
141     {
142         Context ctx = new InitialContext();
143         return ctx.lookup(getJndiLocation());
144     }
145 
146     @Override
147     public void send(Email email) throws MailException
148     {
149         sendWithMessageId(email, null);
150     }
151 
152     @Override
153     public void sendWithMessageId(final Email email, final String messageId) throws MailException
154     {
155         try
156         {
157             Session thisSession = getSession();
158 
159             MimeMessage message = new ExtendedMimeMessage(thisSession, messageId);
160             MessageCreator messageCreator = new MessageCreator();
161 
162             messageCreator.updateMimeMessage(email, getDefaultFrom(), prefix, message);
163 
164             log.debug("Getting transport for protocol [" + getMailProtocol().getProtocol() + "]");
165             // Send the message using the transport configured for this mail server - JRA-24549/MAIL-61
166             final Transport transport = thisSession.getTransport(getMailProtocol().getProtocol());
167             try {
168                 if (log.isDebugEnabled())
169                 {
170                     log.debug("Got transport: [" + transport + "]. Connecting");
171                 }
172                 transport.connect();
173                 log.debug("Sending message");
174                 transport.sendMessage(message, message.getAllRecipients());
175             }
176             finally
177             {
178                 if (transport != null)
179                 {
180                     transport.close();
181                 }
182             }
183 
184             // If Message-Id is null or blank, then the MimeMessage may have created one automatically - lets retrieve it.
185             if (message.getHeader("Message-Id") != null && message.getHeader("Message-Id").length > 0)
186             {
187                 final String actualMessageId = message.getHeader("Message-Id")[0];
188                 if (log.isDebugEnabled())
189                 {
190                     log.debug("Message was sent with Message-Id " + actualMessageId);
191                 }
192                 email.setMessageId(actualMessageId);
193             }
194         }
195         catch (NamingException e)
196         {
197             throw new MailException(e);
198         }
199         catch (MessagingException e)
200         {
201             throw new MailException(e);
202         }
203         catch (UnsupportedEncodingException e)
204         {
205             log.error("Error setting the 'from' address with an email and the user's fullname", e);
206         }
207     }
208 
209 
210     /**
211      * Send a message - but don't throw exceptions, just log the errors
212      */
213     @Override
214     public void quietSend(Email email) throws MailException
215     {
216         try
217         {
218             send(email);
219         }
220         catch (Exception e)
221         {
222             log.error("Error sending mail. to:" + email.getTo() + ", cc:" + email.getCc() + ", bcc:" + email.getBcc() + ", subject:" + email.getSubject() + ", body:" + email.getBody() + ", mimeType:" + email.getMimeType() + ", encoding:" + email.getEncoding() + ", multipart:" + email.getMultipart() + ", error:" + e, e);
223         }
224     }
225 
226     @Override
227     public String getType()
228     {
229         return MailServerManager.SERVER_TYPES[1];
230     }
231 
232     @Override
233     public String getDefaultFrom()
234     {
235         return defaultFrom;
236     }
237 
238     @Override
239     public void setDefaultFrom(String defaultFrom)
240     {
241         this.defaultFrom = defaultFrom;
242         propertyChanged();
243     }
244 
245     @Override
246     public String getPrefix()
247     {
248         return prefix;
249     }
250 
251     @Override
252     public void setPrefix(String prefix)
253     {
254         this.prefix = prefix;
255     }
256 
257     @Override
258     public boolean isRemovePrecedence()
259     {
260         return removePrecedence;
261     }
262 
263     @Override
264     public void setRemovePrecedence(boolean precedence)
265     {
266         this.removePrecedence = precedence;
267         propertyChanged();
268     }
269 
270     @Override
271     public boolean isSessionServer()
272     {
273         return isSessionServer;
274     }
275 
276     @Override
277     public void setSessionServer(boolean sessionServer)
278     {
279         isSessionServer = sessionServer;
280         propertyChanged();
281     }
282 
283     private class MyAuthenticator extends Authenticator
284     {
285         public PasswordAuthentication getPasswordAuthentication()
286         {
287             return new PasswordAuthentication(getUsername(), getPassword());
288         }
289     }
290 
291     ///CLOVER:OFF
292     @SuppressWarnings ("RedundantIfStatement")
293     @Override
294     public boolean equals(Object o)
295     {
296         if (this == o)
297         {
298             return true;
299         }
300         if (!(o instanceof SMTPMailServerImpl))
301         {
302             return false;
303         }
304         if (!super.equals(o))
305         {
306             return false;
307         }
308 
309         final SMTPMailServerImpl smtpMailServer = (SMTPMailServerImpl) o;
310 
311         if (isSessionServer != smtpMailServer.isSessionServer)
312         {
313             return false;
314         }
315         if (defaultFrom != null ? !defaultFrom.equals(smtpMailServer.defaultFrom) : smtpMailServer.defaultFrom != null)
316         {
317             return false;
318         }
319         if (prefix != null ? !prefix.equals(smtpMailServer.prefix) : smtpMailServer.prefix != null)
320         {
321             return false;
322         }
323         if (removePrecedence !=  smtpMailServer.removePrecedence)
324         {
325             return false;
326         }
327 
328         return true;
329     }
330 
331     @Override
332     public int hashCode()
333     {
334         int result = super.hashCode();
335         result = 29 * result + (isSessionServer ? 1 : 0);
336         result = 29 * result + (defaultFrom != null ? defaultFrom.hashCode() : 0);
337         result = 29 * result + (prefix != null ? prefix.hashCode() : 0);
338         result = 29 * result + (removePrecedence ? 1 : 0);
339         return result;
340     }
341 
342     @Override
343     public String toString()
344     {
345        return new ToStringBuilder(this).append("id", getId()).append("name", getName()).append("description", getDescription()).append("server name", getHostname()).append("username", getUsername()).append("password", getPassword()).append("isSessionServer", isSessionServer).append("defaultFrom", defaultFrom).append("prefix", prefix).append("smtpPort", getPort()).toString();
346     }
347 
348     /**
349      * Discard the cached session when a property of the server changes.
350      */
351     @Override
352     protected void propertyChanged()
353     {
354         super.propertyChanged();
355         session = null;
356     }
357 }