123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- /*
- * Copyright (C) 2009 The Android Open Source Project
- * Copyright 2014 gitblit.com.
- *
- * 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.gitblit.transport.ssh.commands;
-
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.concurrent.ExecutorService;
- 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;
-
- 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.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- import com.gitblit.Keys;
- import com.gitblit.manager.IGitblit;
- import com.gitblit.transport.ssh.SshDaemonClient;
- import com.gitblit.utils.WorkQueue;
- import com.google.common.util.concurrent.Atomics;
- import com.google.common.util.concurrent.ThreadFactoryBuilder;
-
- public class SshCommandFactory implements CommandFactory {
- private static final Logger logger = LoggerFactory.getLogger(SshCommandFactory.class);
-
- private final WorkQueue workQueue;
- private final IGitblit gitblit;
- private final ScheduledExecutorService startExecutor;
- private final ExecutorService destroyExecutor;
-
- public SshCommandFactory(IGitblit gitblit, WorkQueue workQueue) {
- this.gitblit = gitblit;
- this.workQueue = workQueue;
-
- int threads = gitblit.getSettings().getInteger(Keys.git.sshCommandStartThreads, 2);
- startExecutor = workQueue.createQueue(threads, "SshCommandStart");
- destroyExecutor = Executors.newSingleThreadExecutor(
- new ThreadFactoryBuilder()
- .setNameFormat("SshCommandDestroy-%s")
- .setDaemon(true)
- .build());
- }
-
- public void stop() {
- destroyExecutor.shutdownNow();
- }
-
- public RootDispatcher createRootDispatcher(SshDaemonClient client, String commandLine) {
- return new RootDispatcher(gitblit, client, commandLine, workQueue);
- }
-
- @Override
- public Command createCommand(final String commandLine) {
- return new Trampoline(commandLine);
- }
-
- private class Trampoline implements Command, SessionAware {
- private final String[] argv;
- private ServerSession session;
- private InputStream in;
- private OutputStream out;
- private OutputStream err;
- private ExitCallback exit;
- private Environment env;
- private String cmdLine;
- private DispatchCommand cmd;
- private final AtomicBoolean logged;
- private final AtomicReference<Future<?>> task;
-
- Trampoline(String line) {
- if (line.startsWith("git-")) {
- line = "git " + line;
- }
- cmdLine = line;
- argv = split(line);
- logged = new AtomicBoolean();
- task = Atomics.newReference();
- }
-
- @Override
- public void setSession(ServerSession session) {
- this.session = session;
- }
-
- @Override
- public void setInputStream(final InputStream in) {
- this.in = in;
- }
-
- @Override
- public void setOutputStream(final OutputStream out) {
- this.out = out;
- }
-
- @Override
- public void setErrorStream(final OutputStream err) {
- this.err = err;
- }
-
- @Override
- public void setExitCallback(final ExitCallback callback) {
- this.exit = callback;
- }
-
- @Override
- public void start(final Environment env) throws IOException {
- this.env = env;
- task.set(startExecutor.submit(new Runnable() {
- @Override
- public void run() {
- try {
- onStart();
- } catch (Exception e) {
- logger.warn("Cannot start command ", e);
- }
- }
-
- @Override
- public String toString() {
- return "start (user " + session.getUsername() + ")";
- }
- }));
- }
-
- private void onStart() throws IOException {
- synchronized (this) {
- SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY);
- try {
- cmd = createRootDispatcher(client, cmdLine);
- 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 {
- client = null;
- }
- }
- }
-
- 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)) {
- logger.info("onExecute: {} exits with: {}", cmd.getClass().getSimpleName(), 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) {
- try {
- cmd.destroy();
- } finally {
- cmd = null;
- }
- }
- }
- }
- }
-
- /** Split a command line into a string array. */
- static public 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()]);
- }
- }
|