You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GitBlitServer.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /*
  2. * Copyright 2011 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit;
  17. import java.io.BufferedReader;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.InputStreamReader;
  21. import java.io.OutputStream;
  22. import java.net.InetAddress;
  23. import java.net.ServerSocket;
  24. import java.net.Socket;
  25. import java.net.URL;
  26. import java.net.UnknownHostException;
  27. import java.security.ProtectionDomain;
  28. import java.text.MessageFormat;
  29. import java.util.ArrayList;
  30. import java.util.List;
  31. import org.apache.log4j.ConsoleAppender;
  32. import org.apache.log4j.PatternLayout;
  33. import org.apache.wicket.protocol.http.ContextParamWebApplicationFactory;
  34. import org.apache.wicket.protocol.http.WicketFilter;
  35. import org.eclipse.jetty.http.security.Constraint;
  36. import org.eclipse.jetty.security.ConstraintMapping;
  37. import org.eclipse.jetty.security.ConstraintSecurityHandler;
  38. import org.eclipse.jetty.security.LoginService;
  39. import org.eclipse.jetty.security.authentication.BasicAuthenticator;
  40. import org.eclipse.jetty.server.Connector;
  41. import org.eclipse.jetty.server.Handler;
  42. import org.eclipse.jetty.server.Server;
  43. import org.eclipse.jetty.server.bio.SocketConnector;
  44. import org.eclipse.jetty.server.nio.SelectChannelConnector;
  45. import org.eclipse.jetty.server.session.HashSessionManager;
  46. import org.eclipse.jetty.server.ssl.SslConnector;
  47. import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
  48. import org.eclipse.jetty.server.ssl.SslSocketConnector;
  49. import org.eclipse.jetty.servlet.FilterHolder;
  50. import org.eclipse.jetty.servlet.FilterMapping;
  51. import org.eclipse.jetty.servlet.ServletHolder;
  52. import org.eclipse.jetty.util.log.Log;
  53. import org.eclipse.jetty.util.log.Logger;
  54. import org.eclipse.jetty.util.thread.QueuedThreadPool;
  55. import org.eclipse.jetty.webapp.WebAppContext;
  56. import com.beust.jcommander.JCommander;
  57. import com.beust.jcommander.Parameter;
  58. import com.beust.jcommander.ParameterException;
  59. import com.beust.jcommander.Parameters;
  60. import com.gitblit.utils.StringUtils;
  61. import com.gitblit.wicket.GitBlitWebApp;
  62. public class GitBlitServer {
  63. private final static Logger logger = Log.getLogger(GitBlitServer.class.getSimpleName());
  64. private final static String border_star = "***********************************************************";
  65. private final static FileSettings fileSettings = new FileSettings();
  66. public static void main(String[] args) {
  67. Params params = new Params();
  68. JCommander jc = new JCommander(params);
  69. try {
  70. jc.parse(args);
  71. if (params.help)
  72. usage(jc, null);
  73. } catch (ParameterException t) {
  74. usage(jc, t);
  75. }
  76. if (params.stop)
  77. stop(params);
  78. else
  79. start(params);
  80. }
  81. private static void usage(JCommander jc, ParameterException t) {
  82. System.out.println(border_star);
  83. System.out.println(Constants.getRunningVersion());
  84. System.out.println(border_star);
  85. System.out.println();
  86. if (t != null) {
  87. System.out.println(t.getMessage());
  88. System.out.println();
  89. }
  90. if (jc != null) {
  91. jc.usage();
  92. System.out.println("\nExample:\n java -server -Xmx1024M -jar gitblit.jar --repos c:\\git --port 80 --securePort 443");
  93. }
  94. System.exit(0);
  95. }
  96. /**
  97. * Stop Server.
  98. */
  99. public static void stop(Params params) {
  100. try {
  101. Socket s = new Socket(InetAddress.getByName("127.0.0.1"), params.shutdownPort);
  102. OutputStream out = s.getOutputStream();
  103. System.out.println("Sending Shutdown Request to " + Constants.NAME);
  104. out.write(("\r\n").getBytes());
  105. out.flush();
  106. s.close();
  107. } catch (UnknownHostException e) {
  108. e.printStackTrace();
  109. } catch (IOException e) {
  110. e.printStackTrace();
  111. }
  112. }
  113. /**
  114. * Start Server.
  115. */
  116. private static void start(Params params) {
  117. String pattern = fileSettings.getString(Keys.server.log4jPattern, "%-5p %d{MM-dd HH:mm:ss.SSS} %-20.20c{1} %m%n");
  118. // allow os override of logging pattern
  119. String os = System.getProperty("os.name").toLowerCase();
  120. if (os.indexOf("windows") > -1) {
  121. String winPattern = fileSettings.getString(Keys.server.log4jPattern_windows, pattern);
  122. if (!StringUtils.isEmpty(winPattern)) {
  123. pattern = winPattern;
  124. }
  125. } else if (os.indexOf("linux") > -1) {
  126. String linuxPattern = fileSettings.getString(Keys.server.log4jPattern_linux, pattern);
  127. if (!StringUtils.isEmpty(linuxPattern)) {
  128. pattern = linuxPattern;
  129. }
  130. }
  131. PatternLayout layout = new PatternLayout(pattern);
  132. org.apache.log4j.Logger rootLogger = org.apache.log4j.Logger.getRootLogger();
  133. rootLogger.addAppender(new ConsoleAppender(layout));
  134. logger.info(border_star);
  135. logger.info(Constants.getRunningVersion());
  136. logger.info(border_star);
  137. String osname = System.getProperty("os.name");
  138. String osversion = System.getProperty("os.version");
  139. logger.info("Running on " + osname + " (" + osversion + ")");
  140. // Determine port connectors
  141. List<Connector> connectors = new ArrayList<Connector>();
  142. if (params.port > 0) {
  143. Connector httpConnector = createConnector(params.useNIO, params.port);
  144. String bindInterface = fileSettings.getString(Keys.server.httpBindInterface, null);
  145. if (!StringUtils.isEmpty(bindInterface)) {
  146. logger.warn(MessageFormat.format("Binding connector on port {0} to {1}", params.port, bindInterface));
  147. httpConnector.setHost(bindInterface);
  148. }
  149. connectors.add(httpConnector);
  150. }
  151. if (params.securePort > 0) {
  152. File keystore = new File("keystore");
  153. if (!keystore.exists()) {
  154. logger.info("Generating self-signed SSL certificate");
  155. MakeCertificate.generateSelfSignedCertificate("localhost", keystore, params.storePassword);
  156. }
  157. if (keystore.exists()) {
  158. Connector secureConnector = createSSLConnector(keystore, params.storePassword, params.useNIO, params.securePort);
  159. String bindInterface = fileSettings.getString(Keys.server.httpsBindInterface, null);
  160. if (!StringUtils.isEmpty(bindInterface)) {
  161. logger.warn(MessageFormat.format("Binding ssl connector on port {0} to {1}", params.securePort, bindInterface));
  162. secureConnector.setHost(bindInterface);
  163. }
  164. connectors.add(secureConnector);
  165. } else {
  166. logger.warn("Failed to find or load Keystore?");
  167. logger.warn("SSL connector DISABLED.");
  168. }
  169. }
  170. // tempDir = Directory where...
  171. // * WebApp is expanded
  172. //
  173. File tempDir = new File(params.temp);
  174. if (tempDir.exists())
  175. deleteRecursively(tempDir);
  176. tempDir.mkdirs();
  177. Server server = new Server();
  178. server.setStopAtShutdown(true);
  179. server.setConnectors(connectors.toArray(new Connector[connectors.size()]));
  180. // Get the execution path of this class
  181. // We use this to set the WAR path.
  182. ProtectionDomain protectionDomain = GitBlitServer.class.getProtectionDomain();
  183. URL location = protectionDomain.getCodeSource().getLocation();
  184. // Root WebApp Context
  185. WebAppContext rootContext = new WebAppContext();
  186. rootContext.setContextPath("/");
  187. rootContext.setServer(server);
  188. rootContext.setWar(location.toExternalForm());
  189. rootContext.setTempDirectory(tempDir);
  190. // Mark all cookies HttpOnly so they are not accessible to JavaScript
  191. // engines.
  192. // http://erlend.oftedal.no/blog/?blogid=33
  193. // https://www.owasp.org/index.php/HttpOnly#Browsers_Supporting_HttpOnly
  194. HashSessionManager sessionManager = new HashSessionManager();
  195. sessionManager.setHttpOnly(true);
  196. // Use secure cookies if only serving https
  197. sessionManager.setSecureCookies(params.port <= 0 && params.securePort > 0);
  198. rootContext.getSessionHandler().setSessionManager(sessionManager);
  199. // Wicket Filter
  200. String wicketPathSpec = "/*";
  201. FilterHolder wicketFilter = new FilterHolder(WicketFilter.class);
  202. wicketFilter.setInitParameter(ContextParamWebApplicationFactory.APP_CLASS_PARAM, GitBlitWebApp.class.getName());
  203. wicketFilter.setInitParameter(WicketFilter.FILTER_MAPPING_PARAM, wicketPathSpec);
  204. wicketFilter.setInitParameter(WicketFilter.IGNORE_PATHS_PARAM, "git/");
  205. rootContext.addFilter(wicketFilter, wicketPathSpec, FilterMapping.DEFAULT);
  206. // Zip Servlet
  207. rootContext.addServlet(DownloadZipServlet.class, Constants.ZIP_SERVLET_PATH + "*");
  208. // Git Servlet
  209. ServletHolder gitServlet = null;
  210. String gitServletPathSpec = Constants.GIT_SERVLET_PATH + "*";
  211. if (fileSettings.getBoolean(Keys.git.enableGitServlet, true)) {
  212. gitServlet = rootContext.addServlet(GitBlitServlet.class, gitServletPathSpec);
  213. gitServlet.setInitParameter("base-path", params.repositoriesFolder);
  214. gitServlet.setInitParameter("export-all", fileSettings.getBoolean(Keys.git.exportAll, true) ? "1" : "0");
  215. }
  216. // Login Service
  217. LoginService loginService = null;
  218. String realmUsers = params.realmFile;
  219. if (!StringUtils.isEmpty(realmUsers)) {
  220. File realmFile = new File(realmUsers);
  221. if (realmFile.exists()) {
  222. logger.info("Setting up login service from " + realmUsers);
  223. JettyLoginService jettyLoginService = new JettyLoginService(realmFile);
  224. GitBlit.self().setLoginService(jettyLoginService);
  225. loginService = jettyLoginService;
  226. }
  227. }
  228. // Determine what handler to use
  229. Handler handler;
  230. if (gitServlet != null) {
  231. if (loginService != null) {
  232. // Authenticate Clone/Push
  233. logger.info("Setting up authenticated git servlet clone/push access");
  234. Constraint constraint = new Constraint();
  235. constraint.setAuthenticate(true);
  236. constraint.setRoles(new String [] { "*" });
  237. ConstraintMapping mapping = new ConstraintMapping();
  238. mapping.setPathSpec(gitServletPathSpec);
  239. mapping.setConstraint(constraint);
  240. ConstraintSecurityHandler security = new ConstraintSecurityHandler();
  241. security.addConstraintMapping(mapping);
  242. security.setAuthenticator(new BasicAuthenticator());
  243. security.setLoginService(loginService);
  244. security.setStrict(false);
  245. security.setHandler(rootContext);
  246. handler = security;
  247. } else {
  248. // Anonymous Pull/Push
  249. logger.info("Setting up anonymous git servlet pull/push access");
  250. handler = rootContext;
  251. }
  252. } else {
  253. logger.info("Git servlet clone/push disabled");
  254. handler = rootContext;
  255. }
  256. // Set the server's contexts
  257. server.setHandler(handler);
  258. // Setup the GitBlit context
  259. GitBlit gitblit = GitBlit.self();
  260. gitblit.configureContext(fileSettings);
  261. rootContext.addEventListener(gitblit);
  262. // Start the Server
  263. try {
  264. if (params.shutdownPort > 0) {
  265. Thread shutdownMonitor = new ShutdownMonitorThread(server, params);
  266. shutdownMonitor.start();
  267. }
  268. server.start();
  269. server.join();
  270. } catch (Exception e) {
  271. e.printStackTrace();
  272. System.exit(100);
  273. }
  274. }
  275. private static Connector createConnector(boolean useNIO, int port) {
  276. Connector connector;
  277. if (useNIO) {
  278. logger.info("Setting up NIO SelectChannelConnector on port " + port);
  279. SelectChannelConnector nioconn = new SelectChannelConnector();
  280. nioconn.setSoLingerTime(-1);
  281. nioconn.setThreadPool(new QueuedThreadPool(20));
  282. connector = nioconn;
  283. } else {
  284. logger.info("Setting up SocketConnector on port " + port);
  285. SocketConnector sockconn = new SocketConnector();
  286. connector = sockconn;
  287. }
  288. connector.setPort(port);
  289. connector.setMaxIdleTime(30000);
  290. return connector;
  291. }
  292. private static Connector createSSLConnector(File keystore, String password, boolean useNIO, int port) {
  293. SslConnector connector;
  294. if (useNIO) {
  295. logger.info("Setting up NIO SslSelectChannelConnector on port " + port);
  296. SslSelectChannelConnector ssl = new SslSelectChannelConnector();
  297. ssl.setSoLingerTime(-1);
  298. ssl.setThreadPool(new QueuedThreadPool(20));
  299. connector = ssl;
  300. } else {
  301. logger.info("Setting up NIO SslSocketConnector on port " + port);
  302. SslSocketConnector ssl = new SslSocketConnector();
  303. connector = ssl;
  304. }
  305. connector.setAllowRenegotiate(false);
  306. connector.setKeystore(keystore.getAbsolutePath());
  307. connector.setPassword(password);
  308. connector.setPort(port);
  309. connector.setMaxIdleTime(30000);
  310. return connector;
  311. }
  312. /**
  313. * Recursively delete a folder and its contents.
  314. *
  315. * @param folder
  316. */
  317. private static void deleteRecursively(File folder) {
  318. for (File file : folder.listFiles()) {
  319. if (file.isDirectory())
  320. deleteRecursively(file);
  321. else
  322. file.delete();
  323. }
  324. folder.delete();
  325. }
  326. private static class ShutdownMonitorThread extends Thread {
  327. private final ServerSocket socket;
  328. private final Server server;
  329. public ShutdownMonitorThread(Server server, Params params) {
  330. this.server = server;
  331. setDaemon(true);
  332. setName(Constants.NAME + " Shutdown Monitor");
  333. ServerSocket skt = null;
  334. try {
  335. skt = new ServerSocket(params.shutdownPort, 1, InetAddress.getByName("127.0.0.1"));
  336. } catch (Exception e) {
  337. logger.warn(e);
  338. }
  339. socket = skt;
  340. }
  341. @Override
  342. public void run() {
  343. logger.info("Shutdown Monitor listening on port " + socket.getLocalPort());
  344. Socket accept;
  345. try {
  346. accept = socket.accept();
  347. BufferedReader reader = new BufferedReader(new InputStreamReader(accept.getInputStream()));
  348. reader.readLine();
  349. logger.info(border_star);
  350. logger.info("Stopping " + Constants.NAME);
  351. logger.info(border_star);
  352. server.stop();
  353. server.setStopAtShutdown(false);
  354. accept.close();
  355. socket.close();
  356. } catch (Exception e) {
  357. logger.warn("Failed to shutdown Jetty", e);
  358. }
  359. }
  360. }
  361. @Parameters(separators = " ")
  362. private static class Params {
  363. /*
  364. * Server parameters
  365. */
  366. @Parameter(names = { "-h", "--help" }, description = "Show this help")
  367. public Boolean help = false;
  368. @Parameter(names = { "--stop" }, description = "Stop Server")
  369. public Boolean stop = false;
  370. @Parameter(names = { "--tempFolder" }, description = "Server temp folder")
  371. public String temp = fileSettings.getString(Keys.server.tempFolder, "temp");
  372. /*
  373. * GIT Servlet Parameters
  374. */
  375. @Parameter(names = { "--repositoriesFolder" }, description = "Git Repositories Folder")
  376. public String repositoriesFolder = fileSettings.getString(Keys.git.repositoriesFolder, "repos");
  377. /*
  378. * Authentication Parameters
  379. */
  380. @Parameter(names = { "--realmFile" }, description = "Users Realm Hash File")
  381. public String realmFile = fileSettings.getString(Keys.realm.realmFile, "users.properties");
  382. /*
  383. * JETTY Parameters
  384. */
  385. @Parameter(names = { "--useNio" }, description = "Use NIO Connector else use Socket Connector.")
  386. public Boolean useNIO = fileSettings.getBoolean(Keys.server.useNio, true);
  387. @Parameter(names = "--httpPort", description = "HTTP port for to serve. (port <= 0 will disable this connector)")
  388. public Integer port = fileSettings.getInteger(Keys.server.httpPort, 80);
  389. @Parameter(names = "--httpsPort", description = "HTTPS port to serve. (port <= 0 will disable this connector)")
  390. public Integer securePort = fileSettings.getInteger(Keys.server.httpsPort, 443);
  391. @Parameter(names = "--storePassword", description = "Password for SSL (https) keystore.")
  392. public String storePassword = fileSettings.getString(Keys.server.storePassword, "");
  393. @Parameter(names = "--shutdownPort", description = "Port for Shutdown Monitor to listen on. (port <= 0 will disable this monitor)")
  394. public Integer shutdownPort = fileSettings.getInteger(Keys.server.shutdownPort, 8081);
  395. }
  396. }