From 31f477050f223b975bce4862a6aa415ee3045857 Mon Sep 17 00:00:00 2001 From: James Moger Date: Sat, 8 Mar 2014 19:10:20 -0500 Subject: Move Git daemon into different package --- src/main/java/com/gitblit/git/GitDaemon.java | 333 -------------------- src/main/java/com/gitblit/git/GitDaemonClient.java | 131 -------- .../java/com/gitblit/git/GitDaemonService.java | 166 ---------- .../com/gitblit/git/GitblitReceivePackFactory.java | 1 + .../com/gitblit/git/GitblitUploadPackFactory.java | 4 +- .../java/com/gitblit/git/RepositoryResolver.java | 1 + .../java/com/gitblit/manager/ServicesManager.java | 2 +- .../java/com/gitblit/transport/git/GitDaemon.java | 336 +++++++++++++++++++++ .../com/gitblit/transport/git/GitDaemonClient.java | 131 ++++++++ .../gitblit/transport/git/GitDaemonService.java | 166 ++++++++++ .../java/com/gitblit/transport/ssh/SshDaemon.java | 2 +- 11 files changed, 640 insertions(+), 633 deletions(-) delete mode 100644 src/main/java/com/gitblit/git/GitDaemon.java delete mode 100644 src/main/java/com/gitblit/git/GitDaemonClient.java delete mode 100644 src/main/java/com/gitblit/git/GitDaemonService.java create mode 100644 src/main/java/com/gitblit/transport/git/GitDaemon.java create mode 100644 src/main/java/com/gitblit/transport/git/GitDaemonClient.java create mode 100644 src/main/java/com/gitblit/transport/git/GitDaemonService.java diff --git a/src/main/java/com/gitblit/git/GitDaemon.java b/src/main/java/com/gitblit/git/GitDaemon.java deleted file mode 100644 index d026b5ed..00000000 --- a/src/main/java/com/gitblit/git/GitDaemon.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (C) 2013 gitblit.com - * Copyright (C) 2008-2009, Google Inc. - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.gitblit.git; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InterruptedIOException; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketAddress; -import java.text.MessageFormat; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.eclipse.jgit.errors.RepositoryNotFoundException; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.transport.ReceivePack; -import org.eclipse.jgit.transport.ServiceMayNotContinueException; -import org.eclipse.jgit.transport.UploadPack; -import org.eclipse.jgit.transport.resolver.ReceivePackFactory; -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.IStoredSettings; -import com.gitblit.Keys; -import com.gitblit.manager.IGitblit; -import com.gitblit.utils.StringUtils; - -/** - * Gitblit's Git Daemon ignores any and all per-repository daemon settings and - * integrates into Gitblit's security model. - * - * @author James Moger - * - */ -public class GitDaemon { - - private final Logger logger = LoggerFactory.getLogger(GitDaemon.class); - - /** 9418: IANA assigned port number for Git. */ - public static final int DEFAULT_PORT = 9418; - - private static final int BACKLOG = 5; - - private InetSocketAddress myAddress; - - private final GitDaemonService[] services; - - private final ThreadGroup processors; - - private AtomicBoolean run; - - private ServerSocket acceptSocket; - - private Thread acceptThread; - - private int timeout; - - private RepositoryResolver repositoryResolver; - - private UploadPackFactory uploadPackFactory; - - private ReceivePackFactory receivePackFactory; - - public GitDaemon(IGitblit gitblit) { - - IStoredSettings settings = gitblit.getSettings(); - int port = settings.getInteger(Keys.git.daemonPort, 0); - String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost"); - - if (StringUtils.isEmpty(bindInterface)) { - myAddress = new InetSocketAddress(port); - } else { - myAddress = new InetSocketAddress(bindInterface, port); - } - - repositoryResolver = new RepositoryResolver(gitblit); - uploadPackFactory = new GitblitUploadPackFactory(gitblit); - receivePackFactory = new GitblitReceivePackFactory(gitblit); - - run = new AtomicBoolean(false); - processors = new ThreadGroup("Git-Daemon"); - services = new GitDaemonService[] { new GitDaemonService("upload-pack", "uploadpack") { - { - setEnabled(true); - setOverridable(false); - } - - @Override - protected void execute(final GitDaemonClient dc, final Repository db) - throws IOException, ServiceNotEnabledException, - ServiceNotAuthorizedException { - UploadPack up = uploadPackFactory.create(dc, db); - InputStream in = dc.getInputStream(); - OutputStream out = dc.getOutputStream(); - up.upload(in, out, null); - } - }, new GitDaemonService("receive-pack", "receivepack") { - { - setEnabled(true); - setOverridable(false); - } - - @Override - protected void execute(final GitDaemonClient dc, final Repository db) - throws IOException, ServiceNotEnabledException, - ServiceNotAuthorizedException { - ReceivePack rp = receivePackFactory.create(dc, db); - InputStream in = dc.getInputStream(); - OutputStream out = dc.getOutputStream(); - rp.receive(in, out, null); - } - } }; - } - - public int getPort() { - return myAddress.getPort(); - } - - public String formatUrl(String servername, String repository) { - if (getPort() == 9418) { - // standard port - return MessageFormat.format("git://{0}/{1}", servername, repository); - } else { - // non-standard port - return MessageFormat.format("git://{0}:{1,number,0}/{2}", servername, getPort(), repository); - } - } - - /** @return timeout (in seconds) before aborting an IO operation. */ - public int getTimeout() { - return timeout; - } - - /** - * Set the timeout before willing to abort an IO call. - * - * @param seconds - * number of seconds to wait (with no data transfer occurring) - * before aborting an IO read or write operation with the - * connected client. - */ - public void setTimeout(final int seconds) { - timeout = seconds; - } - - /** - * Start this daemon on a background thread. - * - * @throws IOException - * the server socket could not be opened. - * @throws IllegalStateException - * the daemon is already running. - */ - public synchronized void start() throws IOException { - if (acceptThread != null) - throw new IllegalStateException(JGitText.get().daemonAlreadyRunning); - - final ServerSocket listenSock = new ServerSocket(myAddress != null ? myAddress.getPort() - : 0, BACKLOG, myAddress != null ? myAddress.getAddress() : null); - myAddress = (InetSocketAddress) listenSock.getLocalSocketAddress(); - - run.set(true); - acceptSocket = listenSock; - acceptThread = new Thread(processors, "Git-Daemon-Accept") { - @Override - public void run() { - while (isRunning()) { - try { - startClient(listenSock.accept()); - } catch (InterruptedIOException e) { - // Test again to see if we should keep accepting. - } catch (IOException e) { - break; - } - } - - try { - listenSock.close(); - } catch (IOException err) { - // - } finally { - acceptSocket = null; - } - - } - }; - acceptThread.start(); - - logger.info(MessageFormat.format("Git Daemon is listening on {0}:{1,number,0}", myAddress.getAddress().getHostAddress(), myAddress.getPort())); - } - - /** @return true if this daemon is receiving connections. */ - public boolean isRunning() { - return run.get(); - } - - /** Stop this daemon. */ - public synchronized void stop() { - if (isRunning() && acceptThread != null) { - run.set(false); - logger.info("Git Daemon stopping..."); - try { - // close the accept socket - // this throws a SocketException in the accept thread - acceptSocket.close(); - } catch (IOException e1) { - } - try { - // join the accept thread - acceptThread.join(); - logger.info("Git Daemon stopped."); - } catch (InterruptedException e) { - logger.error("Accept thread join interrupted", e); - } finally { - acceptThread = null; - } - } - } - - private void startClient(final Socket s) { - final GitDaemonClient dc = new GitDaemonClient(this); - - final SocketAddress peer = s.getRemoteSocketAddress(); - if (peer instanceof InetSocketAddress) - dc.setRemoteAddress(((InetSocketAddress) peer).getAddress()); - - new Thread(processors, "Git-Daemon-Client " + peer.toString()) { - @Override - public void run() { - try { - dc.execute(s); - } catch (ServiceNotEnabledException e) { - // Ignored. Client cannot use this repository. - } catch (ServiceNotAuthorizedException e) { - // Ignored. Client cannot use this repository. - } catch (IOException e) { - // Ignore unexpected IO exceptions from clients - } finally { - try { - s.getInputStream().close(); - } catch (IOException e) { - // Ignore close exceptions - } - try { - s.getOutputStream().close(); - } catch (IOException e) { - // Ignore close exceptions - } - } - } - }.start(); - } - - synchronized GitDaemonService matchService(final String cmd) { - for (final GitDaemonService d : services) { - if (d.handles(cmd)) - return d; - } - return null; - } - - Repository openRepository(GitDaemonClient 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. - // - name = name.replace('\\', '/'); - - // git://thishost/path should always be name="/path" here - // - if (!name.startsWith("/")) //$NON-NLS-1$ - return null; - - try { - return repositoryResolver.open(client, name.substring(1)); - } catch (RepositoryNotFoundException e) { - // null signals it "wasn't found", which is all that is suitable - // for the remote client to know. - return null; - } catch (ServiceNotEnabledException e) { - // null signals it "wasn't found", which is all that is suitable - // for the remote client to know. - return null; - } - } -} diff --git a/src/main/java/com/gitblit/git/GitDaemonClient.java b/src/main/java/com/gitblit/git/GitDaemonClient.java deleted file mode 100644 index 8d8cac6d..00000000 --- a/src/main/java/com/gitblit/git/GitDaemonClient.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.gitblit.git; - -/* - * Copyright (C) 2008-2009, Google Inc. - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.Socket; - -import org.eclipse.jgit.transport.Daemon; -import org.eclipse.jgit.transport.PacketLineIn; -import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; -import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; -import org.eclipse.jgit.util.io.SafeBufferedOutputStream; - -/** Active network client of {@link Daemon}. */ -public class GitDaemonClient { - private final GitDaemon daemon; - - private InetAddress peer; - - private InputStream rawIn; - - private OutputStream rawOut; - - private String repositoryName; - - GitDaemonClient(final GitDaemon d) { - daemon = d; - } - - void setRemoteAddress(final InetAddress ia) { - peer = ia; - } - - /** @return the daemon which spawned this client. */ - public GitDaemon getDaemon() { - return daemon; - } - - /** @return Internet address of the remote client. */ - public InetAddress getRemoteAddress() { - return peer; - } - - /** @return input stream to read from the connected client. */ - public InputStream getInputStream() { - return rawIn; - } - - /** @return output stream to send data to the connected client. */ - public OutputStream getOutputStream() { - return rawOut; - } - - public void setRepositoryName(String repositoryName) { - this.repositoryName = repositoryName; - } - - /** @return the name of the requested repository. */ - public String getRepositoryName() { - return repositoryName; - } - - void execute(final Socket sock) throws IOException, - ServiceNotEnabledException, ServiceNotAuthorizedException { - rawIn = new BufferedInputStream(sock.getInputStream()); - rawOut = new SafeBufferedOutputStream(sock.getOutputStream()); - - if (0 < daemon.getTimeout()) - sock.setSoTimeout(daemon.getTimeout() * 1000); - String cmd = new PacketLineIn(rawIn).readStringRaw(); - final int nul = cmd.indexOf('\0'); - if (nul >= 0) { - // Newer clients hide a "host" header behind this byte. - // Currently we don't use it for anything, so we ignore - // this portion of the command. - // - cmd = cmd.substring(0, nul); - } - - final GitDaemonService srv = getDaemon().matchService(cmd); - if (srv == null) - return; - sock.setSoTimeout(0); - srv.execute(this, cmd); - } -} \ No newline at end of file diff --git a/src/main/java/com/gitblit/git/GitDaemonService.java b/src/main/java/com/gitblit/git/GitDaemonService.java deleted file mode 100644 index 8dee7d0b..00000000 --- a/src/main/java/com/gitblit/git/GitDaemonService.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.gitblit.git; - -/* - * Copyright (C) 2008-2009, Google Inc. - * Copyright (C) 2009, Robin Rosenberg - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import java.io.IOException; - -import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.Config.SectionParser; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.transport.Daemon; -import org.eclipse.jgit.transport.PacketLineOut; -import org.eclipse.jgit.transport.ServiceMayNotContinueException; -import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; -import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; - -/** A service exposed by {@link Daemon} over anonymous git://. */ -public abstract class GitDaemonService { - private final String command; - - private final SectionParser configKey; - - private boolean enabled; - - private boolean overridable; - - GitDaemonService(final String cmdName, final String cfgName) { - command = cmdName.startsWith("git-") ? cmdName : "git-" + cmdName; //$NON-NLS-1$ //$NON-NLS-2$ - configKey = new SectionParser() { - @Override - public ServiceConfig parse(final Config cfg) { - return new ServiceConfig(GitDaemonService.this, cfg, cfgName); - } - }; - overridable = true; - } - - private static class ServiceConfig { - final boolean enabled; - - ServiceConfig(final GitDaemonService service, final Config cfg, - final String name) { - enabled = cfg.getBoolean("daemon", name, service.isEnabled()); //$NON-NLS-1$ - } - } - - /** @return is this service enabled for invocation? */ - public boolean isEnabled() { - return enabled; - } - - /** - * @param on - * true to allow this service to be used; false to deny it. - */ - public void setEnabled(final boolean on) { - enabled = on; - } - - /** @return can this service be configured in the repository config file? */ - public boolean isOverridable() { - return overridable; - } - - /** - * @param on - * true to permit repositories to override this service's enabled - * state with the daemon.servicename config setting. - */ - public void setOverridable(final boolean on) { - overridable = on; - } - - /** @return name of the command requested by clients. */ - public String getCommandName() { - return command; - } - - /** - * Determine if this service can handle the requested command. - * - * @param commandLine - * input line from the client. - * @return true if this command can accept the given command line. - */ - public boolean handles(final String commandLine) { - return command.length() + 1 < commandLine.length() - && commandLine.charAt(command.length()) == ' ' - && commandLine.startsWith(command); - } - - void execute(final GitDaemonClient client, final String commandLine) - throws IOException, ServiceNotEnabledException, - ServiceNotAuthorizedException { - final String name = commandLine.substring(command.length() + 1); - Repository db; - try { - db = client.getDaemon().openRepository(client, name); - } catch (ServiceMayNotContinueException e) { - // An error when opening the repo means the client is expecting a ref - // advertisement, so use that style of error. - PacketLineOut pktOut = new PacketLineOut(client.getOutputStream()); - pktOut.writeString("ERR " + e.getMessage() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ - db = null; - } - if (db == null) - return; - try { - if (isEnabledFor(db)) - execute(client, db); - } finally { - db.close(); - } - } - - private boolean isEnabledFor(final Repository db) { - if (isOverridable()) - return db.getConfig().get(configKey).enabled; - return isEnabled(); - } - - abstract void execute(GitDaemonClient client, Repository db) - throws IOException, ServiceNotEnabledException, - ServiceNotAuthorizedException; -} diff --git a/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java b/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java index 9911258c..af5a8381 100644 --- a/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java +++ b/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java @@ -31,6 +31,7 @@ import com.gitblit.Keys; import com.gitblit.manager.IGitblit; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; +import com.gitblit.transport.git.GitDaemonClient; import com.gitblit.transport.ssh.SshSession; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.StringUtils; diff --git a/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java b/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java index d4e3ca15..39ad0724 100644 --- a/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java +++ b/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java @@ -25,6 +25,7 @@ import org.eclipse.jgit.transport.resolver.UploadPackFactory; import com.gitblit.manager.IAuthenticationManager; import com.gitblit.models.UserModel; +import com.gitblit.transport.git.GitDaemonClient; /** * The upload pack factory creates an upload pack which controls what refs are @@ -51,7 +52,8 @@ public class GitblitUploadPackFactory implements UploadPackFactory { if (req instanceof HttpServletRequest) { // http/https request may or may not be authenticated - user = authenticationManager.authenticate((HttpServletRequest) req); + HttpServletRequest client = (HttpServletRequest) req; + user = authenticationManager.authenticate(client); if (user == null) { user = UserModel.ANONYMOUS; } diff --git a/src/main/java/com/gitblit/git/RepositoryResolver.java b/src/main/java/com/gitblit/git/RepositoryResolver.java index c859f6f6..08048195 100644 --- a/src/main/java/com/gitblit/git/RepositoryResolver.java +++ b/src/main/java/com/gitblit/git/RepositoryResolver.java @@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory; import com.gitblit.manager.IGitblit; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; +import com.gitblit.transport.git.GitDaemonClient; import com.gitblit.transport.ssh.SshSession; /** diff --git a/src/main/java/com/gitblit/manager/ServicesManager.java b/src/main/java/com/gitblit/manager/ServicesManager.java index 11083be3..f75c6d14 100644 --- a/src/main/java/com/gitblit/manager/ServicesManager.java +++ b/src/main/java/com/gitblit/manager/ServicesManager.java @@ -37,11 +37,11 @@ import com.gitblit.Keys; import com.gitblit.fanout.FanoutNioService; import com.gitblit.fanout.FanoutService; import com.gitblit.fanout.FanoutSocketService; -import com.gitblit.git.GitDaemon; import com.gitblit.models.FederationModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; import com.gitblit.service.FederationPullService; +import com.gitblit.transport.git.GitDaemon; import com.gitblit.transport.ssh.SshDaemon; import com.gitblit.utils.IdGenerator; import com.gitblit.utils.StringUtils; diff --git a/src/main/java/com/gitblit/transport/git/GitDaemon.java b/src/main/java/com/gitblit/transport/git/GitDaemon.java new file mode 100644 index 00000000..6581ad87 --- /dev/null +++ b/src/main/java/com/gitblit/transport/git/GitDaemon.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2013 gitblit.com + * Copyright (C) 2008-2009, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.gitblit.transport.git; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.text.MessageFormat; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.ReceivePack; +import org.eclipse.jgit.transport.ServiceMayNotContinueException; +import org.eclipse.jgit.transport.UploadPack; +import org.eclipse.jgit.transport.resolver.ReceivePackFactory; +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.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.git.GitblitReceivePackFactory; +import com.gitblit.git.GitblitUploadPackFactory; +import com.gitblit.git.RepositoryResolver; +import com.gitblit.manager.IGitblit; +import com.gitblit.utils.StringUtils; + +/** + * Gitblit's Git Daemon ignores any and all per-repository daemon settings and + * integrates into Gitblit's security model. + * + * @author James Moger + * + */ +public class GitDaemon { + + private final Logger logger = LoggerFactory.getLogger(GitDaemon.class); + + /** 9418: IANA assigned port number for Git. */ + public static final int DEFAULT_PORT = 9418; + + private static final int BACKLOG = 5; + + private InetSocketAddress myAddress; + + private final GitDaemonService[] services; + + private final ThreadGroup processors; + + private AtomicBoolean run; + + private ServerSocket acceptSocket; + + private Thread acceptThread; + + private int timeout; + + private RepositoryResolver repositoryResolver; + + private UploadPackFactory uploadPackFactory; + + private ReceivePackFactory receivePackFactory; + + public GitDaemon(IGitblit gitblit) { + + IStoredSettings settings = gitblit.getSettings(); + int port = settings.getInteger(Keys.git.daemonPort, 0); + String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost"); + + if (StringUtils.isEmpty(bindInterface)) { + myAddress = new InetSocketAddress(port); + } else { + myAddress = new InetSocketAddress(bindInterface, port); + } + + repositoryResolver = new RepositoryResolver(gitblit); + uploadPackFactory = new GitblitUploadPackFactory(gitblit); + receivePackFactory = new GitblitReceivePackFactory(gitblit); + + run = new AtomicBoolean(false); + processors = new ThreadGroup("Git-Daemon"); + services = new GitDaemonService[] { new GitDaemonService("upload-pack", "uploadpack") { + { + setEnabled(true); + setOverridable(false); + } + + @Override + protected void execute(final GitDaemonClient dc, final Repository db) + throws IOException, ServiceNotEnabledException, + ServiceNotAuthorizedException { + UploadPack up = uploadPackFactory.create(dc, db); + InputStream in = dc.getInputStream(); + OutputStream out = dc.getOutputStream(); + up.upload(in, out, null); + } + }, new GitDaemonService("receive-pack", "receivepack") { + { + setEnabled(true); + setOverridable(false); + } + + @Override + protected void execute(final GitDaemonClient dc, final Repository db) + throws IOException, ServiceNotEnabledException, + ServiceNotAuthorizedException { + ReceivePack rp = receivePackFactory.create(dc, db); + InputStream in = dc.getInputStream(); + OutputStream out = dc.getOutputStream(); + rp.receive(in, out, null); + } + } }; + } + + public int getPort() { + return myAddress.getPort(); + } + + public String formatUrl(String servername, String repository) { + if (getPort() == 9418) { + // standard port + return MessageFormat.format("git://{0}/{1}", servername, repository); + } else { + // non-standard port + return MessageFormat.format("git://{0}:{1,number,0}/{2}", servername, getPort(), repository); + } + } + + /** @return timeout (in seconds) before aborting an IO operation. */ + public int getTimeout() { + return timeout; + } + + /** + * Set the timeout before willing to abort an IO call. + * + * @param seconds + * number of seconds to wait (with no data transfer occurring) + * before aborting an IO read or write operation with the + * connected client. + */ + public void setTimeout(final int seconds) { + timeout = seconds; + } + + /** + * Start this daemon on a background thread. + * + * @throws IOException + * the server socket could not be opened. + * @throws IllegalStateException + * the daemon is already running. + */ + public synchronized void start() throws IOException { + if (acceptThread != null) + throw new IllegalStateException(JGitText.get().daemonAlreadyRunning); + + final ServerSocket listenSock = new ServerSocket(myAddress != null ? myAddress.getPort() + : 0, BACKLOG, myAddress != null ? myAddress.getAddress() : null); + myAddress = (InetSocketAddress) listenSock.getLocalSocketAddress(); + + run.set(true); + acceptSocket = listenSock; + acceptThread = new Thread(processors, "Git-Daemon-Accept") { + @Override + public void run() { + while (isRunning()) { + try { + startClient(listenSock.accept()); + } catch (InterruptedIOException e) { + // Test again to see if we should keep accepting. + } catch (IOException e) { + break; + } + } + + try { + listenSock.close(); + } catch (IOException err) { + // + } finally { + acceptSocket = null; + } + + } + }; + acceptThread.start(); + + logger.info(MessageFormat.format("Git Daemon is listening on {0}:{1,number,0}", myAddress.getAddress().getHostAddress(), myAddress.getPort())); + } + + /** @return true if this daemon is receiving connections. */ + public boolean isRunning() { + return run.get(); + } + + /** Stop this daemon. */ + public synchronized void stop() { + if (isRunning() && acceptThread != null) { + run.set(false); + logger.info("Git Daemon stopping..."); + try { + // close the accept socket + // this throws a SocketException in the accept thread + acceptSocket.close(); + } catch (IOException e1) { + } + try { + // join the accept thread + acceptThread.join(); + logger.info("Git Daemon stopped."); + } catch (InterruptedException e) { + logger.error("Accept thread join interrupted", e); + } finally { + acceptThread = null; + } + } + } + + private void startClient(final Socket s) { + final GitDaemonClient dc = new GitDaemonClient(this); + + final SocketAddress peer = s.getRemoteSocketAddress(); + if (peer instanceof InetSocketAddress) + dc.setRemoteAddress(((InetSocketAddress) peer).getAddress()); + + new Thread(processors, "Git-Daemon-Client " + peer.toString()) { + @Override + public void run() { + try { + dc.execute(s); + } catch (ServiceNotEnabledException e) { + // Ignored. Client cannot use this repository. + } catch (ServiceNotAuthorizedException e) { + // Ignored. Client cannot use this repository. + } catch (IOException e) { + // Ignore unexpected IO exceptions from clients + } finally { + try { + s.getInputStream().close(); + } catch (IOException e) { + // Ignore close exceptions + } + try { + s.getOutputStream().close(); + } catch (IOException e) { + // Ignore close exceptions + } + } + } + }.start(); + } + + synchronized GitDaemonService matchService(final String cmd) { + for (final GitDaemonService d : services) { + if (d.handles(cmd)) + return d; + } + return null; + } + + Repository openRepository(GitDaemonClient 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. + // + name = name.replace('\\', '/'); + + // git://thishost/path should always be name="/path" here + // + if (!name.startsWith("/")) //$NON-NLS-1$ + return null; + + try { + return repositoryResolver.open(client, name.substring(1)); + } catch (RepositoryNotFoundException e) { + // null signals it "wasn't found", which is all that is suitable + // for the remote client to know. + return null; + } catch (ServiceNotEnabledException e) { + // null signals it "wasn't found", which is all that is suitable + // for the remote client to know. + return null; + } + } +} diff --git a/src/main/java/com/gitblit/transport/git/GitDaemonClient.java b/src/main/java/com/gitblit/transport/git/GitDaemonClient.java new file mode 100644 index 00000000..bc3d4cf7 --- /dev/null +++ b/src/main/java/com/gitblit/transport/git/GitDaemonClient.java @@ -0,0 +1,131 @@ +package com.gitblit.transport.git; + +/* + * Copyright (C) 2008-2009, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; + +import org.eclipse.jgit.transport.Daemon; +import org.eclipse.jgit.transport.PacketLineIn; +import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.util.io.SafeBufferedOutputStream; + +/** Active network client of {@link Daemon}. */ +public class GitDaemonClient { + private final GitDaemon daemon; + + private InetAddress peer; + + private InputStream rawIn; + + private OutputStream rawOut; + + private String repositoryName; + + GitDaemonClient(final GitDaemon d) { + daemon = d; + } + + void setRemoteAddress(final InetAddress ia) { + peer = ia; + } + + /** @return the daemon which spawned this client. */ + public GitDaemon getDaemon() { + return daemon; + } + + /** @return Internet address of the remote client. */ + public InetAddress getRemoteAddress() { + return peer; + } + + /** @return input stream to read from the connected client. */ + public InputStream getInputStream() { + return rawIn; + } + + /** @return output stream to send data to the connected client. */ + public OutputStream getOutputStream() { + return rawOut; + } + + public void setRepositoryName(String repositoryName) { + this.repositoryName = repositoryName; + } + + /** @return the name of the requested repository. */ + public String getRepositoryName() { + return repositoryName; + } + + void execute(final Socket sock) throws IOException, + ServiceNotEnabledException, ServiceNotAuthorizedException { + rawIn = new BufferedInputStream(sock.getInputStream()); + rawOut = new SafeBufferedOutputStream(sock.getOutputStream()); + + if (0 < daemon.getTimeout()) + sock.setSoTimeout(daemon.getTimeout() * 1000); + String cmd = new PacketLineIn(rawIn).readStringRaw(); + final int nul = cmd.indexOf('\0'); + if (nul >= 0) { + // Newer clients hide a "host" header behind this byte. + // Currently we don't use it for anything, so we ignore + // this portion of the command. + // + cmd = cmd.substring(0, nul); + } + + final GitDaemonService srv = getDaemon().matchService(cmd); + if (srv == null) + return; + sock.setSoTimeout(0); + srv.execute(this, cmd); + } +} \ No newline at end of file diff --git a/src/main/java/com/gitblit/transport/git/GitDaemonService.java b/src/main/java/com/gitblit/transport/git/GitDaemonService.java new file mode 100644 index 00000000..989b2b4c --- /dev/null +++ b/src/main/java/com/gitblit/transport/git/GitDaemonService.java @@ -0,0 +1,166 @@ +package com.gitblit.transport.git; + +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2009, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import java.io.IOException; + +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.Config.SectionParser; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.Daemon; +import org.eclipse.jgit.transport.PacketLineOut; +import org.eclipse.jgit.transport.ServiceMayNotContinueException; +import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; + +/** A service exposed by {@link Daemon} over anonymous git://. */ +public abstract class GitDaemonService { + private final String command; + + private final SectionParser configKey; + + private boolean enabled; + + private boolean overridable; + + GitDaemonService(final String cmdName, final String cfgName) { + command = cmdName.startsWith("git-") ? cmdName : "git-" + cmdName; //$NON-NLS-1$ //$NON-NLS-2$ + configKey = new SectionParser() { + @Override + public ServiceConfig parse(final Config cfg) { + return new ServiceConfig(GitDaemonService.this, cfg, cfgName); + } + }; + overridable = true; + } + + private static class ServiceConfig { + final boolean enabled; + + ServiceConfig(final GitDaemonService service, final Config cfg, + final String name) { + enabled = cfg.getBoolean("daemon", name, service.isEnabled()); //$NON-NLS-1$ + } + } + + /** @return is this service enabled for invocation? */ + public boolean isEnabled() { + return enabled; + } + + /** + * @param on + * true to allow this service to be used; false to deny it. + */ + public void setEnabled(final boolean on) { + enabled = on; + } + + /** @return can this service be configured in the repository config file? */ + public boolean isOverridable() { + return overridable; + } + + /** + * @param on + * true to permit repositories to override this service's enabled + * state with the daemon.servicename config setting. + */ + public void setOverridable(final boolean on) { + overridable = on; + } + + /** @return name of the command requested by clients. */ + public String getCommandName() { + return command; + } + + /** + * Determine if this service can handle the requested command. + * + * @param commandLine + * input line from the client. + * @return true if this command can accept the given command line. + */ + public boolean handles(final String commandLine) { + return command.length() + 1 < commandLine.length() + && commandLine.charAt(command.length()) == ' ' + && commandLine.startsWith(command); + } + + void execute(final GitDaemonClient client, final String commandLine) + throws IOException, ServiceNotEnabledException, + ServiceNotAuthorizedException { + final String name = commandLine.substring(command.length() + 1); + Repository db; + try { + db = client.getDaemon().openRepository(client, name); + } catch (ServiceMayNotContinueException e) { + // An error when opening the repo means the client is expecting a ref + // advertisement, so use that style of error. + PacketLineOut pktOut = new PacketLineOut(client.getOutputStream()); + pktOut.writeString("ERR " + e.getMessage() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + db = null; + } + if (db == null) + return; + try { + if (isEnabledFor(db)) + execute(client, db); + } finally { + db.close(); + } + } + + private boolean isEnabledFor(final Repository db) { + if (isOverridable()) + return db.getConfig().get(configKey).enabled; + return isEnabled(); + } + + abstract void execute(GitDaemonClient client, Repository db) + throws IOException, ServiceNotEnabledException, + ServiceNotAuthorizedException; +} diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java index b23ddd58..cc938bc1 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java +++ b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java @@ -44,7 +44,7 @@ import com.gitblit.utils.WorkQueue; /** * Manager for the ssh transport. Roughly analogous to the - * {@link com.gitblit.git.GitDaemon} class. + * {@link com.gitblit.transport.git.GitDaemon} class. * * @author Eric Myhre * -- cgit v1.2.3