1 package org.cacert.gigi.email;
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStreamReader;
6 import java.io.OutputStreamWriter;
7 import java.io.PrintWriter;
8 import java.net.Socket;
9 import java.security.GeneralSecurityException;
10 import java.security.Key;
11 import java.security.PrivateKey;
12 import java.security.cert.Certificate;
13 import java.security.cert.X509Certificate;
14 import java.util.Arrays;
15 import java.util.Comparator;
16 import java.util.Properties;
17 import java.util.regex.Pattern;
19 import javax.naming.NamingException;
20 import javax.net.ssl.SSLSocketFactory;
22 import org.cacert.gigi.crypto.SMIME;
23 import org.cacert.gigi.database.GigiPreparedStatement;
24 import org.cacert.gigi.util.DNSUtil;
26 public abstract class EmailProvider {
28 public abstract void sendmail(String to, String subject, String message, String from, String replyto, String toname, String fromname, String errorsto, boolean extra) throws IOException;
30 private static EmailProvider instance;
32 private X509Certificate c;
36 protected void init(Certificate c, Key k) {
37 this.c = (X509Certificate) c;
38 this.k = (PrivateKey) k;
41 protected final void sendSigned(String contents, PrintWriter output) throws IOException, GeneralSecurityException {
42 if (k == null || c == null) {
43 output.println("Content-Transfer-Encoding: base64");
45 output.print(contents);
47 SMIME.smime(contents, k, c, output);
51 public static EmailProvider getInstance() {
55 protected static void setInstance(EmailProvider instance) {
56 EmailProvider.instance = instance;
59 public static void initSystem(Properties conf, Certificate cert, Key pk) {
61 Class<?> c = Class.forName(conf.getProperty("emailProvider"));
62 EmailProvider ep = (EmailProvider) c.getDeclaredConstructor(Properties.class).newInstance(conf);
65 } catch (ReflectiveOperationException e) {
70 public static final String OK = "OK";
72 public static final String FAIL = "FAIL";
74 public static final Pattern MAIL = Pattern.compile("^([a-zA-Z0-9])+([a-zA-Z0-9\\+\\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\\._-]+)+$");
76 public String checkEmailServer(int forUid, String address) throws IOException {
77 if (MAIL.matcher(address).matches()) {
78 String[] parts = address.split("@", 2);
79 String domain = parts[1];
83 mxhosts = DNSUtil.getMXEntries(domain);
84 } catch (NamingException e1) {
85 return "MX lookup for your hostname failed.";
89 for (String host : mxhosts) {
90 host = host.split(" ", 2)[1];
91 if (host.endsWith(".")) {
92 host = host.substring(0, host.length() - 1);
94 return "Strange MX records.";
96 try (Socket s = new Socket(host, 25); BufferedReader br0 = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));//
97 PrintWriter pw0 = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), "UTF-8"))) {
98 BufferedReader br = br0;
101 if ( !Sendmail.readSMTPResponse(br, 220)) {
105 pw.print("EHLO www.cacert.org\r\n");
107 boolean starttls = false;
109 line = br.readLine();
113 starttls |= line.substring(4).equals("STARTTLS");
114 } while (line.startsWith("250-"));
115 if (line == null || !line.startsWith("250 ")) {
120 pw.print("STARTTLS\r\n");
122 if ( !Sendmail.readSMTPResponse(br, 220)) {
125 Socket s1 = ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(s, host, 25, true);
126 br = new BufferedReader(new InputStreamReader(s1.getInputStream(), "UTF-8"));
127 pw = new PrintWriter(new OutputStreamWriter(s1.getOutputStream(), "UTF-8"));
128 pw.print("EHLO www.cacert.org\r\n");
130 if ( !Sendmail.readSMTPResponse(br, 250)) {
135 pw.print("MAIL FROM: <returns@cacert.org>\r\n");
138 if ( !Sendmail.readSMTPResponse(br, 250)) {
141 pw.print("RCPT TO: <" + address + ">\r\n");
144 if ( !Sendmail.readSMTPResponse(br, 250)) {
147 pw.print("QUIT\r\n");
149 if ( !Sendmail.readSMTPResponse(br, 221)) {
153 try (GigiPreparedStatement statmt = new GigiPreparedStatement("INSERT INTO `emailPinglog` SET `when`=NOW(), `email`=?, `result`=?, `uid`=?, `type`='fast', `status`=?::`pingState`")) {
154 statmt.setString(1, address);
155 statmt.setString(2, line);
156 statmt.setInt(3, forUid);
157 statmt.setString(4, "success");
161 if (line == null || !line.startsWith("250")) {
170 try (GigiPreparedStatement statmt = new GigiPreparedStatement("INSERT INTO `emailPinglog` SET `when`=NOW(), `email`=?, `result`=?, `uid`=?, `type`='fast', `status`=?::`pingState`")) {
171 statmt.setString(1, address);
172 statmt.setString(2, "Failed to make a connection to the mail server");
173 statmt.setInt(3, forUid);
174 statmt.setString(4, "failed");
180 private static void sortMX(String[] mxhosts) {
181 Arrays.sort(mxhosts, new Comparator<String>() {
184 public int compare(String o1, String o2) {
185 int i1 = Integer.parseInt(o1.split(" ")[0]);
186 int i2 = Integer.parseInt(o2.split(" ")[0]);
187 return Integer.compare(i1, i2);