Package com.google.gerrit.sshd

Source Code of com.google.gerrit.sshd.CommandFactoryProvider

// 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.sshd;

import com.google.common.util.concurrent.Atomics;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.WorkQueue;
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.CommandFactory;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.apache.sshd.server.SessionAware;
import org.apache.sshd.server.session.ServerSession;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
* Creates a CommandFactory using commands registered by {@link CommandModule}.
*/
class CommandFactoryProvider implements Provider<CommandFactory> {
  private static final Logger logger = LoggerFactory
      .getLogger(CommandFactoryProvider.class);

  private final DispatchCommandProvider dispatcher;
  private final SshLog log;
  private final SshScope sshScope;
  private final ScheduledExecutorService startExecutor;
  private final Executor destroyExecutor;

  @Inject
  CommandFactoryProvider(
      @CommandName(Commands.ROOT) final DispatchCommandProvider d,
      @GerritServerConfig final Config cfg, final WorkQueue workQueue,
      final SshLog l, final SshScope s) {
    dispatcher = d;
    log = l;
    sshScope = s;

    int threads = cfg.getInt("sshd","commandStartThreads", 2);
    startExecutor = workQueue.createQueue(threads, "SshCommandStart");
    destroyExecutor = Executors.newSingleThreadExecutor(
        new ThreadFactoryBuilder()
          .setNameFormat("SshCommandDestroy-%s")
          .setDaemon(true)
          .build());
  }

  @Override
  public CommandFactory get() {
    return new CommandFactory() {
      public Command createCommand(final String requestCommand) {
        return new Trampoline(requestCommand);
      }
    };
  }

  private class Trampoline implements Command, SessionAware {
    private final String commandLine;
    private final String[] argv;
    private InputStream in;
    private OutputStream out;
    private OutputStream err;
    private ExitCallback exit;
    private Environment env;
    private Context ctx;
    private DispatchCommand cmd;
    private final AtomicBoolean logged;
    private final AtomicReference<Future<?>> task;

    Trampoline(final String cmdLine) {
      commandLine = cmdLine;
      argv = split(cmdLine);
      logged = new AtomicBoolean();
      task = Atomics.newReference();
    }

    public void setInputStream(final InputStream in) {
      this.in = in;
    }

    public void setOutputStream(final OutputStream out) {
      this.out = out;
    }

    public void setErrorStream(final OutputStream err) {
      this.err = err;
    }

    public void setExitCallback(final ExitCallback callback) {
      this.exit = callback;
    }

    public void setSession(final ServerSession session) {
      final SshSession s = session.getAttribute(SshSession.KEY);
      this.ctx = sshScope.newContext(s, commandLine);
    }

    public void start(final Environment env) throws IOException {
      this.env = env;
      final Context ctx = this.ctx;
      task.set(startExecutor.submit(new Runnable() {
        public void run() {
          try {
            onStart();
          } catch (Exception e) {
            logger.warn("Cannot start command \"" + ctx.getCommandLine()
                + "\" for user " + ctx.getSession().getUsername(), e);
          }
        }

        @Override
        public String toString() {
          return "start (user " + ctx.getSession().getUsername() + ")";
        }
      }));
    }

    private void onStart() throws IOException {
      synchronized (this) {
        final Context old = sshScope.set(ctx);
        try {
          cmd = dispatcher.get();
          cmd.setArguments(argv);
          cmd.setInputStream(in);
          cmd.setOutputStream(out);
          cmd.setErrorStream(err);
          cmd.setExitCallback(new ExitCallback() {
            @Override
            public void onExit(int rc, String exitMessage) {
              exit.onExit(translateExit(rc), exitMessage);
              log(rc);
            }

            @Override
            public void onExit(int rc) {
              exit.onExit(translateExit(rc));
              log(rc);
            }
          });
          cmd.start(env);
        } finally {
          sshScope.set(old);
        }
      }
    }

    private int translateExit(final int rc) {
      switch (rc) {
        case BaseCommand.STATUS_NOT_ADMIN:
          return 1;

        case BaseCommand.STATUS_CANCEL:
          return 15 /* SIGKILL */;

        case BaseCommand.STATUS_NOT_FOUND:
          return 127 /* POSIX not found */;

        default:
          return rc;
      }
    }

    private void log(final int rc) {
      if (logged.compareAndSet(false, true)) {
        log.onExecute(rc);
      }
    }

    @Override
    public void destroy() {
      Future<?> future = task.getAndSet(null);
      if (future != null) {
        future.cancel(true);
        destroyExecutor.execute(new Runnable() {
          @Override
          public void run() {
            onDestroy();
          }
        });
      }
    }

    private void onDestroy() {
      synchronized (this) {
        if (cmd != null) {
          final Context old = sshScope.set(ctx);
          try {
            cmd.destroy();
            log(BaseCommand.STATUS_CANCEL);
          } finally {
            ctx = null;
            cmd = null;
            sshScope.set(old);
          }
        }
      }
    }
  }

  /** Split a command line into a string array. */
  static String[] split(String commandLine) {
    final List<String> list = new ArrayList<String>();
    boolean inquote = false;
    boolean inDblQuote = 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 || inDblQuote)
            r.append(b);
          else if (r.length() > 0) {
            list.add(r.toString());
            r = new StringBuilder();
          }
          continue;
        case '\"':
          if (inquote)
            r.append(b);
          else
            inDblQuote = !inDblQuote;
          continue;
        case '\'':
          if (inDblQuote)
            r.append(b);
          else
            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()]);
  }
}
TOP

Related Classes of com.google.gerrit.sshd.CommandFactoryProvider

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.