]> WPIA git - gigi.git/blob - src/club/wpia/gigi/Launcher.java
upd: rename package name and all references to it
[gigi.git] / src / club / wpia / gigi / Launcher.java
1 package club.wpia.gigi;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.net.HttpURLConnection;
9 import java.net.InetSocketAddress;
10 import java.security.GeneralSecurityException;
11 import java.security.Key;
12 import java.security.KeyStore;
13 import java.security.KeyStoreException;
14 import java.security.NoSuchAlgorithmException;
15 import java.security.UnrecoverableKeyException;
16 import java.security.cert.Certificate;
17 import java.security.cert.CertificateException;
18 import java.security.cert.CertificateFactory;
19 import java.security.cert.X509Certificate;
20 import java.util.List;
21 import java.util.Locale;
22 import java.util.Properties;
23 import java.util.TimeZone;
24
25 import javax.net.ssl.ExtendedSSLSession;
26 import javax.net.ssl.SNIHostName;
27 import javax.net.ssl.SNIServerName;
28 import javax.net.ssl.SSLEngine;
29 import javax.net.ssl.SSLParameters;
30 import javax.net.ssl.SSLSession;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.eclipse.jetty.http.HttpFields;
34 import org.eclipse.jetty.http.HttpHeader;
35 import org.eclipse.jetty.http.HttpVersion;
36 import org.eclipse.jetty.server.Connector;
37 import org.eclipse.jetty.server.Handler;
38 import org.eclipse.jetty.server.HttpConfiguration;
39 import org.eclipse.jetty.server.HttpConfiguration.Customizer;
40 import org.eclipse.jetty.server.HttpConnectionFactory;
41 import org.eclipse.jetty.server.Request;
42 import org.eclipse.jetty.server.SecureRequestCustomizer;
43 import org.eclipse.jetty.server.Server;
44 import org.eclipse.jetty.server.ServerConnector;
45 import org.eclipse.jetty.server.SessionManager;
46 import org.eclipse.jetty.server.SslConnectionFactory;
47 import org.eclipse.jetty.server.handler.ContextHandler;
48 import org.eclipse.jetty.server.handler.HandlerList;
49 import org.eclipse.jetty.server.handler.HandlerWrapper;
50 import org.eclipse.jetty.server.handler.ResourceHandler;
51 import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
52 import org.eclipse.jetty.servlet.ServletContextHandler;
53 import org.eclipse.jetty.servlet.ServletHolder;
54 import org.eclipse.jetty.util.log.Log;
55 import org.eclipse.jetty.util.resource.Resource;
56 import org.eclipse.jetty.util.ssl.SslContextFactory;
57
58 import club.wpia.gigi.api.GigiAPI;
59 import club.wpia.gigi.email.EmailProvider;
60 import club.wpia.gigi.natives.SetUID;
61 import club.wpia.gigi.util.CipherInfo;
62 import club.wpia.gigi.util.PEM;
63 import club.wpia.gigi.util.ServerConstants;
64
65 public class Launcher {
66
67     class ExtendedForwarded implements Customizer {
68
69         @Override
70         public void customize(Connector connector, HttpConfiguration config, Request request) {
71             HttpFields httpFields = request.getHttpFields();
72
73             String ip = httpFields.getStringField("X-Real-IP");
74             String proto = httpFields.getStringField("X-Real-Proto");
75             String cert = httpFields.getStringField("X-Client-Cert");
76             request.setSecure("https".equals(proto));
77             request.setScheme(proto);
78             if ( !"https".equals(proto)) {
79                 cert = null;
80
81             }
82             if (cert != null) {
83                 X509Certificate[] certs = new X509Certificate[1];
84                 try {
85                     certs[0] = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(new ByteArrayInputStream(PEM.decode("CERTIFICATE", cert)));
86                     request.setAttribute("javax.servlet.request.X509Certificate", certs);
87                 } catch (CertificateException e) {
88                     e.printStackTrace();
89                 }
90             }
91             if (ip != null) {
92                 String[] parts = ip.split(":");
93                 if (parts.length == 2) {
94                     request.setRemoteAddr(InetSocketAddress.createUnresolved(parts[0], Integer.parseInt(parts[1])));
95                 }
96             }
97
98         }
99     }
100
101     public static void main(String[] args) throws Exception {
102         System.setProperty("jdk.tls.ephemeralDHKeySize", "4096");
103         InputStream in;
104         if (args.length >= 1) {
105             in = new FileInputStream(new File(args[0]));
106         } else {
107             in = System.in;
108         }
109         new Launcher().boot(in);
110     }
111
112     Server s;
113
114     GigiConfig conf;
115
116     private boolean isSystemPort(int port) {
117         return 1 <= port && port <= 1024;
118     }
119
120     public synchronized void boot(InputStream in) throws Exception {
121         Locale.setDefault(Locale.ENGLISH);
122         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
123         HttpURLConnection.setFollowRedirects(false);
124
125         conf = GigiConfig.parse(in);
126         ServerConstants.init(conf.getMainProps());
127         initEmails(conf);
128
129         s = new Server();
130
131         initConnectors();
132         initHandlers();
133
134         s.start();
135         if ((isSystemPort(ServerConstants.getSecurePort()) || isSystemPort(ServerConstants.getPort())) && !System.getProperty("os.name").toLowerCase().contains("win")) {
136             String uid_s = conf.getMainProps().getProperty("gigi.uid", Integer.toString(65536 - 2));
137             String gid_s = conf.getMainProps().getProperty("gigi.gid", Integer.toString(65536 - 2));
138             try {
139                 int uid = Integer.parseInt(uid_s);
140                 int gid = Integer.parseInt(gid_s);
141                 if (uid == -1 && gid == -1) {
142                     // skip setuid step
143                 } else if (uid > 0 && gid > 0 && uid < 65536 && gid < 65536) {
144                     SetUID.Status status = new SetUID().setUid(uid, gid);
145                     if ( !status.getSuccess()) {
146                         Log.getLogger(Launcher.class).warn(status.getMessage());
147                     }
148                 } else {
149                     Log.getLogger(Launcher.class).warn("Invalid uid or gid (must satisfy 0 < id < 65536)");
150                 }
151             } catch (NumberFormatException e) {
152                 Log.getLogger(Launcher.class).warn("Invalid gigi.uid or gigi.gid", e);
153             }
154         }
155     }
156
157     private HttpConfiguration createHttpConfiguration() {
158         // SSL HTTP Configuration
159         HttpConfiguration httpsConfig = new HttpConfiguration();
160         httpsConfig.setSendServerVersion(false);
161         httpsConfig.setSendXPoweredBy(false);
162         return httpsConfig;
163     }
164
165     private void initConnectors() throws GeneralSecurityException, IOException {
166         HttpConfiguration httpConfig = createHttpConfiguration();
167         if (conf.getMainProps().getProperty("proxy", "false").equals("true")) {
168             httpConfig.addCustomizer(new ExtendedForwarded());
169             s.setConnectors(new Connector[] {
170                     ConnectorsLauncher.createConnector(conf, s, httpConfig, false)
171             });
172         } else {
173             HttpConfiguration httpsConfig = createHttpConfiguration();
174             // for client-cert auth
175             httpsConfig.addCustomizer(new SecureRequestCustomizer());
176             s.setConnectors(new Connector[] {
177                     ConnectorsLauncher.createConnector(conf, s, httpsConfig, true), ConnectorsLauncher.createConnector(conf, s, httpConfig, false)
178             });
179         }
180     }
181
182     private void initEmails(GigiConfig conf) throws GeneralSecurityException, IOException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
183         KeyStore privateStore = conf.getPrivateStore();
184         Certificate mail = null;
185         Key k = null;
186         if (privateStore != null && privateStore.containsAlias("mail")) {
187             mail = privateStore.getCertificate("mail");
188             k = privateStore.getKey("mail", conf.getPrivateStorePw().toCharArray());
189         }
190         EmailProvider.initSystem(conf.getMainProps(), mail, k);
191     }
192
193     private static class ConnectorsLauncher {
194
195         private ConnectorsLauncher() {}
196
197         protected static ServerConnector createConnector(GigiConfig conf, Server s, HttpConfiguration httpConfig, boolean doHttps) throws GeneralSecurityException, IOException {
198             ServerConnector connector;
199             int port;
200             if (doHttps) {
201                 connector = new ServerConnector(s, createConnectionFactory(conf), new HttpConnectionFactory(httpConfig));
202                 port = ServerConstants.getSecurePort();
203             } else {
204                 connector = new ServerConnector(s, new HttpConnectionFactory(httpConfig));
205                 port = ServerConstants.getPort();
206             }
207             if (port == -1) {
208                 connector.setInheritChannel(true);
209             } else {
210                 connector.setHost(conf.getMainProps().getProperty("host"));
211                 connector.setPort(port);
212             }
213             connector.setAcceptQueueSize(100);
214             return connector;
215         }
216
217         private static SslConnectionFactory createConnectionFactory(GigiConfig conf) throws GeneralSecurityException, IOException {
218             final SslContextFactory sslContextFactory = generateSSLContextFactory(conf, "www");
219             final SslContextFactory secureContextFactory = generateSSLContextFactory(conf, "secure");
220             secureContextFactory.setWantClientAuth(true);
221             secureContextFactory.setNeedClientAuth(true);
222             final SslContextFactory staticContextFactory = generateSSLContextFactory(conf, "static");
223             final SslContextFactory apiContextFactory = generateSSLContextFactory(conf, "api");
224             apiContextFactory.setWantClientAuth(true);
225             try {
226                 secureContextFactory.start();
227                 staticContextFactory.start();
228                 apiContextFactory.start();
229             } catch (Exception e) {
230                 e.printStackTrace();
231             }
232             return new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()) {
233
234                 @Override
235                 public boolean shouldRestartSSL() {
236                     return true;
237                 }
238
239                 @Override
240                 public SSLEngine restartSSL(SSLSession sslSession) {
241                     SSLEngine e2 = null;
242                     if (sslSession instanceof ExtendedSSLSession) {
243                         ExtendedSSLSession es = (ExtendedSSLSession) sslSession;
244                         List<SNIServerName> names = es.getRequestedServerNames();
245                         for (SNIServerName sniServerName : names) {
246                             if (sniServerName instanceof SNIHostName) {
247                                 SNIHostName host = (SNIHostName) sniServerName;
248                                 String hostname = host.getAsciiName();
249                                 if (hostname.equals(ServerConstants.getWwwHostName())) {
250                                     e2 = sslContextFactory.newSSLEngine();
251                                 } else if (hostname.equals(ServerConstants.getStaticHostName())) {
252                                     e2 = staticContextFactory.newSSLEngine();
253                                 } else if (hostname.equals(ServerConstants.getSecureHostName())) {
254                                     e2 = secureContextFactory.newSSLEngine();
255                                 } else if (hostname.equals(ServerConstants.getApiHostName())) {
256                                     e2 = apiContextFactory.newSSLEngine();
257                                 }
258                                 break;
259                             }
260                         }
261                     }
262                     if (e2 == null) {
263                         e2 = sslContextFactory.newSSLEngine(sslSession.getPeerHost(), sslSession.getPeerPort());
264                     }
265                     e2.setUseClientMode(false);
266                     return e2;
267                 }
268             };
269         }
270
271         private static SslContextFactory generateSSLContextFactory(GigiConfig conf, String alias) throws GeneralSecurityException, IOException {
272             SslContextFactory scf = new SslContextFactory() {
273
274                 String[] ciphers = null;
275
276                 @Override
277                 public void customize(SSLEngine sslEngine) {
278                     super.customize(sslEngine);
279
280                     SSLParameters ssl = sslEngine.getSSLParameters();
281                     ssl.setUseCipherSuitesOrder(true);
282                     if (ciphers == null) {
283                         ciphers = CipherInfo.filter(sslEngine.getSupportedCipherSuites());
284                     }
285
286                     ssl.setCipherSuites(ciphers);
287                     sslEngine.setSSLParameters(ssl);
288
289                 }
290
291             };
292             scf.setRenegotiationAllowed(false);
293
294             scf.setProtocol("TLS");
295             scf.setIncludeProtocols("TLSv1", "TLSv1.1", "TLSv1.2");
296             scf.setTrustStore(conf.getTrustStore());
297             KeyStore privateStore = conf.getPrivateStore();
298             scf.setKeyStorePassword(conf.getPrivateStorePw());
299             scf.setKeyStore(privateStore);
300             scf.setCertAlias(alias);
301             return scf;
302         }
303     }
304
305     private void initHandlers() throws GeneralSecurityException, IOException {
306         HandlerList hl = new HandlerList();
307         hl.setHandlers(new Handler[] {
308                 ContextLauncher.generateStaticContext(), ContextLauncher.generateGigiContexts(conf.getMainProps(), conf.getTrustStore()), ContextLauncher.generateAPIContext()
309         });
310         s.setHandler(hl);
311     }
312
313     private static class ContextLauncher {
314
315         private ContextLauncher() {}
316
317         protected static Handler generateGigiContexts(Properties conf, KeyStore trust) {
318             ServletHolder webAppServlet = new ServletHolder(new Gigi(conf, trust));
319
320             ContextHandler ch = generateGigiServletContext(webAppServlet);
321             ch.setVirtualHosts(new String[] {
322                     ServerConstants.getWwwHostName()
323             });
324             ContextHandler chSecure = generateGigiServletContext(webAppServlet);
325             chSecure.setVirtualHosts(new String[] {
326                     ServerConstants.getSecureHostName()
327             });
328
329             HandlerList hl = new HandlerList();
330             hl.setHandlers(new Handler[] {
331                     ch, chSecure
332             });
333             return hl;
334         }
335
336         private static ContextHandler generateGigiServletContext(ServletHolder webAppServlet) {
337             final ResourceHandler rh = generateResourceHandler();
338             rh.setResourceBase("static/www");
339
340             HandlerWrapper hw = new PolicyRedirector();
341             hw.setHandler(rh);
342
343             ServletContextHandler servlet = new ServletContextHandler(ServletContextHandler.SESSIONS);
344             servlet.setInitParameter(SessionManager.__SessionCookieProperty, "SomeCA-Session");
345             servlet.addServlet(webAppServlet, "/*");
346             ErrorPageErrorHandler epeh = new ErrorPageErrorHandler();
347             epeh.addErrorPage(404, "/error");
348             epeh.addErrorPage(403, "/denied");
349             servlet.setErrorHandler(epeh);
350
351             HandlerList hl = new HandlerList();
352             hl.setHandlers(new Handler[] {
353                     hw, servlet
354             });
355
356             ContextHandler ch = new ContextHandler();
357             ch.setHandler(hl);
358             return ch;
359         }
360
361         protected static Handler generateStaticContext() {
362             final ResourceHandler rh = generateResourceHandler();
363             rh.setResourceBase("static/static");
364
365             ContextHandler ch = new ContextHandler();
366             ch.setHandler(rh);
367             ch.setVirtualHosts(new String[] {
368                     ServerConstants.getStaticHostName()
369             });
370
371             return ch;
372         }
373
374         private static ResourceHandler generateResourceHandler() {
375             ResourceHandler rh = new ResourceHandler() {
376
377                 @Override
378                 protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType) {
379                     super.doResponseHeaders(response, resource, mimeType);
380                     response.setDateHeader(HttpHeader.EXPIRES.asString(), System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 7);
381                 }
382             };
383             rh.setEtags(true);
384             return rh;
385         }
386
387         protected static Handler generateAPIContext() {
388             ServletContextHandler sch = new ServletContextHandler();
389
390             sch.addVirtualHosts(new String[] {
391                     ServerConstants.getApiHostName()
392             });
393             sch.addServlet(new ServletHolder(new GigiAPI()), "/*");
394             return sch;
395         }
396
397     }
398
399 }