1 package club.wpia.gigi.ping;
3 import java.io.IOException;
4 import java.security.GeneralSecurityException;
5 import java.security.KeyStore;
6 import java.security.cert.X509Certificate;
7 import java.sql.Timestamp;
9 import java.util.HashMap;
10 import java.util.LinkedList;
11 import java.util.Locale;
13 import java.util.Queue;
15 import club.wpia.gigi.GigiApiException;
16 import club.wpia.gigi.database.DatabaseConnection;
17 import club.wpia.gigi.database.DatabaseConnection.Link;
18 import club.wpia.gigi.database.GigiPreparedStatement;
19 import club.wpia.gigi.database.GigiResultSet;
20 import club.wpia.gigi.dbObjects.Certificate;
21 import club.wpia.gigi.dbObjects.Certificate.RevocationType;
22 import club.wpia.gigi.dbObjects.CertificateOwner;
23 import club.wpia.gigi.dbObjects.Domain;
24 import club.wpia.gigi.dbObjects.DomainPingConfiguration;
25 import club.wpia.gigi.dbObjects.DomainPingExecution;
26 import club.wpia.gigi.dbObjects.DomainPingType;
27 import club.wpia.gigi.dbObjects.Organisation;
28 import club.wpia.gigi.dbObjects.User;
29 import club.wpia.gigi.localisation.Language;
30 import club.wpia.gigi.output.ArrayIterable;
31 import club.wpia.gigi.output.template.MailTemplate;
32 import club.wpia.gigi.pages.account.domain.EditDomain;
33 import club.wpia.gigi.ping.DomainPinger.PingState;
34 import club.wpia.gigi.util.ServerConstants;
35 import club.wpia.gigi.util.ServerConstants.Host;
37 public class PingerDaemon extends Thread {
39 HashMap<DomainPingType, DomainPinger> pingers = new HashMap<>();
41 private KeyStore truststore;
43 private Queue<DomainPingConfiguration> toExecute = new LinkedList<>();
45 private final MailTemplate pingFailedMail = new MailTemplate(PingerDaemon.class.getResource("PingFailedWithActiveCertificates.templ"));
47 public PingerDaemon(KeyStore truststore) {
48 this.truststore = truststore;
53 try (Link l = DatabaseConnection.newLink(false)) {
55 } catch (InterruptedException e) {
60 public void runWithConnection() {
62 initializeConnectionUsage();
66 boolean worked = false;
68 DomainPingConfiguration conf;
69 while ((conf = toExecute.peek()) != null) {
76 long time = System.currentTimeMillis();
77 worked |= executeNeededPings(new Date(time));
82 } catch (InterruptedException e) {
84 } catch (Throwable t) {
88 } catch (InterruptedException e) {
94 protected void initializeConnectionUsage() {
95 pingers.put(DomainPingType.EMAIL, new EmailPinger());
96 pingers.put(DomainPingType.SSL, new SSLPinger(truststore));
97 pingers.put(DomainPingType.HTTP, new HTTPFetch());
98 pingers.put(DomainPingType.DNS, new DNSPinger());
101 public synchronized boolean executeNeededPings(Date time) {
102 boolean worked = false;
103 try (GigiPreparedStatement searchNeededPings = new GigiPreparedStatement("SELECT `d`.`id`, `dpl`.`configId`, `dpl`.`when`," //
104 // .. for all found pings we want to know, if we do not have a more
105 // recent successful ping
107 + " SELECT 1 FROM `domainPinglog` AS `dpl2`" //
108 + " WHERE `dpl`.`configId` = `dpl2`.`configId`" //
109 + " AND `dpl2`.state = 'success' AND `dpl2`.`when` > `dpl`.`when`) AS `resucceeded`" //
110 + " FROM `domainPinglog` AS `dpl`" //
111 // We search valid pings
112 + " INNER JOIN `pingconfig` AS `pc` ON `pc`.`id` = `dpl`.`configId` AND `pc`.`deleted` IS NULL" //
113 + " INNER JOIN `domains` AS `d` ON `d`.`id` = `pc`.`domainid` AND `d`.`deleted` IS NULL" //
114 // .. that failed, ..
115 + " WHERE `dpl`.`state` = 'failed'" //
116 // .. are older than 2 weeks
117 + " AND `dpl`.`when` <= ?::timestamp - interval '2 weeks'" //
118 // .. and are flagged for corrective action
119 + " AND `dpl`.`needsAction`" //
121 searchNeededPings.setTimestamp(1, new Timestamp(time.getTime()));
122 GigiResultSet rs = searchNeededPings.executeQuery();
123 try (GigiPreparedStatement updateDone = new GigiPreparedStatement("UPDATE `domainPinglog` SET `needsAction`=false WHERE `configId`=? AND `when`=?")) {
126 // Give this ping a last chance to succeed.
127 handle(DomainPingConfiguration.getById(rs.getInt(2)));
128 // We only consider revoking if this ping has not been
129 // superseded by a following successful ping.
130 if (rs.getBoolean(4)) {
131 Domain d = Domain.getById(rs.getInt(1));
133 boolean[] used = new boolean[DomainPingType.values().length];
134 // We only revoke, there are not 2 pings that are not
135 // 'strictly invalid'
136 for (DomainPingConfiguration cfg : d.getConfiguredPings()) {
137 if ( !cfg.isStrictlyInvalid(time) && !used[cfg.getType().ordinal()]) {
139 used[cfg.getType().ordinal()] = true;
146 for (Certificate c : d.fetchActiveCertificates()) {
148 c.revoke(RevocationType.PING_TIMEOUT);
152 updateDone.setInt(1, rs.getInt(2));
153 updateDone.setTimestamp(2, rs.getTimestamp(3));
154 updateDone.executeUpdate();
158 try (GigiPreparedStatement searchNeededPings = new GigiPreparedStatement("SELECT `pc`.`id`" //
159 + " FROM `pingconfig` AS `pc`" //
160 + " INNER JOIN `domains` AS `d` ON `d`.`id` = `pc`.`domainid`" //
161 + " WHERE `d`.`deleted` IS NULL" //
162 + " AND `pc`.`deleted` IS NULL" //
163 + " AND NOT EXISTS (" //
164 + " SELECT 1 FROM `domainPinglog` AS `dpl`" //
165 + " WHERE `dpl`.`configId` = `pc`.`id`" //
166 + " AND `dpl`.`when` >= ?::timestamp - interval '6 mons')")) {
167 searchNeededPings.setTimestamp(1, new Timestamp(time.getTime()));
168 GigiResultSet rs = searchNeededPings.executeQuery();
171 handle(DomainPingConfiguration.getById(rs.getInt("id")));
177 protected void handle(DomainPingConfiguration conf) {
178 DomainPingType type = conf.getType();
179 String config = conf.getInfo();
180 DomainPinger dp = pingers.get(type);
182 Domain target = conf.getTarget();
183 System.err.println("Executing " + dp + " on " + target + " (" + System.currentTimeMillis() + ")");
185 DomainPingExecution x = dp.ping(target, config, target.getOwner(), conf);
186 if (x.getState() == PingState.FAILED) {
187 Certificate[] cs = target.fetchActiveCertificates();
188 if (cs.length != 0) {
189 CertificateOwner o = target.getOwner();
190 Locale l = Locale.ENGLISH;
192 if (o instanceof User) {
193 l = ((User) o).getPreferredLocale();
194 contact = ((User) o).getEmail();
195 } else if (o instanceof Organisation) {
196 contact = ((Organisation) o).getContactEmail();
201 HashMap<String, Object> vars = new HashMap<>();
202 vars.put("valid", target.isVerified());
203 vars.put("domain", target.getSuffix());
204 vars.put("domainLink", "https://" + ServerConstants.getHostNamePortSecure(Host.WWW) + "/" + EditDomain.PATH + target.getId());
205 vars.put("certs", new ArrayIterable<Certificate>(cs) {
208 public void apply(Certificate t, Language l, Map<String, Object> vars) {
209 vars.put("serial", t.getSerial());
210 vars.put("ca", t.getParent().getKeyname());
212 X509Certificate c = t.cert();
213 vars.put("from", c.getNotBefore());
214 vars.put("to", c.getNotAfter());
215 } catch (IOException e) {
217 } catch (GeneralSecurityException e) {
219 } catch (GigiApiException e) {
225 pingFailedMail.sendMail(Language.getInstance(l), vars, contact);
226 System.out.println("Ping failed with active certificates");
229 } catch (Throwable t) {
231 DomainPinger.enterPingResult(conf, "error", "exception", null);
233 System.err.println("done (" + System.currentTimeMillis() + ")");
237 public synchronized void queue(DomainPingConfiguration toReping) {
239 toExecute.add(toReping);
240 while (toExecute.size() > 0) {
243 } catch (InterruptedException e) {