Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

Daemon.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /*
  2. * Copyright (C) 2008-2009, Google Inc.
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.transport;
  44. import java.io.File;
  45. import java.io.IOException;
  46. import java.io.InputStream;
  47. import java.io.InterruptedIOException;
  48. import java.net.InetAddress;
  49. import java.net.InetSocketAddress;
  50. import java.net.ServerSocket;
  51. import java.net.Socket;
  52. import java.net.SocketAddress;
  53. import java.util.Collection;
  54. import java.util.Map;
  55. import java.util.concurrent.ConcurrentHashMap;
  56. import java.util.concurrent.CopyOnWriteArrayList;
  57. import org.eclipse.jgit.JGitText;
  58. import org.eclipse.jgit.lib.Constants;
  59. import org.eclipse.jgit.lib.PersonIdent;
  60. import org.eclipse.jgit.lib.Repository;
  61. import org.eclipse.jgit.lib.RepositoryCache;
  62. import org.eclipse.jgit.lib.RepositoryCache.FileKey;
  63. import org.eclipse.jgit.util.FS;
  64. /** Basic daemon for the anonymous <code>git://</code> transport protocol. */
  65. public class Daemon {
  66. /** 9418: IANA assigned port number for Git. */
  67. public static final int DEFAULT_PORT = 9418;
  68. private static final int BACKLOG = 5;
  69. private InetSocketAddress myAddress;
  70. private final DaemonService[] services;
  71. private final ThreadGroup processors;
  72. private volatile boolean exportAll;
  73. private Map<String, Repository> exports;
  74. private Collection<File> exportBase;
  75. private boolean run;
  76. private Thread acceptThread;
  77. private int timeout;
  78. /** Configure a daemon to listen on any available network port. */
  79. public Daemon() {
  80. this(null);
  81. }
  82. /**
  83. * Configure a new daemon for the specified network address.
  84. *
  85. * @param addr
  86. * address to listen for connections on. If null, any available
  87. * port will be chosen on all network interfaces.
  88. */
  89. public Daemon(final InetSocketAddress addr) {
  90. myAddress = addr;
  91. exports = new ConcurrentHashMap<String, Repository>();
  92. exportBase = new CopyOnWriteArrayList<File>();
  93. processors = new ThreadGroup("Git-Daemon");
  94. services = new DaemonService[] {
  95. new DaemonService("upload-pack", "uploadpack") {
  96. {
  97. setEnabled(true);
  98. }
  99. @Override
  100. protected void execute(final DaemonClient dc,
  101. final Repository db) throws IOException {
  102. final UploadPack rp = new UploadPack(db);
  103. final InputStream in = dc.getInputStream();
  104. rp.setTimeout(Daemon.this.getTimeout());
  105. rp.upload(in, dc.getOutputStream(), null);
  106. }
  107. }, new DaemonService("receive-pack", "receivepack") {
  108. {
  109. setEnabled(false);
  110. }
  111. @Override
  112. protected void execute(final DaemonClient dc,
  113. final Repository db) throws IOException {
  114. final InetAddress peer = dc.getRemoteAddress();
  115. String host = peer.getCanonicalHostName();
  116. if (host == null)
  117. host = peer.getHostAddress();
  118. final ReceivePack rp = new ReceivePack(db);
  119. final InputStream in = dc.getInputStream();
  120. final String name = "anonymous";
  121. final String email = name + "@" + host;
  122. rp.setRefLogIdent(new PersonIdent(name, email));
  123. rp.setTimeout(Daemon.this.getTimeout());
  124. rp.receive(in, dc.getOutputStream(), null);
  125. }
  126. } };
  127. }
  128. /** @return the address connections are received on. */
  129. public synchronized InetSocketAddress getAddress() {
  130. return myAddress;
  131. }
  132. /**
  133. * Lookup a supported service so it can be reconfigured.
  134. *
  135. * @param name
  136. * name of the service; e.g. "receive-pack"/"git-receive-pack" or
  137. * "upload-pack"/"git-upload-pack".
  138. * @return the service; null if this daemon implementation doesn't support
  139. * the requested service type.
  140. */
  141. public synchronized DaemonService getService(String name) {
  142. if (!name.startsWith("git-"))
  143. name = "git-" + name;
  144. for (final DaemonService s : services) {
  145. if (s.getCommandName().equals(name))
  146. return s;
  147. }
  148. return null;
  149. }
  150. /**
  151. * @return false if <code>git-daemon-export-ok</code> is required to export
  152. * a repository; true if <code>git-daemon-export-ok</code> is
  153. * ignored.
  154. * @see #setExportAll(boolean)
  155. */
  156. public boolean isExportAll() {
  157. return exportAll;
  158. }
  159. /**
  160. * Set whether or not to export all repositories.
  161. * <p>
  162. * If false (the default), repositories must have a
  163. * <code>git-daemon-export-ok</code> file to be accessed through this
  164. * daemon.
  165. * <p>
  166. * If true, all repositories are available through the daemon, whether or
  167. * not <code>git-daemon-export-ok</code> exists.
  168. *
  169. * @param export
  170. */
  171. public void setExportAll(final boolean export) {
  172. exportAll = export;
  173. }
  174. /**
  175. * Add a single repository to the set that is exported by this daemon.
  176. * <p>
  177. * The existence (or lack-thereof) of <code>git-daemon-export-ok</code> is
  178. * ignored by this method. The repository is always published.
  179. *
  180. * @param name
  181. * name the repository will be published under.
  182. * @param db
  183. * the repository instance.
  184. */
  185. public void exportRepository(String name, final Repository db) {
  186. if (!name.endsWith(Constants.DOT_GIT_EXT))
  187. name = name + Constants.DOT_GIT_EXT;
  188. exports.put(name, db);
  189. RepositoryCache.register(db);
  190. }
  191. /**
  192. * Recursively export all Git repositories within a directory.
  193. *
  194. * @param dir
  195. * the directory to export. This directory must not itself be a
  196. * git repository, but any directory below it which has a file
  197. * named <code>git-daemon-export-ok</code> will be published.
  198. */
  199. public void exportDirectory(final File dir) {
  200. exportBase.add(dir);
  201. }
  202. /** @return timeout (in seconds) before aborting an IO operation. */
  203. public int getTimeout() {
  204. return timeout;
  205. }
  206. /**
  207. * Set the timeout before willing to abort an IO call.
  208. *
  209. * @param seconds
  210. * number of seconds to wait (with no data transfer occurring)
  211. * before aborting an IO read or write operation with the
  212. * connected client.
  213. */
  214. public void setTimeout(final int seconds) {
  215. timeout = seconds;
  216. }
  217. /**
  218. * Start this daemon on a background thread.
  219. *
  220. * @throws IOException
  221. * the server socket could not be opened.
  222. * @throws IllegalStateException
  223. * the daemon is already running.
  224. */
  225. public synchronized void start() throws IOException {
  226. if (acceptThread != null)
  227. throw new IllegalStateException(JGitText.get().daemonAlreadyRunning);
  228. final ServerSocket listenSock = new ServerSocket(
  229. myAddress != null ? myAddress.getPort() : 0, BACKLOG,
  230. myAddress != null ? myAddress.getAddress() : null);
  231. myAddress = (InetSocketAddress) listenSock.getLocalSocketAddress();
  232. run = true;
  233. acceptThread = new Thread(processors, "Git-Daemon-Accept") {
  234. public void run() {
  235. while (isRunning()) {
  236. try {
  237. startClient(listenSock.accept());
  238. } catch (InterruptedIOException e) {
  239. // Test again to see if we should keep accepting.
  240. } catch (IOException e) {
  241. break;
  242. }
  243. }
  244. try {
  245. listenSock.close();
  246. } catch (IOException err) {
  247. //
  248. } finally {
  249. synchronized (Daemon.this) {
  250. acceptThread = null;
  251. }
  252. }
  253. }
  254. };
  255. acceptThread.start();
  256. }
  257. /** @return true if this daemon is receiving connections. */
  258. public synchronized boolean isRunning() {
  259. return run;
  260. }
  261. /** Stop this daemon. */
  262. public synchronized void stop() {
  263. if (acceptThread != null) {
  264. run = false;
  265. acceptThread.interrupt();
  266. }
  267. }
  268. private void startClient(final Socket s) {
  269. final DaemonClient dc = new DaemonClient(this);
  270. final SocketAddress peer = s.getRemoteSocketAddress();
  271. if (peer instanceof InetSocketAddress)
  272. dc.setRemoteAddress(((InetSocketAddress) peer).getAddress());
  273. new Thread(processors, "Git-Daemon-Client " + peer.toString()) {
  274. public void run() {
  275. try {
  276. dc.execute(s);
  277. } catch (IOException e) {
  278. // Ignore unexpected IO exceptions from clients
  279. e.printStackTrace();
  280. } finally {
  281. try {
  282. s.getInputStream().close();
  283. } catch (IOException e) {
  284. // Ignore close exceptions
  285. }
  286. try {
  287. s.getOutputStream().close();
  288. } catch (IOException e) {
  289. // Ignore close exceptions
  290. }
  291. }
  292. }
  293. }.start();
  294. }
  295. synchronized DaemonService matchService(final String cmd) {
  296. for (final DaemonService d : services) {
  297. if (d.handles(cmd))
  298. return d;
  299. }
  300. return null;
  301. }
  302. Repository openRepository(String name) {
  303. // Assume any attempt to use \ was by a Windows client
  304. // and correct to the more typical / used in Git URIs.
  305. //
  306. name = name.replace('\\', '/');
  307. // git://thishost/path should always be name="/path" here
  308. //
  309. if (!name.startsWith("/"))
  310. return null;
  311. // Forbid Windows UNC paths as they might escape the base
  312. //
  313. if (name.startsWith("//"))
  314. return null;
  315. // Forbid funny paths which contain an up-reference, they
  316. // might be trying to escape and read /../etc/password.
  317. //
  318. if (name.contains("/../"))
  319. return null;
  320. name = name.substring(1);
  321. Repository db;
  322. db = exports.get(name.endsWith(Constants.DOT_GIT_EXT) ? name : name
  323. + Constants.DOT_GIT_EXT);
  324. if (db != null) {
  325. db.incrementOpen();
  326. return db;
  327. }
  328. for (final File baseDir : exportBase) {
  329. final File gitdir = FileKey.resolve(new File(baseDir, name), FS.DETECTED);
  330. if (gitdir != null && canExport(gitdir))
  331. return openRepository(gitdir);
  332. }
  333. return null;
  334. }
  335. private static Repository openRepository(final File gitdir) {
  336. try {
  337. return RepositoryCache.open(FileKey.exact(gitdir, FS.DETECTED));
  338. } catch (IOException err) {
  339. // null signals it "wasn't found", which is all that is suitable
  340. // for the remote client to know.
  341. return null;
  342. }
  343. }
  344. private boolean canExport(final File d) {
  345. if (isExportAll()) {
  346. return true;
  347. }
  348. return new File(d, "git-daemon-export-ok").exists();
  349. }
  350. }