]> WPIA git - gigi.git/blob - src/org/cacert/gigi/dbObjects/Domain.java
FIX: Adding the Blocked Assuree-Group to the enum.
[gigi.git] / src / org / cacert / gigi / dbObjects / Domain.java
1 package org.cacert.gigi.dbObjects;
2
3 import java.io.IOException;
4 import java.net.IDN;
5 import java.util.Arrays;
6 import java.util.Collections;
7 import java.util.HashSet;
8 import java.util.LinkedList;
9 import java.util.List;
10 import java.util.Properties;
11 import java.util.Set;
12
13 import org.cacert.gigi.GigiApiException;
14 import org.cacert.gigi.database.DatabaseConnection;
15 import org.cacert.gigi.database.GigiPreparedStatement;
16 import org.cacert.gigi.database.GigiResultSet;
17 import org.cacert.gigi.dbObjects.DomainPingConfiguration.PingType;
18 import org.cacert.gigi.util.PublicSuffixes;
19
20 public class Domain implements IdCachable {
21
22     public class DomainPingExecution {
23
24         private String state;
25
26         private String type;
27
28         private String info;
29
30         private String result;
31
32         private DomainPingConfiguration config;
33
34         public DomainPingExecution(GigiResultSet rs) {
35             state = rs.getString(1);
36             type = rs.getString(2);
37             info = rs.getString(3);
38             result = rs.getString(4);
39             config = DomainPingConfiguration.getById(rs.getInt(5));
40         }
41
42         public String getState() {
43             return state;
44         }
45
46         public String getType() {
47             return type;
48         }
49
50         public String getInfo() {
51             return info;
52         }
53
54         public String getResult() {
55             return result;
56         }
57
58         public DomainPingConfiguration getConfig() {
59             return config;
60         }
61
62     }
63
64     private User owner;
65
66     private String suffix;
67
68     private int id;
69
70     private static final Set<String> IDNEnabledTLDs;
71     static {
72         Properties CPS = new Properties();
73         try {
74             CPS.load(Domain.class.getResourceAsStream("CPS.properties"));
75             IDNEnabledTLDs = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(CPS.getProperty("IDN-enabled").split(","))));
76         } catch (IOException e) {
77             throw new Error(e);
78         }
79     }
80
81     private Domain(int id) {
82         GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT memid, domain FROM `domains` WHERE id=? AND deleted IS NULL");
83         ps.setInt(1, id);
84
85         GigiResultSet rs = ps.executeQuery();
86         if ( !rs.next()) {
87             throw new IllegalArgumentException("Invalid domain id " + id);
88         }
89         this.id = id;
90         owner = User.getById(rs.getInt(1));
91         suffix = rs.getString(2);
92         rs.close();
93     }
94
95     public Domain(User owner, String suffix) throws GigiApiException {
96         checkCertifyableDomain(suffix, owner.isInGroup(Group.CODESIGNING));
97         this.owner = owner;
98         this.suffix = suffix;
99
100     }
101
102     public static void checkCertifyableDomain(String s, boolean hasPunycodeRight) throws GigiApiException {
103         String[] parts = s.split("\\.", -1);
104         if (parts.length < 2) {
105             throw new GigiApiException("Domain does not contain '.'.");
106         }
107         for (int i = parts.length - 1; i >= 0; i--) {
108             if ( !isVaildDomainPart(parts[i], hasPunycodeRight)) {
109                 throw new GigiApiException("Syntax error in Domain");
110             }
111         }
112         String publicSuffix = PublicSuffixes.getInstance().getRegistrablePart(s);
113         if ( !s.equals(publicSuffix)) {
114             throw new GigiApiException("You may only register a domain with exactly one lable before the public suffix.");
115         }
116         checkPunycode(parts[0], s.substring(parts[0].length() + 1));
117     }
118
119     private static void checkPunycode(String label, String domainContext) throws GigiApiException {
120         if (label.charAt(2) != '-' || label.charAt(3) != '-') {
121             return; // is no punycode
122         }
123         if ( !IDNEnabledTLDs.contains(domainContext)) {
124             throw new GigiApiException("Punycode label could not be positively verified.");
125         }
126         if ( !label.startsWith("xn--")) {
127             throw new GigiApiException("Unknown ACE prefix.");
128         }
129         try {
130             String unicode = IDN.toUnicode(label);
131             if (unicode.startsWith("xn--")) {
132                 throw new GigiApiException("Punycode label could not be positively verified.");
133             }
134         } catch (IllegalArgumentException e) {
135             throw new GigiApiException("Punycode label could not be positively verified.");
136         }
137     }
138
139     public static boolean isVaildDomainPart(String s, boolean allowPunycode) {
140         if ( !s.matches("[a-z0-9-]+")) {
141             return false;
142         }
143         if (s.charAt(0) == '-' || s.charAt(s.length() - 1) == '-') {
144             return false;
145         }
146         if (s.length() > 63) {
147             return false;
148         }
149         boolean canBePunycode = s.length() >= 4 && s.charAt(2) == '-' && s.charAt(3) == '-';
150         if (canBePunycode && !allowPunycode) {
151             return false;
152         }
153         return true;
154     }
155
156     private static void checkInsert(String suffix) throws GigiApiException {
157         GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT 1 FROM `domains` WHERE (domain=? OR (CONCAT('.', domain)=RIGHT(?,LENGTH(domain)+1)  OR RIGHT(domain,LENGTH(?)+1)=CONCAT('.',?))) AND deleted IS NULL");
158         ps.setString(1, suffix);
159         ps.setString(2, suffix);
160         ps.setString(3, suffix);
161         ps.setString(4, suffix);
162         GigiResultSet rs = ps.executeQuery();
163         boolean existed = rs.next();
164         rs.close();
165         if (existed) {
166             throw new GigiApiException("Domain could not be inserted. Domain is already valid.");
167         }
168     }
169
170     public void insert() throws GigiApiException {
171         if (id != 0) {
172             throw new GigiApiException("already inserted.");
173         }
174         synchronized (Domain.class) {
175             checkInsert(suffix);
176             GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO `domains` SET memid=?, domain=?");
177             ps.setInt(1, owner.getId());
178             ps.setString(2, suffix);
179             ps.execute();
180             id = ps.lastInsertId();
181             myCache.put(this);
182         }
183     }
184
185     public void delete() throws GigiApiException {
186         if (id == 0) {
187             throw new GigiApiException("not inserted.");
188         }
189         GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE `domains` SET deleted=CURRENT_TIMESTAMP WHERE id=?");
190         ps.setInt(1, id);
191         ps.execute();
192     }
193
194     public User getOwner() {
195         return owner;
196     }
197
198     @Override
199     public int getId() {
200         return id;
201     }
202
203     public String getSuffix() {
204         return suffix;
205     }
206
207     private LinkedList<DomainPingConfiguration> configs = null;
208
209     public List<DomainPingConfiguration> getConfiguredPings() throws GigiApiException {
210         LinkedList<DomainPingConfiguration> configs = this.configs;
211         if (configs == null) {
212             configs = new LinkedList<>();
213             GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id FROM pingconfig WHERE domainid=?");
214             ps.setInt(1, id);
215             GigiResultSet rs = ps.executeQuery();
216             while (rs.next()) {
217                 configs.add(DomainPingConfiguration.getById(rs.getInt(1)));
218             }
219             rs.close();
220             this.configs = configs;
221
222         }
223         return Collections.unmodifiableList(configs);
224     }
225
226     public void addPing(PingType type, String config) throws GigiApiException {
227         GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO pingconfig SET domainid=?, type=?, info=?");
228         ps.setInt(1, id);
229         ps.setString(2, type.toString().toLowerCase());
230         ps.setString(3, config);
231         ps.execute();
232         configs = null;
233     }
234
235     public void verify(String hash) throws GigiApiException {
236         GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE domainPinglog SET state='success' WHERE challenge=? AND configId IN (SELECT id FROM pingconfig WHERE domainId=?)");
237         ps.setString(1, hash);
238         ps.setInt(2, id);
239         ps.executeUpdate();
240     }
241
242     public boolean isVerified() {
243         GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT 1 FROM domainPinglog INNER JOIN pingconfig ON pingconfig.id=domainPinglog.configId WHERE domainid=? AND state='success'");
244         ps.setInt(1, id);
245         GigiResultSet rs = ps.executeQuery();
246         return rs.next();
247     }
248
249     public DomainPingExecution[] getPings() throws GigiApiException {
250         GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT state, type, info, result, configId FROM domainPinglog INNER JOIN pingconfig ON pingconfig.id=domainPinglog.configid WHERE pingconfig.domainid=? ORDER BY `when` DESC;");
251         ps.setInt(1, id);
252         GigiResultSet rs = ps.executeQuery();
253         rs.last();
254         DomainPingExecution[] contents = new DomainPingExecution[rs.getRow()];
255         rs.beforeFirst();
256         for (int i = 0; i < contents.length && rs.next(); i++) {
257             contents[i] = new DomainPingExecution(rs);
258         }
259         return contents;
260
261     }
262
263     private static ObjectCache<Domain> myCache = new ObjectCache<>();
264
265     public static synchronized Domain getById(int id) throws IllegalArgumentException {
266         Domain em = myCache.get(id);
267         if (em == null) {
268             myCache.put(em = new Domain(id));
269         }
270         return em;
271     }
272
273     public static int searchUserIdByDomain(String domain) {
274         GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT memid FROM domains WHERE domain = ?");
275         ps.setString(1, domain);
276         GigiResultSet res = ps.executeQuery();
277         res.beforeFirst();
278         if (res.next()) {
279             return res.getInt(1);
280         } else {
281             return -1;
282         }
283     }
284
285 }