From af816d3fdd18d6d7d1b2c854b70eb30be789d466 Mon Sep 17 00:00:00 2001
From: David Ostrovsky <david@ostrovsky.org>
Date: Thu, 10 Apr 2014 18:58:07 -0400
Subject: [PATCH] Convert SshDaemon to unix format

---
 src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java |  255 +++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 237 insertions(+), 18 deletions(-)

diff --git a/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java b/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java
index c0b4930..85c503d 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshCommandFactory.java
@@ -16,11 +16,23 @@
 package com.gitblit.transport.ssh;
 
 import java.io.IOException;
-import java.util.Scanner;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.inject.Inject;
 
 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.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.PacketLineOut;
@@ -31,8 +43,13 @@
 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
 import org.eclipse.jgit.transport.resolver.UploadPackFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.gitblit.git.RepositoryResolver;
+import com.gitblit.transport.ssh.commands.DispatchCommand;
+import com.gitblit.utils.WorkQueue;
+import com.google.common.util.concurrent.Atomics;
 
 /**
  *
@@ -40,30 +57,232 @@
  *
  */
 public class SshCommandFactory implements CommandFactory {
-	public SshCommandFactory(RepositoryResolver<SshDaemonClient> repositoryResolver, UploadPackFactory<SshDaemonClient> uploadPackFactory, ReceivePackFactory<SshDaemonClient> receivePackFactory) {
+  private static final Logger logger = LoggerFactory
+      .getLogger(SshCommandFactory.class);
+  private RepositoryResolver<SshSession> repositoryResolver;
+
+  private UploadPackFactory<SshSession> uploadPackFactory;
+
+  private ReceivePackFactory<SshSession> receivePackFactory;
+  private final ScheduledExecutorService startExecutor;
+
+  private CommandDispatcher dispatcher;
+
+    @Inject
+	public SshCommandFactory(RepositoryResolver<SshSession> repositoryResolver,
+	    UploadPackFactory<SshSession> uploadPackFactory,
+	    ReceivePackFactory<SshSession> receivePackFactory,
+	    WorkQueue workQueue,
+	    CommandDispatcher d) {
 		this.repositoryResolver = repositoryResolver;
 		this.uploadPackFactory = uploadPackFactory;
 		this.receivePackFactory = receivePackFactory;
+		this.dispatcher = d;
+		int threads = 2;//cfg.getInt("sshd","commandStartThreads", 2);
+	    startExecutor = workQueue.createQueue(threads, "SshCommandStart");
 	}
-
-	private RepositoryResolver<SshDaemonClient> repositoryResolver;
-
-	private UploadPackFactory<SshDaemonClient> uploadPackFactory;
-
-	private ReceivePackFactory<SshDaemonClient> receivePackFactory;
 
 	@Override
 	public Command createCommand(final String commandLine) {
-		Scanner commandScanner = new Scanner(commandLine);
-		final String command = commandScanner.next();
-		final String argument = commandScanner.nextLine();
-
+	  return new Trampoline(commandLine);
+        /*
 		if ("git-upload-pack".equals(command))
 			return new UploadPackCommand(argument);
 		if ("git-receive-pack".equals(command))
 			return new ReceivePackCommand(argument);
 		return new NonCommand();
+		*/
 	}
+
+	  private class Trampoline implements Command, SessionAware {
+	    private final String[] argv;
+	    private InputStream in;
+	    private OutputStream out;
+	    private OutputStream err;
+	    private ExitCallback exit;
+	    private Environment env;
+	    private DispatchCommand cmd;
+	    private final AtomicBoolean logged;
+	    private final AtomicReference<Future<?>> task;
+
+	    Trampoline(final String cmdLine) {
+	      argv = split(cmdLine);
+	      logged = new AtomicBoolean();
+	      task = Atomics.newReference();
+	    }
+
+	    @Override
+	    public void setSession(ServerSession session) {
+	    // TODO Auto-generated method stub
+	    }
+
+	    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 start(final Environment env) throws IOException {
+	      this.env = env;
+	      task.set(startExecutor.submit(new Runnable() {
+	        public void run() {
+	          try {
+	            onStart();
+	          } catch (Exception e) {
+	            logger.warn("Cannot start command ", e);
+	          }
+	        }
+
+	        @Override
+	        public String toString() {
+	          //return "start (user " + ctx.getSession().getUsername() + ")";
+	          return "start (user TODO)";
+	        }
+	      }));
+	    }
+
+	    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) {
+	      return 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(cmd, rc);
+	        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) {
+	          //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 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()]);
+	  }
 
 	public abstract class RepositoryCommand extends AbstractSshCommand {
 		protected final String repositoryName;
@@ -76,7 +295,7 @@
 		public void start(Environment env) throws IOException {
 			Repository db = null;
 			try {
-				SshDaemonClient client = session.getAttribute(SshDaemonClient.ATTR_KEY);
+				SshSession client = session.getAttribute(SshSession.KEY);
 				db = selectRepository(client, repositoryName);
 				if (db == null) return;
 				run(client, db);
@@ -92,7 +311,7 @@
 			}
 		}
 
-		protected Repository selectRepository(SshDaemonClient client, String name) throws IOException {
+		protected Repository selectRepository(SshSession client, String name) throws IOException {
 			try {
 				return openRepository(client, name);
 			} catch (ServiceMayNotContinueException e) {
@@ -104,7 +323,7 @@
 			}
 		}
 
-		protected Repository openRepository(SshDaemonClient client, String name)
+		protected Repository openRepository(SshSession client, String name)
 				throws ServiceMayNotContinueException {
 			// Assume any attempt to use \ was by a Windows client
 			// and correct to the more typical / used in Git URIs.
@@ -129,7 +348,7 @@
 			}
 		}
 
-		protected abstract void run(SshDaemonClient client, Repository db)
+		protected abstract void run(SshSession client, Repository db)
 			throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException;
 	}
 
@@ -137,7 +356,7 @@
 		public UploadPackCommand(String repositoryName) { super(repositoryName); }
 
 		@Override
-		protected void run(SshDaemonClient client, Repository db)
+		protected void run(SshSession client, Repository db)
 				throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException {
 			UploadPack up = uploadPackFactory.create(client, db);
 			up.upload(in, out, null);
@@ -148,7 +367,7 @@
 		public ReceivePackCommand(String repositoryName) { super(repositoryName); }
 
 		@Override
-		protected void run(SshDaemonClient client, Repository db)
+		protected void run(SshSession client, Repository db)
 				throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException {
 			ReceivePack rp = receivePackFactory.create(client, db);
 			rp.receive(in, out, null);

--
Gitblit v1.9.1