blob: c8f1cd70db9437bc65ebfe9a180e14fe471da898 [file] [log] [blame]
// 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.api.InitUtil.die;
import static com.google.gerrit.pgm.init.api.InitUtil.domainOf;
import static com.google.gerrit.pgm.init.api.InitUtil.isAnyAddress;
import static com.google.gerrit.pgm.init.api.InitUtil.toURI;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitFlags;
import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.pgm.init.api.Section;
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);
}
@Override
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.get("http", null, "sslKeyPassword");
if (ssl_pass == null || ssl_pass.isEmpty()) {
ssl_pass = SignedToken.generateRandomKey();
flags.sec.set("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 {
}
}