]> WPIA git - gigi.git/blob - src/org/cacert/gigi/Gigi.java
UPD: Remove "CAcert" from some menu names.
[gigi.git] / src / org / cacert / gigi / Gigi.java
1 package org.cacert.gigi;
2
3 import java.io.IOException;
4 import java.io.PrintWriter;
5 import java.security.KeyStore;
6 import java.security.cert.X509Certificate;
7 import java.util.Calendar;
8 import java.util.HashMap;
9 import java.util.LinkedList;
10 import java.util.Locale;
11 import java.util.Map;
12 import java.util.Properties;
13 import java.util.regex.Pattern;
14
15 import javax.servlet.ServletException;
16 import javax.servlet.http.HttpServlet;
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpServletResponse;
19 import javax.servlet.http.HttpSession;
20
21 import org.cacert.gigi.database.DatabaseConnection;
22 import org.cacert.gigi.dbObjects.User;
23 import org.cacert.gigi.localisation.Language;
24 import org.cacert.gigi.output.Menu;
25 import org.cacert.gigi.output.PageMenuItem;
26 import org.cacert.gigi.output.SimpleMenuItem;
27 import org.cacert.gigi.output.template.Form.CSRFException;
28 import org.cacert.gigi.output.template.Outputable;
29 import org.cacert.gigi.output.template.Template;
30 import org.cacert.gigi.pages.LoginPage;
31 import org.cacert.gigi.pages.LogoutPage;
32 import org.cacert.gigi.pages.MainPage;
33 import org.cacert.gigi.pages.Page;
34 import org.cacert.gigi.pages.PolicyIndex;
35 import org.cacert.gigi.pages.RootCertPage;
36 import org.cacert.gigi.pages.StaticPage;
37 import org.cacert.gigi.pages.TestSecure;
38 import org.cacert.gigi.pages.Verify;
39 import org.cacert.gigi.pages.account.ChangePasswordPage;
40 import org.cacert.gigi.pages.account.MyDetails;
41 import org.cacert.gigi.pages.account.certs.CertificateAdd;
42 import org.cacert.gigi.pages.account.certs.Certificates;
43 import org.cacert.gigi.pages.account.domain.DomainOverview;
44 import org.cacert.gigi.pages.account.mail.MailOverview;
45 import org.cacert.gigi.pages.admin.TTPAdminPage;
46 import org.cacert.gigi.pages.admin.support.FindDomainPage;
47 import org.cacert.gigi.pages.admin.support.FindUserPage;
48 import org.cacert.gigi.pages.admin.support.SupportUserDetailsPage;
49 import org.cacert.gigi.pages.error.AccessDenied;
50 import org.cacert.gigi.pages.error.PageNotFound;
51 import org.cacert.gigi.pages.main.RegisterPage;
52 import org.cacert.gigi.pages.orga.CreateOrgPage;
53 import org.cacert.gigi.pages.orga.ViewOrgPage;
54 import org.cacert.gigi.pages.wot.AssurePage;
55 import org.cacert.gigi.pages.wot.MyPoints;
56 import org.cacert.gigi.pages.wot.RequestTTPPage;
57 import org.cacert.gigi.ping.PingerDaemon;
58 import org.cacert.gigi.util.ServerConstants;
59
60 public class Gigi extends HttpServlet {
61
62     private boolean firstInstanceInited = false;
63
64     public static final String LOGGEDIN = "loggedin";
65
66     public static final String CERT_SERIAL = "org.cacert.gigi.serial";
67
68     public static final String CERT_ISSUER = "org.cacert.gigi.issuer";
69
70     public static final String USER = "user";
71
72     private static final long serialVersionUID = -6386785421902852904L;
73
74     private Template baseTemplate;
75
76     private LinkedList<Menu> categories = new LinkedList<Menu>();
77
78     private HashMap<String, Page> pages = new HashMap<String, Page>();
79
80     private HashMap<Page, String> reveresePages = new HashMap<Page, String>();
81
82     private Menu rootMenu;
83
84     private static Gigi instance;
85
86     private PingerDaemon pinger;
87
88     private KeyStore truststore;
89
90     private boolean testing;
91
92     public Gigi(Properties conf, KeyStore truststore) {
93         if (instance != null) {
94             throw new IllegalStateException("Multiple Gigi instances!");
95         }
96         testing = conf.getProperty("testing") != null;
97         instance = this;
98         DatabaseConnection.init(conf);
99         this.truststore = truststore;
100         pinger = new PingerDaemon(truststore);
101         pinger.start();
102     }
103
104     @Override
105     public void init() throws ServletException {
106         if ( !firstInstanceInited) {
107             putPage("/denied", new AccessDenied(), null);
108             putPage("/error", new PageNotFound(), null);
109             putPage("/login", new LoginPage("Password Login"), "CAcert.org");
110             getMenu("CAcert.org").addItem(new SimpleMenuItem("https://" + ServerConstants.getSecureHostNamePort() + "/login", "Certificate Login") {
111
112                 @Override
113                 public boolean isPermitted(User u) {
114                     return u == null;
115                 }
116             });
117             putPage("/", new MainPage("CAcert - Home"), null);
118             putPage("/roots", new RootCertPage(truststore), "CAcert.org");
119             putPage(ChangePasswordPage.PATH, new ChangePasswordPage(), "My Account");
120             putPage(LogoutPage.PATH, new LogoutPage("Logout"), "My Account");
121             putPage("/secure", new TestSecure(), null);
122             putPage(Verify.PATH, new Verify(), null);
123             putPage(AssurePage.PATH + "/*", new AssurePage(), "Web of Trust");
124             putPage(Certificates.PATH + "/*", new Certificates(), "Certificates");
125             putPage(MyDetails.PATH, new MyDetails(), "My Account");
126             putPage(RegisterPage.PATH, new RegisterPage(), "CAcert.org");
127             putPage(CertificateAdd.PATH, new CertificateAdd(), "Certificates");
128             putPage(MailOverview.DEFAULT_PATH, new MailOverview("My email addresses"), "Certificates");
129             putPage(DomainOverview.PATH + "*", new DomainOverview("Domains"), "Certificates");
130             putPage(MyPoints.PATH, new MyPoints("My Points"), "Web of Trust");
131             putPage(RequestTTPPage.PATH, new RequestTTPPage(), "Web of Trust");
132             putPage(TTPAdminPage.PATH + "/*", new TTPAdminPage(), "Admin");
133             putPage(CreateOrgPage.DEFAULT_PATH, new CreateOrgPage(), "Organisation Admin");
134             putPage(ViewOrgPage.DEFAULT_PATH + "/*", new ViewOrgPage(), "Organisation Admin");
135             putPage(FindDomainPage.PATH, new FindDomainPage("Find Domain"), "System Admin");
136             putPage(FindUserPage.PATH, new FindUserPage("Find User"), "System Admin");
137             putPage(SupportUserDetailsPage.PATH + "*", new SupportUserDetailsPage("Support: User Details"), null);
138             if (testing) {
139                 try {
140                     Class<?> manager = Class.forName("org.cacert.gigi.pages.Manager");
141                     Page p = (Page) manager.getMethod("getInstance").invoke(null);
142                     String pa = (String) manager.getField("PATH").get(null);
143                     putPage(pa + "/*", p, "Gigi test server");
144                 } catch (ReflectiveOperationException e) {
145                     e.printStackTrace();
146                 }
147             }
148
149             putPage("/wot/rules", new StaticPage("Web of Trust Rules", AssurePage.class.getResourceAsStream("Rules.templ")), "Web of Trust");
150             baseTemplate = new Template(Gigi.class.getResource("Gigi.templ"));
151             rootMenu = new Menu("Main");
152             Menu about = new Menu("About CAcert.org");
153             categories.add(about);
154
155             about.addItem(new SimpleMenuItem("//blog.cacert.org/", "CAcert News"));
156             about.addItem(new SimpleMenuItem("//wiki.cacert.org/", "Wiki Documentation"));
157             putPage(PolicyIndex.DEFAULT_PATH, new PolicyIndex(), "About CAcert.org");
158             about.addItem(new SimpleMenuItem("//wiki.cacert.org/FAQ/Privileges", "Point System"));
159             about.addItem(new SimpleMenuItem("//bugs.cacert.org/", "Bug Database"));
160             about.addItem(new SimpleMenuItem("//wiki.cacert.org/Board", "CAcert Board"));
161             about.addItem(new SimpleMenuItem("//lists.cacert.org/wws", "Mailing Lists"));
162             about.addItem(new SimpleMenuItem("//blog.CAcert.org/feed", "RSS News Feed"));
163
164             Menu languages = new Menu("Translations");
165             for (Locale l : Language.getSupportedLocales()) {
166                 languages.addItem(new SimpleMenuItem("?lang=" + l.toString(), l.getDisplayName(l)));
167             }
168             categories.add(languages);
169             for (Menu menu : categories) {
170                 menu.prepare();
171                 rootMenu.addItem(menu);
172             }
173
174             rootMenu.prepare();
175             firstInstanceInited = true;
176         }
177         super.init();
178     }
179
180     private void putPage(String path, Page p, String category) {
181         pages.put(path, p);
182         reveresePages.put(p, path);
183         if (category == null) {
184             return;
185         }
186         Menu m = getMenu(category);
187         m.addItem(new PageMenuItem(p));
188
189     }
190
191     private Menu getMenu(String category) {
192         Menu m = null;
193         for (Menu menu : categories) {
194             if (menu.getMenuName().equals(category)) {
195                 m = menu;
196                 break;
197             }
198         }
199         if (m == null) {
200             m = new Menu(category);
201             categories.add(m);
202         }
203         return m;
204     }
205
206     private static String staticTemplateVarHttp;
207
208     private static String staticTemplateVarHttps;
209
210     private static String getStaticTemplateVar(boolean https) {
211         if (https) {
212             if (staticTemplateVarHttps == null) {
213                 staticTemplateVarHttps = "https://" + ServerConstants.getStaticHostNamePortSecure();
214             }
215             return staticTemplateVarHttps;
216         } else {
217             if (staticTemplateVarHttp == null) {
218                 staticTemplateVarHttp = "http://" + ServerConstants.getStaticHostNamePort();
219             }
220             return staticTemplateVarHttp;
221         }
222     }
223
224     @Override
225     protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
226         boolean isSecure = req.getServerPort() == ServerConstants.getSecurePort();
227         addXSSHeaders(resp, isSecure);
228         // Firefox only sends this, if it's a cross domain access; safari sends
229         // it always
230         String originHeader = req.getHeader("Origin");
231         if (originHeader != null //
232                 &&
233                 !(originHeader.matches("^" + Pattern.quote("https://" + ServerConstants.getWwwHostNamePortSecure()) + "(/.*|)") || //
234                         originHeader.matches("^" + Pattern.quote("http://" + ServerConstants.getWwwHostNamePort()) + "(/.*|)") || //
235                 originHeader.matches("^" + Pattern.quote("https://" + ServerConstants.getSecureHostNamePort()) + "(/.*|)"))) {
236             resp.setContentType("text/html; charset=utf-8");
237             resp.getWriter().println("<html><head><title>Alert</title></head><body>No cross domain access allowed.<br/><b>If you don't know why you're seeing this you may have been fished! Please change your password immediately!</b></body></html>");
238             return;
239         }
240         HttpSession hs = req.getSession();
241         String clientSerial = (String) hs.getAttribute(CERT_SERIAL);
242         if (clientSerial != null) {
243             X509Certificate[] cert = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
244             if (cert == null || cert[0] == null//
245                     || !cert[0].getSerialNumber().toString(16).toUpperCase().equals(clientSerial) //
246                     || !cert[0].getIssuerDN().equals(hs.getAttribute(CERT_ISSUER))) {
247                 hs.invalidate();
248                 resp.sendError(403, "Certificate mismatch.");
249                 return;
250             }
251
252         }
253         if (req.getParameter("lang") != null) {
254             Locale l = Language.getLocaleFromString(req.getParameter("lang"));
255             Language lu = Language.getInstance(l);
256             req.getSession().setAttribute(Language.SESSION_ATTRIB_NAME, lu.getLocale());
257         }
258         final Page p = getPage(req.getPathInfo());
259
260         if (p != null) {
261             if ( !isSecure && (p.needsLogin() || p instanceof LoginPage || p instanceof RegisterPage)) {
262                 resp.sendRedirect("https://" + ServerConstants.getWwwHostNamePortSecure() + req.getPathInfo());
263                 return;
264             }
265             User currentPageUser = LoginPage.getUser(req);
266             if ( !p.isPermitted(currentPageUser)) {
267                 if (hs.getAttribute("loggedin") == null) {
268                     String request = req.getPathInfo();
269                     request = request.split("\\?")[0];
270                     hs.setAttribute(LoginPage.LOGIN_RETURNPATH, request);
271                     resp.sendRedirect("/login");
272                     return;
273                 }
274                 resp.sendError(403);
275                 return;
276             }
277             if (p.beforeTemplate(req, resp)) {
278                 return;
279             }
280             HashMap<String, Object> vars = new HashMap<String, Object>();
281             Outputable content = new Outputable() {
282
283                 @Override
284                 public void output(PrintWriter out, Language l, Map<String, Object> vars) {
285                     try {
286                         if (req.getMethod().equals("POST")) {
287                             if (req.getQueryString() != null) {
288                                 return;
289                             }
290                             p.doPost(req, resp);
291                         } else {
292                             p.doGet(req, resp);
293                         }
294                     } catch (CSRFException err) {
295                         try {
296                             resp.sendError(500, "CSRF invalid");
297                         } catch (IOException e) {
298                             e.printStackTrace();
299                         }
300                     } catch (IOException e) {
301                         e.printStackTrace();
302                     }
303
304                 }
305             };
306             vars.put(Menu.USER_VALUE, currentPageUser);
307             vars.put("menu", rootMenu);
308             vars.put("title", Page.getLanguage(req).getTranslation(p.getTitle()));
309             vars.put("static", getStaticTemplateVar(isSecure));
310             vars.put("year", Calendar.getInstance().get(Calendar.YEAR));
311             vars.put("content", content);
312             if (currentPageUser != null) {
313                 vars.put("loggedInAs", currentPageUser.getName().toString());
314             }
315             resp.setContentType("text/html; charset=utf-8");
316             baseTemplate.output(resp.getWriter(), Page.getLanguage(req), vars);
317         } else {
318             resp.sendError(404, "Page not found.");
319         }
320
321     }
322
323     private Page getPage(String pathInfo) {
324         if (pathInfo.endsWith("/") && !pathInfo.equals("/")) {
325             pathInfo = pathInfo.substring(0, pathInfo.length() - 1);
326         }
327         Page page = pages.get(pathInfo);
328         if (page != null) {
329             return page;
330         }
331         page = pages.get(pathInfo + "/*");
332         if (page != null) {
333             return page;
334         }
335         int idx = pathInfo.lastIndexOf('/');
336         pathInfo = pathInfo.substring(0, idx);
337
338         page = pages.get(pathInfo + "/*");
339         if (page != null) {
340             return page;
341         }
342         return null;
343
344     }
345
346     public static void addXSSHeaders(HttpServletResponse hsr, boolean doHttps) {
347         hsr.addHeader("Access-Control-Allow-Origin", "https://" + ServerConstants.getWwwHostNamePortSecure() + " https://" + ServerConstants.getSecureHostNamePort());
348         hsr.addHeader("Access-Control-Max-Age", "60");
349         if (doHttps) {
350             hsr.addHeader("Content-Security-Policy", getHttpsCSP());
351         } else {
352             hsr.addHeader("Content-Security-Policy", getHttpCSP());
353         }
354         hsr.addHeader("Strict-Transport-Security", "max-age=31536000");
355
356     }
357
358     private static String httpsCSP = null;
359
360     private static String httpCSP = null;
361
362     private static String getHttpsCSP() {
363         if (httpsCSP == null) {
364             StringBuffer csp = new StringBuffer();
365             csp.append("default-src 'none'");
366             csp.append(";font-src https://" + ServerConstants.getStaticHostNamePortSecure());
367             csp.append(";img-src https://" + ServerConstants.getStaticHostNamePortSecure());
368             csp.append(";media-src 'none'; object-src 'none'");
369             csp.append(";script-src https://" + ServerConstants.getStaticHostNamePortSecure());
370             csp.append(";style-src https://" + ServerConstants.getStaticHostNamePortSecure());
371             csp.append(";form-action https://" + ServerConstants.getSecureHostNamePort() + " https://" + ServerConstants.getWwwHostNamePortSecure());
372             csp.append(";report-url https://api.cacert.org/security/csp/report");
373             httpsCSP = csp.toString();
374         }
375         return httpsCSP;
376     }
377
378     private static String getHttpCSP() {
379         if (httpCSP == null) {
380             StringBuffer csp = new StringBuffer();
381             csp.append("default-src 'none'");
382             csp.append(";font-src http://" + ServerConstants.getStaticHostNamePort());
383             csp.append(";img-src http://" + ServerConstants.getStaticHostNamePort());
384             csp.append(";media-src 'none'; object-src 'none'");
385             csp.append(";script-src http://" + ServerConstants.getStaticHostNamePort());
386             csp.append(";style-src http://" + ServerConstants.getStaticHostNamePort());
387             csp.append(";form-action https://" + ServerConstants.getSecureHostNamePort() + " https://" + ServerConstants.getWwwHostNamePort());
388             csp.append(";report-url http://api.cacert.org/security/csp/report");
389             httpCSP = csp.toString();
390         }
391         return httpCSP;
392     }
393
394     public static String getPathByPage(Page p) {
395         return instance.reveresePages.get(p).replaceFirst("/?\\*$", "");
396     }
397
398     public static void notifyPinger() {
399         instance.pinger.interrupt();
400     }
401
402 }