/* * 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.File; 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.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; /** Configure a daemon to listen on any available network port. */ public GitDaemon() { this(null); } /** * Construct the Gitblit Git daemon. * * @param bindInterface * the ip address of the interface to bind * @param port * the port to serve on * @param folder * the folder to serve from */ public GitDaemon(String bindInterface, int port, File folder) { this(StringUtils.isEmpty(bindInterface) ? new InetSocketAddress(port) : new InetSocketAddress(bindInterface, port)); // set the repository resolver and pack factories repositoryResolver = new RepositoryResolver(folder); } /** * Configure a new daemon for the specified network address. * * @param addr * address to listen for connections on. If null, any available * port will be chosen on all network interfaces. */ public GitDaemon(final InetSocketAddress addr) { myAddress = addr; processors = new ThreadGroup("Git-Daemon"); run = new AtomicBoolean(false); repositoryResolver = null; uploadPackFactory = new GitblitUploadPackFactory(); receivePackFactory = new GitblitReceivePackFactory(); 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") { 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()) { 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; } } } id='n223' href='#n223'>223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296