diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java index 563d9cdffe..8abbc33baf 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java @@ -45,8 +45,6 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.Future; public abstract class BaseCommand implements Command { @@ -61,6 +59,7 @@ public abstract class BaseCommand implements Command { @Option(name = "--help", usage = "display this help text", aliases = {"-h"}) private boolean help; + @SuppressWarnings("unused") @Option(name = "--", usage = "end of options", handler = EndOfOptionsHandler.class) private boolean endOfOptions; @@ -90,10 +89,10 @@ public abstract class BaseCommand implements Command { private Future task; /** Text of the command line which lead up to invoking this instance. */ - protected String commandPrefix = ""; + private String commandName = ""; - /** Unparsed rest of the command line. */ - protected String commandLine = ""; + /** Unparsed command line options. */ + private String[] argv; public void setInputStream(final InputStream in) { this.in = in; @@ -111,21 +110,12 @@ public abstract class BaseCommand implements Command { this.exit = callback; } - public void setCommandPrefix(final String prefix) { - this.commandPrefix = prefix; + void setName(final String prefix) { + this.commandName = prefix; } - /** - * Set the command line to be evaluated by this command. - *

- * If this command is being invoked from a higher level - * {@link DispatchCommand} then only the portion after the command name (that - * is, the arguments) is supplied. - * - * @param line the command line received from the client. - */ - public void setCommandLine(final String line) { - this.commandLine = line; + public void setArguments(final String[] argv) { + this.argv = argv; } @Override @@ -154,50 +144,16 @@ public abstract class BaseCommand implements Command { /** * Parses the command line argument, injecting parsed values into fields. *

- * This method must be explicitly invoked to cause a parse. When parsing, - * arguments are split out of and read from the {@link #commandLine} field. + * This method must be explicitly invoked to cause a parse. * - * @throws Failure if the command line arguments were invalid. + * @throws UnloggedFailure if the command line arguments were invalid. * @see Option * @see Argument */ - protected void parseCommandLine() throws Failure { - final List list = new ArrayList(); - boolean inquote = false; - StringBuilder r = new StringBuilder(); - for (int ip = 0; ip < commandLine.length();) { - final char b = commandLine.charAt(ip++); - switch (b) { - case '\t': - case ' ': - if (inquote) - r.append(b); - else if (r.length() > 0) { - list.add(r.toString()); - r = new StringBuilder(); - } - continue; - case '\'': - inquote = !inquote; - continue; - case '\\': - if (inquote || ip == commandLine.length()) - r.append(b); // literal within a quote - else - r.append(commandLine.charAt(ip++)); - continue; - default: - r.append(b); - continue; - } - } - if (r.length() > 0) { - list.add(r.toString()); - } - + protected void parseCommandLine() throws UnloggedFailure { final CmdLineParser clp = newCmdLineParser(); try { - clp.parseArgument(list.toArray(new String[list.size()])); + clp.parseArgument(argv); } catch (IllegalArgumentException err) { if (!help) { throw new UnloggedFailure(1, "fatal: " + err.getMessage()); @@ -210,17 +166,22 @@ public abstract class BaseCommand implements Command { if (help) { final StringWriter msg = new StringWriter(); - msg.write(commandPrefix); + msg.write(commandName); clp.printSingleLineUsage(msg, null); msg.write('\n'); msg.write('\n'); clp.printUsage(msg, null); msg.write('\n'); + msg.write(usage()); throw new UnloggedFailure(1, msg.toString()); } } + protected String usage() { + return ""; + } + /** Construct a new parser for this command's received command line. */ protected CmdLineParser newCmdLineParser() { return cmdLineParserFactory.create(this); @@ -347,7 +308,7 @@ public abstract class BaseCommand implements Command { m.append(")"); } m.append(" during "); - m.append(getFullCommandLine()); + m.append(contextProvider.get().getCommandLine()); log.error(m.toString(), e); } @@ -374,20 +335,6 @@ public abstract class BaseCommand implements Command { } } - @Override - public String toString() { - return getFullCommandLine(); - } - - private String getFullCommandLine() { - if (commandPrefix.isEmpty()) - return commandLine; - else if (commandLine.isEmpty()) - return commandPrefix; - else - return commandPrefix + " " + commandLine; - } - private final class TaskThunk implements CancelableRunnable { private final CommandRunnable thunk; private final Context context; @@ -398,7 +345,7 @@ public abstract class BaseCommand implements Command { this.context = contextProvider.get(); StringBuilder m = new StringBuilder(); - m.append(getFullCommandLine()); + m.append(context.getCommandLine()); if (userProvider.get() instanceof IdentifiedUser) { IdentifiedUser u = (IdentifiedUser) userProvider.get(); m.append(" (" + u.getAccount().getUserName() + ")"); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java index fbe2373d03..ade3aac563 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java @@ -28,6 +28,8 @@ import org.apache.sshd.server.session.ServerSession; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; /** * Creates a CommandFactory using commands registered by {@link CommandModule}. @@ -55,6 +57,7 @@ class CommandFactoryProvider implements Provider { private class Trampoline implements Command, SessionAware { private final String commandLine; + private final String[] argv; private InputStream in; private OutputStream out; private OutputStream err; @@ -65,6 +68,7 @@ class CommandFactoryProvider implements Provider { Trampoline(final String cmdLine) { commandLine = cmdLine; + argv = split(cmdLine); } public void setInputStream(final InputStream in) { @@ -84,7 +88,8 @@ class CommandFactoryProvider implements Provider { } public void setSession(final ServerSession session) { - this.ctx = new Context(session.getAttribute(SshSession.KEY)); + final SshSession s = session.getAttribute(SshSession.KEY); + this.ctx = new Context(s, commandLine); } public void start(final Environment env) throws IOException { @@ -92,7 +97,7 @@ class CommandFactoryProvider implements Provider { final Context old = SshScope.set(ctx); try { cmd = dispatcher.get(); - cmd.setCommandLine(commandLine); + cmd.setArguments(argv); cmd.setInputStream(in); cmd.setOutputStream(out); cmd.setErrorStream(err); @@ -135,8 +140,7 @@ class CommandFactoryProvider implements Provider { private void log(final int rc) { synchronized (this) { if (!logged) { - ctx.finished = System.currentTimeMillis(); - log.onExecute(ctx, commandLine, rc); + log.onExecute(rc); logged = true; } } @@ -159,4 +163,41 @@ class CommandFactoryProvider implements Provider { } } } + + /** Split a command line into a string array. */ + static String[] split(String commandLine) { + final List list = new ArrayList(); + boolean inquote = false; + StringBuilder r = new StringBuilder(); + for (int ip = 0; ip < commandLine.length();) { + final char b = commandLine.charAt(ip++); + switch (b) { + case '\t': + case ' ': + if (inquote) + r.append(b); + else if (r.length() > 0) { + list.add(r.toString()); + r = new StringBuilder(); + } + continue; + case '\'': + inquote = !inquote; + continue; + case '\\': + if (inquote || ip == commandLine.length()) + r.append(b); // literal within a quote + else + r.append(commandLine.charAt(ip++)); + continue; + default: + r.append(b); + continue; + } + } + if (r.length() > 0) { + list.add(r.toString()); + } + return list.toArray(new String[list.size()]); + } } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.java index 5d5572a724..aff64216a9 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePasswordAuth.java @@ -77,7 +77,7 @@ class DatabasePasswordAuth implements PasswordAuthenticator { // session, record a login event in the log and add // a close listener to record a logout event. // - Context ctx = new Context(sd); + Context ctx = new Context(sd, null); Context old = SshScope.set(ctx); try { log.onLogin(); @@ -89,7 +89,7 @@ class DatabasePasswordAuth implements PasswordAuthenticator { new IoFutureListener() { @Override public void operationComplete(IoFuture future) { - final Context ctx = new Context(sd); + final Context ctx = new Context(sd, null); final Context old = SshScope.set(ctx); try { log.onLogout(); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java index 8fd9d72fbc..fa320b74b9 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java @@ -156,7 +156,7 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator { // session, record a login event in the log and add // a close listener to record a logout event. // - Context ctx = new Context(sd); + Context ctx = new Context(sd, null); Context old = SshScope.set(ctx); try { sshLog.onLogin(); @@ -168,7 +168,7 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator { new IoFutureListener() { @Override public void operationComplete(IoFuture future) { - final Context ctx = new Context(sd); + final Context ctx = new Context(sd, null); final Context old = SshScope.set(ctx); try { sshLog.onLogout(); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java index a5e0b776ac..a421f701d0 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java @@ -15,15 +15,18 @@ package com.google.gerrit.sshd; import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.sshd.args4j.SubcommandHandler; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.assistedinject.Assisted; import org.apache.sshd.server.Command; import org.apache.sshd.server.Environment; +import org.kohsuke.args4j.Argument; import java.io.IOException; -import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -39,6 +42,12 @@ final class DispatchCommand extends BaseCommand { private final Map> commands; private Command cmd; + @Argument(index = 0, required = true, metaVar = "COMMAND", handler = SubcommandHandler.class) + private String commandName; + + @Argument(index = 1, multiValued = true, metaVar = "ARG") + private List args = new ArrayList(); + @Inject DispatchCommand(final Provider cu, @Assisted final String pfx, @Assisted final Map> all) { @@ -49,65 +58,58 @@ final class DispatchCommand extends BaseCommand { @Override public void start(final Environment env) throws IOException { - if (commandLine.isEmpty()) { - usage(); - return; - } + try { + parseCommandLine(); - final String name, args; - int sp = commandLine.indexOf(' '); - if (0 < sp) { - name = commandLine.substring(0, sp); - while (Character.isWhitespace(commandLine.charAt(sp))) { - sp++; + final Provider p = commands.get(commandName); + if (p == null) { + String msg = + (prefix.isEmpty() ? "Gerrit Code Review" : prefix) + ": " + + commandName + ": not found"; + throw new UnloggedFailure(1, msg); } - args = commandLine.substring(sp); - } else { - name = commandLine; - args = ""; - } - if (name.equals("help") || name.equals("--help") || name.equals("-h")) { - usage(); - return; - } - - final Provider p = commands.get(name); - if (p != null) { final Command cmd = p.get(); + if (isAdminCommand(cmd) && !currentUser.get().isAdministrator()) { + final String msg = "fatal: Not a Gerrit administrator"; + throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg); + } + + if (cmd instanceof BaseCommand) { + final BaseCommand bc = (BaseCommand) cmd; + if (prefix.isEmpty()) + bc.setName(commandName); + else + bc.setName(prefix + " " + commandName); + bc.setArguments(args.toArray(new String[args.size()])); + + } else if (!args.isEmpty()) { + throw new UnloggedFailure(1, commandName + " does not take arguments"); + } + + provideStateTo(cmd); + synchronized (this) { this.cmd = cmd; } - - if (cmd.getClass().getAnnotation(AdminCommand.class) != null) { - final CurrentUser u = currentUser.get(); - if (!u.isAdministrator()) { - err.write("fatal: Not a Gerrit administrator\n".getBytes(ENC)); - err.flush(); - onExit(BaseCommand.STATUS_NOT_ADMIN); - return; - } - } - - provideStateTo(cmd); - if (cmd instanceof BaseCommand) { - final BaseCommand bc = (BaseCommand) cmd; - if (commandPrefix.isEmpty()) - bc.setCommandPrefix(name); - else - bc.setCommandPrefix(commandPrefix + " " + name); - bc.setCommandLine(args); - } cmd.start(env); - } else { - final String msg = prefix + ": " + name + ": not found\n"; + + } catch (UnloggedFailure e) { + String msg = e.getMessage(); + if (!msg.endsWith("\n")) { + msg += "\n"; + } err.write(msg.getBytes(ENC)); err.flush(); - onExit(BaseCommand.STATUS_NOT_FOUND); + onExit(e.exitCode); } } + private boolean isAdminCommand(final Command cmd) { + return cmd.getClass().getAnnotation(AdminCommand.class) != null; + } + @Override public void destroy() { synchronized (this) { @@ -118,13 +120,15 @@ final class DispatchCommand extends BaseCommand { } } - private void usage() throws IOException, UnsupportedEncodingException { + @Override + protected String usage() { final StringBuilder usage = new StringBuilder(); - if (prefix.indexOf(' ') < 0) { - usage.append("usage: " + prefix + " COMMAND [ARGS]\n"); + usage.append("Available commands"); + if (!prefix.isEmpty()) { + usage.append(" of "); + usage.append(prefix); } - usage.append("\n"); - usage.append("Available commands of " + prefix + " are:\n"); + usage.append(" are:\n"); usage.append("\n"); for (Map.Entry> e : commands.entrySet()) { usage.append(" "); @@ -140,8 +144,6 @@ final class DispatchCommand extends BaseCommand { } usage.append("COMMAND --help' for more information.\n"); usage.append("\n"); - err.write(usage.toString().getBytes("UTF-8")); - err.flush(); - onExit(1); + return usage.toString(); } } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java index d21e6e9ed6..9dcfb44f21 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java @@ -108,7 +108,7 @@ import java.util.List; @Singleton public class SshDaemon extends SshServer implements SshInfo, LifecycleListener { private static final int IANA_SSH_PORT = 22; - private static final int DEFAULT_PORT = 29418; + public static final int DEFAULT_PORT = 29418; private static final Logger log = LoggerFactory.getLogger(SshDaemon.class); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java index d4d79708dd..32d5a076c6 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java @@ -54,11 +54,14 @@ class SshLog implements LifecycleListener { private static final String P_STATUS = "status"; private final Provider session; + private final Provider context; private final AsyncAppender async; @Inject - SshLog(final Provider session, final SitePaths site) { + SshLog(final Provider session, final Provider context, + final SitePaths site) { this.session = session; + this.context = context; final DailyRollingFileAppender dst = new DailyRollingFileAppender(); dst.setName(LOG_NAME); @@ -118,7 +121,11 @@ class SshLog implements LifecycleListener { async.append(event); } - void onExecute(final Context ctx, final String commandLine, int exitValue) { + void onExecute(int exitValue) { + final Context ctx = context.get(); + ctx.finished = System.currentTimeMillis(); + + final String commandLine = ctx.getCommandLine(); String cmd = QuotedString.BOURNE.quote(commandLine); if (cmd == commandLine) { cmd = "'" + commandLine + "'"; diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java index f85a117a16..8e19ad968b 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java @@ -33,6 +33,7 @@ import com.google.gerrit.sshd.args4j.AccountGroupIdHandler; import com.google.gerrit.sshd.args4j.AccountIdHandler; import com.google.gerrit.sshd.args4j.PatchSetIdHandler; import com.google.gerrit.sshd.args4j.ProjectControlHandler; +import com.google.gerrit.sshd.args4j.SocketAddressHandler; import com.google.gerrit.sshd.commands.DefaultCommandModule; import com.google.gerrit.sshd.commands.QueryShell; import com.google.gerrit.util.cli.CmdLineParser; @@ -53,8 +54,6 @@ import java.net.SocketAddress; /** Configures standard dependencies for {@link SshDaemon}. */ public class SshModule extends FactoryModule { - private static final String NAME = "Gerrit Code Review"; - @Override protected void configure() { bindScope(RequestScoped.class, SshScope.REQUEST); @@ -70,7 +69,7 @@ public class SshModule extends FactoryModule { factory(PeerDaemonUser.Factory.class); bind(DispatchCommandProvider.class).annotatedWith(Commands.CMD_ROOT) - .toInstance(new DispatchCommandProvider(NAME, Commands.CMD_ROOT)); + .toInstance(new DispatchCommandProvider("", Commands.CMD_ROOT)); bind(CommandFactoryProvider.class); bind(CommandFactory.class).toProvider(CommandFactoryProvider.class); @@ -115,6 +114,7 @@ public class SshModule extends FactoryModule { registerOptionHandler(AccountGroup.Id.class, AccountGroupIdHandler.class); registerOptionHandler(PatchSet.Id.class, PatchSetIdHandler.class); registerOptionHandler(ProjectControl.class, ProjectControlHandler.class); + registerOptionHandler(SocketAddress.class, SocketAddressHandler.class); } private void registerOptionHandler(Class type, diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java index b980c18dbb..247948cd40 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java @@ -26,17 +26,26 @@ import java.util.Map; class SshScope { static class Context { private final SshSession session; + private final String commandLine; private final Map, Object> map; final long created; volatile long started; volatile long finished; - Context(final SshSession s) { + Context(final SshSession s, final String c) { session = s; + commandLine = c; map = new HashMap, Object>(); - created = System.currentTimeMillis(); - started = created; + + final long now = System.currentTimeMillis(); + created = now; + started = now; + finished = now; + } + + String getCommandLine() { + return commandLine; } synchronized T get(Key key, Provider creator) { diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshSession.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshSession.java index b79db521aa..3c4d4f8f19 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshSession.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshSession.java @@ -42,6 +42,17 @@ public class SshSession { this.remoteAsString = format(remoteAddress); } + SshSession(SshSession parent, SocketAddress peer, CurrentUser user) { + this.sessionId = parent.sessionId; + this.remoteAddress = peer; + if (parent.remoteAddress == peer) { + this.remoteAsString = parent.remoteAsString; + } else { + this.remoteAsString = format(peer) + "/" + parent.remoteAsString; + } + this.identity = user; + } + /** Unique session number, assigned during connect. */ public int getSessionId() { return sessionId; diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java new file mode 100644 index 0000000000..6300a7e2aa --- /dev/null +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java @@ -0,0 +1,145 @@ +// Copyright (C) 2010 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.sshd; + +import com.google.gerrit.reviewdb.Account; +import com.google.gerrit.server.AccessPath; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.PeerDaemonUser; +import com.google.gerrit.sshd.SshScope.Context; +import com.google.inject.Inject; +import com.google.inject.Provider; + +import org.apache.sshd.server.Command; +import org.apache.sshd.server.Environment; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.List; + +/** + * Executes any other command as a different user identity. + *

+ * The calling user must be authenticated as a {@link PeerDaemonUser}, which + * usually requires public key authentication using this daemon's private host + * key, or a key on this daemon's peer host key ring. + */ +public final class SuExec extends BaseCommand { + private final DispatchCommandProvider dispatcher; + + private Provider caller; + private Provider session; + private IdentifiedUser.GenericFactory userFactory; + + @Option(name = "--as", required = true) + private Account.Id accountId; + + @Option(name = "--from") + private SocketAddress peerAddress; + + @Argument(index = 0, multiValued = true, metaVar = "COMMAND") + private List args = new ArrayList(); + + private Command cmd; + + @Inject + SuExec(@CommandName(Commands.ROOT) final DispatchCommandProvider dispatcher, + final Provider caller, final Provider session, + final IdentifiedUser.GenericFactory userFactory) { + this.dispatcher = dispatcher; + this.caller = caller; + this.session = session; + this.userFactory = userFactory; + } + + @Override + public void start(Environment env) throws IOException { + try { + if (caller.get() instanceof PeerDaemonUser) { + final PeerDaemonUser peer = (PeerDaemonUser) caller.get(); + + parseCommandLine(); + + final Context ctx = new Context(newSession(), join(args)); + final Context old = SshScope.set(ctx); + try { + final BaseCommand cmd = dispatcher.get(); + cmd.setArguments(args.toArray(new String[args.size()])); + provideStateTo(cmd); + + synchronized (this) { + this.cmd = cmd; + } + cmd.start(env); + } finally { + SshScope.set(old); + } + + } else { + throw new UnloggedFailure(1, "fatal: Not a peer daemon"); + } + } catch (UnloggedFailure e) { + String msg = e.getMessage(); + if (!msg.endsWith("\n")) { + msg += "\n"; + } + err.write(msg.getBytes("UTF-8")); + err.flush(); + onExit(1); + } + } + + private SshSession newSession() { + final SocketAddress peer; + if (peerAddress == null) { + peer = session.get().getRemoteAddress(); + } else { + peer = peerAddress; + } + + return new SshSession(session.get(), peer, userFactory.create( + AccessPath.SSH, new Provider() { + @Override + public SocketAddress get() { + return peer; + } + }, accountId)); + } + + private static String join(List args) { + StringBuilder r = new StringBuilder(); + for (String a : args) { + if (r.length() > 0) { + r.append(" "); + } + r.append(a); + } + return r.toString(); + } + + @Override + public void destroy() { + synchronized (this) { + if (cmd != null) { + cmd.destroy(); + cmd = null; + } + } + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SocketAddressHandler.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SocketAddressHandler.java new file mode 100644 index 0000000000..75defd43a4 --- /dev/null +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SocketAddressHandler.java @@ -0,0 +1,54 @@ +// Copyright (C) 2010 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.sshd.args4j; + +import com.google.gerrit.server.util.SocketUtil; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; + +import java.net.SocketAddress; + +public class SocketAddressHandler extends OptionHandler { + @SuppressWarnings("unchecked") + @Inject + public SocketAddressHandler(@Assisted final CmdLineParser parser, + @Assisted final OptionDef option, @Assisted final Setter setter) { + super(parser, option, setter); + } + + @Override + public final int parseArguments(final Parameters params) + throws CmdLineException { + final String token = params.getParameter(0); + try { + setter.addValue(SocketUtil.parse(token, 0)); + } catch (IllegalArgumentException e) { + throw new CmdLineException(owner, e.getMessage()); + } + return 1; + } + + @Override + public final String getDefaultMetaVariable() { + return "HOST:PORT"; + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SubcommandHandler.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SubcommandHandler.java new file mode 100644 index 0000000000..4eedca0bcd --- /dev/null +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/SubcommandHandler.java @@ -0,0 +1,47 @@ +// Copyright (C) 2010 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.sshd.args4j; + +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; + +public class SubcommandHandler extends OptionHandler { + @SuppressWarnings("unchecked") + @Inject + public SubcommandHandler(@Assisted final CmdLineParser parser, + @Assisted final OptionDef option, @Assisted final Setter setter) { + super(parser, option, setter); + } + + @Override + public final int parseArguments(final Parameters params) + throws CmdLineException { + setter.addValue(params.getParameter(0)); + owner.stopOptionParsing(); + return 1; + } + + @Override + public final String getDefaultMetaVariable() { + return "COMMAND"; + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java index d9ebf57899..19e15fe605 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java @@ -18,6 +18,7 @@ import com.google.gerrit.sshd.CommandModule; import com.google.gerrit.sshd.CommandName; import com.google.gerrit.sshd.Commands; import com.google.gerrit.sshd.DispatchCommandProvider; +import com.google.gerrit.sshd.SuExec; /** Register the basic commands any Gerrit server should support. */ @@ -52,5 +53,7 @@ public class DefaultCommandModule extends CommandModule { command("git-upload-pack").to(Commands.key(git, "upload-pack")); command("git-receive-pack").to(Commands.key(git, "receive-pack")); command("gerrit-receive-pack").to(Commands.key(git, "receive-pack")); + + command("suexec").to(SuExec.class); } } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java index 46802f5b90..9bdba7005d 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ScpCommand.java @@ -56,10 +56,7 @@ final class ScpCommand extends BaseCommand { private IOException error; @Override - public void setCommandLine(final String line) { - super.setCommandLine(line); - - final String[] args = line.split(" "); + public void setArguments(final String[] args) { root = ""; for (int i = 0; i < args.length; i++) { if (args[i].charAt(0) == '-') {