Add slave mode to ssh daemon

The daemon can now be ran on a replication server by passing the
--slave option to the daemon command.  This will start the ssh
server, but disables the approve, create-project, receive-pack,
and replicate commands.  This is useful for pulling code from
a replication server through gerrit, mostly for authentication
purposes.

Bug: GERRIT-216
Change-Id: I742a27ce81e3a5e7524210b673267ce70daf0f9e
Signed-off-by: Brad Larson <bklarson@gmail.com>
[sp: daemon documentation, minor guice usage cleanup in daemon]
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/Documentation/index.txt b/Documentation/index.txt
index bb94c7e..8169f31 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -17,6 +17,7 @@
 * link:licenses.html[Licenses and Notices]
 * link:install.html[Installation Guide]
 * link:project-setup.html[Project Setup]
+* link:pgm-index.html[Server Programs]
 
 Configuration
 -------------
diff --git a/Documentation/pgm-daemon.txt b/Documentation/pgm-daemon.txt
new file mode 100644
index 0000000..7a96735
--- /dev/null
+++ b/Documentation/pgm-daemon.txt
@@ -0,0 +1,85 @@
+daemon
+======
+
+NAME
+----
+daemon - Git enabled SSH daemon
+
+SYNOPSIS
+--------
+[verse]
+'java' -DGerritServer=PATH -jar gerrit.war 'daemon' [\--slave]
+
+DESCRIPTION
+-----------
+Runs the Gerrit SSH daemon on the local system, configured as per
+the local copy of link:config-gerrit.html[gerrit.config].
+
+The path to gerrit.config is read from the metadata database,
+which requires that all slaves (and master) reading from the same
+database must place gerrit.config at the same location on the local
+filesystem.  However, any option within gerrit.config, including
+link:config-gerrit.html#gerrit.basePath[gerrit.basePath] may be set
+to different values.
+
+OPTIONS
+-------
+
+-DGerritServer=PATH::
+	Path of the GerritServer.properties file, which describes
+	how to connect to the database.  To obtain a template of this
+	file get it from the WAR:
++
+====
+java -jar gerrit.war \--cat extra/GerritServer.properties_example >GerritServer.properties
+====
+
+\--slave::
+	Run in slave mode, permitting only read operations
+    by clients.  Commands which modify state such as
+    link:cmd-receive-pack.html[recieve-pack] (creates new changes
+    or updates existing ones) or link:cmd-approve.html[approve]
+    (sets approve marks) are disabled.
+
+CONTEXT
+-------
+This command can only be run on a server which has direct
+connectivity to the metadata database, and local access to the
+managed Git repositories.
+
+KNOWN ISSUES
+------------
+Slave daemon caches can quickly become out of date when modifications
+are made on the master.  The following configuration is suggested in
+a slave to reduce the maxAge for each cache entry, so that changes
+are recognized in a reasonable period of time:
+
+----
+[cache "accounts"]
+  maxAge = 5 min
+[cache "accounts_byemail"]
+  maxAge = 5 min
+[cache "diff"]
+  maxAge = 5 min
+[cache "groups"]
+  maxAge = 5 min
+[cache "projects"]
+  maxAge = 5 min
+[cache "sshkeys"]
+  maxAge = 5 min
+----
+
+and if LDAP support was enabled, also include:
+----
+[cache "ldap_groups"]
+  maxAge = 5 min
+[cache "ldap_usernames"]
+  maxAge = 5 min
+----
+
+Automatic cache coherency between master and slave systems is
+planned to be implemented in a future version.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/pgm-index.txt b/Documentation/pgm-index.txt
new file mode 100644
index 0000000..d89771c
--- /dev/null
+++ b/Documentation/pgm-index.txt
@@ -0,0 +1,23 @@
+Gerrit2 - Server Programs
+=========================
+
+Server side programs can be started by executing the WAR file
+through the Java command line.  For example:
+
+  $ java -jar gerrit.war program [options]
+
+[[programs]]Programs
+--------------------
+
+CreateSchema::
+  Initialize a new database schema.
+
+link:pgm-daemon.html[daemon]::
+  Git enabled SSH daemon.
+
+version::
+  Display the release version of Gerrit Code Review.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/src/main/java/com/google/gerrit/pgm/Daemon.java b/src/main/java/com/google/gerrit/pgm/Daemon.java
index de16d3d..473c3a1 100644
--- a/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -18,16 +18,39 @@
 import com.google.gerrit.server.config.GerritGlobalModule;
 import com.google.gerrit.server.ssh.SshDaemon;
 import com.google.gerrit.server.ssh.SshModule;
+import com.google.gerrit.server.ssh.commands.MasterCommandModule;
+import com.google.gerrit.server.ssh.commands.SlaveCommandModule;
 import com.google.inject.Injector;
+import com.google.inject.Module;
+
+import org.kohsuke.args4j.Option;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /** Run only the SSH daemon portions of Gerrit. */
 public class Daemon extends AbstractProgram {
+
+  @Option(name = "--slave", usage = "support fetch only")
+  boolean slave;
+
   @Override
   public int run() throws Exception {
     Injector sysInjector = GerritGlobalModule.createInjector();
-    Injector sshInjector = sysInjector.createChildInjector(new SshModule());
+    Injector sshInjector = createSshInjector(sysInjector);
     sysInjector.getInstance(CachePool.class).start();
     sshInjector.getInstance(SshDaemon.class).start();
     return never();
   }
+
+  private Injector createSshInjector(final Injector sysInjector) {
+    final List<Module> modules = new ArrayList<Module>();
+    modules.add(new SshModule());
+    if (slave) {
+      modules.add(new SlaveCommandModule());
+    } else {
+      modules.add(new MasterCommandModule());
+    }
+    return sysInjector.createChildInjector(modules);
+  }
 }
diff --git a/src/main/java/com/google/gerrit/server/http/GerritServletConfig.java b/src/main/java/com/google/gerrit/server/http/GerritServletConfig.java
index fa74955..df21460 100644
--- a/src/main/java/com/google/gerrit/server/http/GerritServletConfig.java
+++ b/src/main/java/com/google/gerrit/server/http/GerritServletConfig.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.server.config.GerritGlobalModule;
 import com.google.gerrit.server.ssh.SshDaemon;
 import com.google.gerrit.server.ssh.SshModule;
+import com.google.gerrit.server.ssh.commands.MasterCommandModule;
 import com.google.inject.ConfigurationException;
 import com.google.inject.CreationException;
 import com.google.inject.Guice;
@@ -108,7 +109,8 @@
   }
 
   private Injector createSshInjector() {
-    return sysInjector.createChildInjector(new SshModule());
+    return sysInjector.createChildInjector(new SshModule(),
+        new MasterCommandModule());
   }
 
   private Injector createWebInjector() {
diff --git a/src/main/java/com/google/gerrit/server/ssh/commands/DefaultCommandModule.java b/src/main/java/com/google/gerrit/server/ssh/commands/DefaultCommandModule.java
index 488a241..19cbf90 100644
--- a/src/main/java/com/google/gerrit/server/ssh/commands/DefaultCommandModule.java
+++ b/src/main/java/com/google/gerrit/server/ssh/commands/DefaultCommandModule.java
@@ -27,13 +27,14 @@
     final CommandName git = Commands.named("git");
     final CommandName gerrit = Commands.named("gerrit");
 
+    // The following commands can be ran on a server in either Master or Slave
+    // mode. If a command should only be used on a server in one mode, but not
+    // both, it should be bound in both MasterCommandModule and
+    // SlaveCommandModule.
+
     command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
-    command(gerrit, "approve").to(ApproveCommand.class);
-    command(gerrit, "create-project").to(AdminCreateProject.class);
     command(gerrit, "flush-caches").to(AdminFlushCaches.class);
     command(gerrit, "ls-projects").to(ListProjects.class);
-    command(gerrit, "receive-pack").to(Receive.class);
-    command(gerrit, "replicate").to(AdminReplicate.class);
     command(gerrit, "show-caches").to(AdminShowCaches.class);
     command(gerrit, "show-connections").to(AdminShowConnections.class);
     command(gerrit, "show-queue").to(AdminShowQueue.class);
@@ -44,7 +45,7 @@
 
     command("scp").to(ScpCommand.class);
 
-    // Honor the legacy hypenated forms as aliases for the non-hypenated forms
+    // Honor the legacy hyphenated forms as aliases for the non-hyphenated forms
     //
     command("git-upload-pack").to(Commands.key(git, "upload-pack"));
     command("git-receive-pack").to(Commands.key(git, "receive-pack"));
diff --git a/src/main/java/com/google/gerrit/server/ssh/commands/ErrorSlaveMode.java b/src/main/java/com/google/gerrit/server/ssh/commands/ErrorSlaveMode.java
new file mode 100644
index 0000000..3226b86
--- /dev/null
+++ b/src/main/java/com/google/gerrit/server/ssh/commands/ErrorSlaveMode.java
@@ -0,0 +1,40 @@
+// 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.server.ssh.commands;
+
+import com.google.gerrit.server.ssh.BaseCommand;
+
+import java.io.IOException;
+
+/**
+ * A command which just throws an error because it shouldn't be ran on this
+ * server. This is used when a user tries to run a command on a server in Slave
+ * Mode, but the command only applies to the Master server.
+ */
+final class ErrorSlaveMode extends BaseCommand {
+  @Override
+  public void start() {
+    String msg =
+        "error: That command is disabled on this server.\n\n"
+            + "Please use the master server URL.\n";
+    try {
+      err.write(msg.getBytes(ENC));
+      err.flush();
+    } catch (IOException e) {
+      // Ignore errors writing to the client
+    }
+    onExit(1);
+  }
+}
diff --git a/src/main/java/com/google/gerrit/server/ssh/commands/MasterCommandModule.java b/src/main/java/com/google/gerrit/server/ssh/commands/MasterCommandModule.java
new file mode 100644
index 0000000..47e056f
--- /dev/null
+++ b/src/main/java/com/google/gerrit/server/ssh/commands/MasterCommandModule.java
@@ -0,0 +1,33 @@
+// 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.server.ssh.commands;
+
+import com.google.gerrit.server.ssh.CommandModule;
+import com.google.gerrit.server.ssh.CommandName;
+import com.google.gerrit.server.ssh.Commands;
+
+
+/** Register the commands a Gerrit server in master mode supports. */
+public class MasterCommandModule extends CommandModule {
+  @Override
+  protected void configure() {
+    final CommandName gerrit = Commands.named("gerrit");
+
+    command(gerrit, "approve").to(ApproveCommand.class);
+    command(gerrit, "create-project").to(AdminCreateProject.class);
+    command(gerrit, "receive-pack").to(Receive.class);
+    command(gerrit, "replicate").to(AdminReplicate.class);
+  }
+}
diff --git a/src/main/java/com/google/gerrit/server/ssh/commands/SlaveCommandModule.java b/src/main/java/com/google/gerrit/server/ssh/commands/SlaveCommandModule.java
new file mode 100644
index 0000000..449de81
--- /dev/null
+++ b/src/main/java/com/google/gerrit/server/ssh/commands/SlaveCommandModule.java
@@ -0,0 +1,33 @@
+// 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.server.ssh.commands;
+
+import com.google.gerrit.server.ssh.CommandModule;
+import com.google.gerrit.server.ssh.CommandName;
+import com.google.gerrit.server.ssh.Commands;
+
+
+/** Register the commands a Gerrit server in slave mode supports. */
+public class SlaveCommandModule extends CommandModule {
+  @Override
+  protected void configure() {
+    final CommandName gerrit = Commands.named("gerrit");
+
+    command(gerrit, "approve").to(ErrorSlaveMode.class);
+    command(gerrit, "create-project").to(ErrorSlaveMode.class);
+    command(gerrit, "receive-pack").to(ErrorSlaveMode.class);
+    command(gerrit, "replicate").to(ErrorSlaveMode.class);
+  }
+}