// Copyright (C) 2009 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.gerrit.pgm.init; import static com.google.gerrit.common.FileUtil.chmod; import static com.google.gerrit.pgm.init.InitUtil.die; import static com.google.gerrit.pgm.init.InitUtil.domainOf; import static com.google.gerrit.pgm.init.InitUtil.isAnyAddress; import static com.google.gerrit.pgm.init.InitUtil.toURI; import com.google.gerrit.pgm.util.ConsoleUI; import com.google.gerrit.server.config.SitePaths; import com.google.gwtjsonrpc.server.SignedToken; import com.google.inject.Inject; import com.google.inject.Singleton; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; /** Initialize the {@code httpd} configuration section. */ @Singleton class InitHttpd implements InitStep { private final ConsoleUI ui; private final SitePaths site; private final InitFlags flags; private final Section httpd; private final Section gerrit; @Inject InitHttpd(final ConsoleUI ui, final SitePaths site, final InitFlags flags, final Section.Factory sections) { this.ui = ui; this.site = site; this.flags = flags; this.httpd = sections.get("httpd", null); this.gerrit = sections.get("gerrit", null); } public void run() throws IOException, InterruptedException { ui.header("HTTP Daemon"); boolean proxy = false, ssl = false; String address = "*"; int port = -1; String context = "/"; String listenUrl = httpd.get("listenUrl"); if (listenUrl != null && !listenUrl.isEmpty()) { try { final URI uri = toURI(listenUrl); proxy = uri.getScheme().startsWith("proxy-"); ssl = uri.getScheme().endsWith("https"); address = isAnyAddress(new URI(listenUrl)) ? "*" : uri.getHost(); port = uri.getPort(); context = uri.getPath(); } catch (URISyntaxException e) { System.err.println("warning: invalid httpd.listenUrl " + listenUrl); } } proxy = ui.yesno(proxy, "Behind reverse proxy"); if (proxy) { ssl = ui.yesno(ssl, "Proxy uses SSL (https://)"); context = ui.readString(context, "Subdirectory on proxy server"); } else { ssl = ui.yesno(ssl, "Use SSL (https://)"); context = "/"; } address = ui.readString(address, "Listen on address"); if (port < 0) { if (proxy) { port = 8081; } else if (ssl) { port = 8443; } else { port = 8080; } } port = ui.readInt(port, "Listen on port"); final StringBuilder urlbuf = new StringBuilder(); urlbuf.append(proxy ? "proxy-" : ""); urlbuf.append(ssl ? "https" : "http"); urlbuf.append("://"); urlbuf.append(address); if (0 <= port) { urlbuf.append(":"); urlbuf.append(port); } urlbuf.append(context); httpd.set("listenUrl", urlbuf.toString()); URI uri; try { uri = toURI(httpd.get("listenUrl")); if (uri.getScheme().startsWith("proxy-")) { // If its a proxy URL, assume the reverse proxy is on our system // at the protocol standard ports (so omit the ports from the URL). // String s = uri.getScheme().substring("proxy-".length()); uri = new URI(s + "://" + uri.getHost() + uri.getPath()); } } catch (URISyntaxException e) { throw die("invalid httpd.listenUrl"); } gerrit.string("Canonical URL", "canonicalWebUrl", uri.toString()); generateSslCertificate(); } private void generateSslCertificate() throws IOException, InterruptedException { final String listenUrl = httpd.get("listenUrl"); if (!listenUrl.startsWith("https://")) { // We aren't responsible for SSL processing. // return; } String hostname; try { String url = gerrit.get("canonicalWebUrl"); if (url == null || url.isEmpty()) { url = listenUrl; } hostname = toURI(url).getHost(); } catch (URISyntaxException e) { System.err.println("Invalid httpd.listenUrl, not checking certificate"); return; } final File store = site.ssl_keystore; if (!ui.yesno(!store.exists(), "Create new self-signed SSL certificate")) { return; } String ssl_pass = flags.sec.getString("http", null, "sslKeyPassword"); if (ssl_pass == null || ssl_pass.isEmpty()) { ssl_pass = SignedToken.generateRandomKey(); flags.sec.setString("httpd", null, "sslKeyPassword", ssl_pass); } hostname = ui.readString(hostname, "Certificate server name"); final String validity = ui.readString("365", "Certificate expires in (days)"); final String dname = "CN=" + hostname + ",OU=Gerrit Code Review,O=" + domainOf(hostname); final File tmpdir = new File(site.etc_dir, "tmp.sslcertgen"); if (!tmpdir.mkdir()) { throw die("Cannot create directory " + tmpdir); } chmod(0600, tmpdir); final File tmpstore = new File(tmpdir, "keystore"); Runtime.getRuntime().exec(new String[] {"keytool", // "-keystore", tmpstore.getAbsolutePath(), // "-storepass", ssl_pass, // "-genkeypair", // "-alias", hostname, // "-keyalg", "RSA", // "-validity", validity, // "-dname", dname, // "-keypass", ssl_pass, // }).waitFor(); chmod(0600, tmpstore); if (!tmpstore.renameTo(store)) { throw die("Cannot rename " + tmpstore + " to " + store); } if (!tmpdir.delete()) { throw die("Cannot delete " + tmpdir); } } @Override public void postRun() throws Exception { } }