forked from j62/ctbrec
1
0
Fork 0

Add support for TLS and changing the context path

This commit is contained in:
0xboobface 2019-08-10 17:45:13 +02:00
parent bb02b5fd9f
commit f12a20a15e
14 changed files with 261 additions and 95 deletions

View File

@ -118,24 +118,28 @@ public class Player {
public void run() {
running = true;
Runtime rt = Runtime.getRuntime();
Config cfg = Config.getInstance();
try {
if (Config.getInstance().getSettings().localRecording && rec != null) {
File file = new File(Config.getInstance().getSettings().recordingsDir, rec.getPath());
if (cfg.getSettings().localRecording && rec != null) {
File file = new File(cfg.getSettings().recordingsDir, rec.getPath());
String[] args = new String[] {
Config.getInstance().getSettings().mediaPlayer,
cfg.getSettings().mediaPlayer,
file.getName()
};
playerProcess = rt.exec(args, OS.getEnvironment(), file.getParentFile());
} else {
if(Config.getInstance().getSettings().requireAuthentication) {
if(cfg.getSettings().requireAuthentication) {
URL u = new URL(url);
String path = u.getPath();
byte[] key = Config.getInstance().getSettings().key;
if(!cfg.getContextPath().isEmpty()) {
path = path.substring(cfg.getContextPath().length());
}
byte[] key = cfg.getSettings().key;
String hmac = Hmac.calculate(path, key);
url = url + "?hmac=" + hmac;
}
LOG.debug("Playing {}", url);
playerProcess = rt.exec(Config.getInstance().getSettings().mediaPlayer + " " + url);
playerProcess = rt.exec(cfg.getSettings().mediaPlayer + " " + url);
}
// create threads, which read stdout and stderr of the player process. these are needed,

View File

@ -472,7 +472,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
File target = chooser.showSaveDialog(null);
if(target != null) {
config.getSettings().lastDownloadDir = target.getParent();
String hlsBase = "http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/hls";
String hlsBase = config.getServerUrl() + "/hls";
URL url = new URL(hlsBase + recording.getPath() + "/playlist.m3u8");
LOG.info("Downloading {}", recording.getPath());
@ -542,7 +542,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
}
}.start();
} else {
String hlsBase = "http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/hls";
String hlsBase = Config.getInstance().getServerUrl() + "/hls";
url = hlsBase + recording.getPath() + "/playlist.m3u8";
new Thread() {
@Override

View File

@ -61,8 +61,10 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private TextField overviewUpdateIntervalInSecs;
private TextField leaveSpaceOnDevice;
private TextField minimumLengthInSecs;
private TextField servletContext;
private CheckBox loadResolution;
private CheckBox secureCommunication = new CheckBox();
private CheckBox useAuthentication = new CheckBox();
private CheckBox useTLS = new CheckBox();
private CheckBox chooseStreamQuality = new CheckBox();
private CheckBox multiplePlayers = new CheckBox();
private CheckBox updateThumbnails = new CheckBox();
@ -152,7 +154,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private Node createRecordLocationPanel() {
GridPane layout = createGridLayout();
Label l = new Label("Record Location");
layout.add(l, 0, 0);
int row = 0;
layout.add(l, 0, row);
recordLocation = new ToggleGroup();
recordLocal = new RadioButton("Local");
recordRemote = new RadioButton("Remote");
@ -160,8 +163,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
recordRemote.setToggleGroup(recordLocation);
recordLocal.setSelected(Config.getInstance().getSettings().localRecording);
recordRemote.setSelected(!recordLocal.isSelected());
layout.add(recordLocal, 1, 0);
layout.add(recordRemote, 2, 0);
layout.add(recordLocal, 1, row);
layout.add(recordRemote, 2, row++);
recordLocation.selectedToggleProperty().addListener((e) -> {
Config.getInstance().getSettings().localRecording = recordLocal.isSelected();
setRecordingMode(recordLocal.isSelected());
@ -172,7 +175,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
GridPane.setMargin(recordLocal, new Insets(0, 0, CHECKBOX_MARGIN, 0));
GridPane.setMargin(recordRemote, new Insets(0, 0, CHECKBOX_MARGIN, 0));
layout.add(new Label("Server"), 0, 1);
layout.add(new Label("Server"), 0, row);
server = new TextField(Config.getInstance().getSettings().httpServer);
server.textProperty().addListener((ob, o, n) -> {
if(!server.getText().isEmpty()) {
@ -183,9 +186,9 @@ public class SettingsTab extends Tab implements TabSelectionListener {
GridPane.setFillWidth(server, true);
GridPane.setHgrow(server, Priority.ALWAYS);
GridPane.setColumnSpan(server, 2);
layout.add(server, 1, 1);
layout.add(server, 1, row++);
layout.add(new Label("Port"), 0, 2);
layout.add(new Label("Port"), 0, row);
port = new TextField(Integer.toString(Config.getInstance().getSettings().httpPort));
port.textProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue.matches("\\d*")) {
@ -199,14 +202,27 @@ public class SettingsTab extends Tab implements TabSelectionListener {
GridPane.setFillWidth(port, true);
GridPane.setHgrow(port, Priority.ALWAYS);
GridPane.setColumnSpan(port, 2);
layout.add(port, 1, 2);
layout.add(port, 1, row++);
layout.add(new Label("Path"), 0, row);
servletContext = new TextField(Config.getInstance().getSettings().servletContext);
servletContext.setPromptText("e.g. /ctbrec");
servletContext.setTooltip(new Tooltip("Leave empty, if you didn't change the servletContext in the server config"));
servletContext.textProperty().addListener((observable, oldValue, newValue) -> {
Config.getInstance().getSettings().servletContext = servletContext.getText();
saveConfig();
});
GridPane.setFillWidth(servletContext, true);
GridPane.setHgrow(servletContext, Priority.ALWAYS);
GridPane.setColumnSpan(servletContext, 2);
layout.add(servletContext, 1, row++);
l = new Label("Require authentication");
layout.add(l, 0, 3);
secureCommunication.setSelected(Config.getInstance().getSettings().requireAuthentication);
secureCommunication.setOnAction((e) -> {
Config.getInstance().getSettings().requireAuthentication = secureCommunication.isSelected();
if(secureCommunication.isSelected()) {
layout.add(l, 0, row);
useAuthentication.setSelected(Config.getInstance().getSettings().requireAuthentication);
useAuthentication.setOnAction((e) -> {
Config.getInstance().getSettings().requireAuthentication = useAuthentication.isSelected();
if(useAuthentication.isSelected()) {
byte[] key = Config.getInstance().getSettings().key;
if(key == null) {
key = Hmac.generateKey();
@ -226,8 +242,19 @@ public class SettingsTab extends Tab implements TabSelectionListener {
}
});
GridPane.setMargin(l, new Insets(4, CHECKBOX_MARGIN, 0, 0));
GridPane.setMargin(secureCommunication, new Insets(4, 0, 0, 0));
layout.add(secureCommunication, 1, 3);
GridPane.setMargin(useAuthentication, new Insets(4, 0, 0, 0));
layout.add(useAuthentication, 1, row++);
l = new Label("Use Secure Communication (TLS)");
layout.add(l, 0, row);
useTLS.setSelected(Config.getInstance().getSettings().transportLayerSecurity);
useTLS.setOnAction((e) -> {
Config.getInstance().getSettings().transportLayerSecurity = useTLS.isSelected();
saveConfig();
});
GridPane.setMargin(l, new Insets(4, CHECKBOX_MARGIN, 0, 0));
GridPane.setMargin(useTLS, new Insets(4, 0, 0, 0));
layout.add(useTLS, 1, row++);
TitledPane recordLocation = new TitledPane("Record Location", layout);
recordLocation.setCollapsible(false);
@ -582,7 +609,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private void setRecordingMode(boolean local) {
server.setDisable(local);
port.setDisable(local);
secureCommunication.setDisable(local);
useAuthentication.setDisable(local);
useTLS.setDisable(local);
recordingsDirectory.setDisable(!local);
splitAfter.setDisable(!local);
maxResolution.setDisable(!local);

View File

@ -11,6 +11,7 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -163,4 +164,23 @@ public class Config {
return new File(getSettings().recordingsDir);
}
}
public String getServerUrl() {
String scheme = getSettings().transportLayerSecurity ? "https" : "http";
//int port = getSettings().transportLayerSecurity ? getSettings().httpSecurePort : getSettings().httpPort;
int port = getSettings().httpPort;
String baseUrl = scheme + "://" + getSettings().httpServer + ":" + port + getContextPath();
return baseUrl;
}
public String getContextPath() {
String context = Optional.ofNullable(getSettings().servletContext).orElse("");
if (!context.startsWith("/") && !context.isEmpty()) {
context = '/' + context;
}
if (context.endsWith("/")) {
context = context.substring(0, context.length() - 1);
}
return context;
}
}

View File

@ -54,6 +54,7 @@ public class Settings {
public String flirt4freeUsername;
public boolean generatePlaylist = true;
public int httpPort = 8080;
public int httpSecurePort = 8443;
public String httpServer = "localhost";
public int httpTimeout = 10000;
public String httpUserAgent = "Mozilla/5.0 Gecko/20100101 Firefox/62.0";
@ -100,12 +101,14 @@ public class Settings {
public String recordingsSortColumn = "";
public String recordingsSortType = "";
public boolean requireAuthentication = false;
public String servletContext = "";
public boolean showPlayerStarting = false;
public boolean singlePlayer = true;
public int splitRecordings = 0;
public String startTab = "Settings";
public String streamatePassword = "";
public String streamateUsername = "";
public boolean transportLayerSecurity = true;
public int thumbWidth = 180;
public boolean updateThumbnails = true;
public String username = ""; // chaturbate username TODO maybe rename this onetime

View File

@ -6,6 +6,11 @@ import java.io.IOException;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.nio.file.Files;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -13,6 +18,12 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -124,9 +135,45 @@ public abstract class HttpClient {
}
}
// if transport layer security (TLS) is switched on, accept the self signed cert from the server
if (Config.getInstance().getSettings().transportLayerSecurity) {
acceptAllTlsCerts(builder);
}
client = builder.build();
}
/**
* This is a very simple and insecure solution to accept the self-signed cert from
* the server. The side effect is, that certificates from other servers are neither checked!
* TODO Delegate to the default trustmanager, if it is not the self-signed cert
*/
private void acceptAllTlsCerts(Builder builder) {
X509TrustManager x509TrustManager = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] x509Certificates = new X509Certificate[0];
return x509Certificates;
}
@Override public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {}
@Override public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {}
};
try {
final TrustManager[] trustManagers = new TrustManager[] { x509TrustManager };
final String PROTOCOL = "TLSv1.2";
SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
KeyManager[] keyManagers = null;
SecureRandom secureRandom = new SecureRandom();
sslContext.init(keyManagers, trustManagers, secureRandom);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
builder.sslSocketFactory(sslSocketFactory, x509TrustManager);
builder.hostnameVerifier((name, sslSession) -> true);
} catch (KeyManagementException | NoSuchAlgorithmException e) {
LOG.error("Couldn't install trust managers for TLS connections");
}
}
public void shutdown() {
persistCookies();
client.connectionPool().evictAll();

View File

@ -38,6 +38,7 @@ import okhttp3.Response;
public class RemoteRecorder implements Recorder {
private static final transient Logger LOG = LoggerFactory.getLogger(RemoteRecorder.class);
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private Moshi moshi = new Moshi.Builder()
.add(Instant.class, new InstantJsonAdapter())
@ -71,6 +72,10 @@ public class RemoteRecorder implements Recorder {
syncThread.start();
}
private String getRecordingEndpoint() {
return config.getServerUrl() + "/rec";
}
@Override
public void startRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
sendRequest("start", model);
@ -86,7 +91,7 @@ public class RemoteRecorder implements Recorder {
LOG.debug("Sending request to recording server: {}", payload);
RequestBody body = RequestBody.create(JSON, payload);
Request.Builder builder = new Request.Builder()
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
.url(getRecordingEndpoint())
.post(body);
addHmacIfNeeded(payload, builder);
Request request = builder.build();
@ -172,7 +177,7 @@ public class RemoteRecorder implements Recorder {
String msg = "{\"action\": \"space\"}";
RequestBody body = RequestBody.create(JSON, msg);
Request.Builder builder = new Request.Builder()
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
.url(getRecordingEndpoint())
.post(body);
addHmacIfNeeded(msg, builder);
Request request = builder.build();
@ -196,7 +201,7 @@ public class RemoteRecorder implements Recorder {
String msg = "{\"action\": \"list\"}";
RequestBody body = RequestBody.create(JSON, msg);
Request.Builder builder = new Request.Builder()
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
.url(getRecordingEndpoint())
.post(body);
addHmacIfNeeded(msg, builder);
Request request = builder.build();
@ -231,7 +236,7 @@ public class RemoteRecorder implements Recorder {
String msg = "{\"action\": \"listOnline\"}";
RequestBody body = RequestBody.create(JSON, msg);
Request.Builder builder = new Request.Builder()
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
.url(getRecordingEndpoint())
.post(body);
addHmacIfNeeded(msg, builder);
Request request = builder.build();
@ -265,7 +270,7 @@ public class RemoteRecorder implements Recorder {
String msg = "{\"action\": \"recordings\"}";
RequestBody body = RequestBody.create(JSON, msg);
Request.Builder builder = new Request.Builder()
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
.url(getRecordingEndpoint())
.post(body);
addHmacIfNeeded(msg, builder);
Request request = builder.build();
@ -344,7 +349,7 @@ public class RemoteRecorder implements Recorder {
String msg = recordingRequestAdapter.toJson(recReq);
RequestBody body = RequestBody.create(JSON, msg);
Request.Builder builder = new Request.Builder()
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
.url(getRecordingEndpoint())
.post(body);
addHmacIfNeeded(msg, builder);
Request request = builder.build();
@ -472,7 +477,8 @@ public class RemoteRecorder implements Recorder {
String msg = recordingRequestAdapter.toJson(recReq);
LOG.debug(msg);
RequestBody body = RequestBody.create(JSON, msg);
Request.Builder builder = new Request.Builder().url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
Request.Builder builder = new Request.Builder()
.url(getRecordingEndpoint())
.post(body);
addHmacIfNeeded(msg, builder);
Request request = builder.build();

View File

@ -89,6 +89,9 @@ public class MergedHlsDownload extends AbstractHlsDownload {
URL u = new URL(segmentPlaylistUri);
String path = u.getPath();
byte[] key = Config.getInstance().getSettings().key;
if(!Config.getInstance().getContextPath().isEmpty()) {
path = path.substring(Config.getInstance().getContextPath().length());
}
String hmac = Hmac.calculate(path, key);
segmentPlaylistUri = segmentPlaylistUri + "?hmac=" + hmac;
}

View File

@ -4,6 +4,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@ -13,14 +15,19 @@ import javax.servlet.http.HttpServletResponse;
public class StaticFileServlet extends HttpServlet {
private String classPathRoot;
private Map<String, String> mimetypes = new HashMap<>();
public StaticFileServlet(String classPathRoot) {
this.classPathRoot = classPathRoot;
mimetypes.put("css", "text/css");
mimetypes.put("js", "application/javascript");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String request = req.getRequestURI();
String contextPath = getServletContext().getContextPath();
request = request.substring(contextPath.length());
serveFile(request, resp);
}
@ -34,7 +41,12 @@ public class StaticFileServlet extends HttpServlet {
if (resourceAsStream == null) {
throw new FileNotFoundException();
}
resp.setContentType(URLConnection.guessContentTypeFromName(resource));
String mimetype = URLConnection.guessContentTypeFromName(resource);
if (mimetype == null) {
mimetype = guessMimeType(resource);
}
resp.setContentType(mimetype);
resp.setStatus(HttpServletResponse.SC_OK);
OutputStream out = resp.getOutputStream();
int length = 0;
@ -43,4 +55,12 @@ public class StaticFileServlet extends HttpServlet {
out.write(buffer, 0, length);
}
}
private String guessMimeType(String resource) {
try {
String extension = resource.substring(resource.lastIndexOf('.') + 1);
return mimetypes.get(extension);
} catch(Exception e) {}
return null;
}
}

View File

@ -20,6 +20,7 @@ public abstract class AbstractCtbrecServlet extends HttpServlet {
String httpHeaderHmac = req.getHeader("CTBREC-HMAC");
String hmac = null;
String url = req.getRequestURI();
url = url.substring(getServletContext().getContextPath().length());
if(reqParamHmac != null) {
hmac = reqParamHmac;

View File

@ -32,7 +32,8 @@ public class HlsServlet extends AbstractCtbrecServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String request = req.getRequestURI().substring(5);
String contextPath = getServletContext().getContextPath();
String request = req.getRequestURI().substring(contextPath.length() + 5);
File recordingsDir = new File(config.getSettings().recordingsDir);
File requestedFile = new File(recordingsDir, request);

View File

@ -5,8 +5,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.BindException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@ -25,11 +27,12 @@ import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Credential;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -68,7 +71,7 @@ public class HttpServer {
logEnvironment();
createSites();
System.setProperty("ctbrec.server.mode", "1");
if(System.getProperty("ctbrec.config") == null) {
if (System.getProperty("ctbrec.config") == null) {
System.setProperty("ctbrec.config", "server.json");
}
try {
@ -83,12 +86,12 @@ public class HttpServer {
registerAlertSystem();
config = Config.getInstance();
if(config.getSettings().key != null) {
if (config.getSettings().key != null) {
LOG.info("HMAC authentication is enabled");
}
recorder = new NextGenLocalRecorder(config, sites);
for (Site site : sites) {
if(site.isEnabled()) {
if (site.isEnabled()) {
site.init();
}
}
@ -105,7 +108,7 @@ public class HttpServer {
sites.add(new Fc2Live());
sites.add(new Flirt4Free());
sites.add(new LiveJasmin());
//sites.add(new MyFreeCams());
// sites.add(new MyFreeCams());
sites.add(new Streamate());
}
@ -114,10 +117,10 @@ public class HttpServer {
@Override
public void run() {
LOG.info("Shutting down");
if(onlineMonitor != null) {
if (onlineMonitor != null) {
onlineMonitor.shutdown();
}
if(recorder != null) {
if (recorder != null) {
recorder.shutdown();
}
try {
@ -140,48 +143,62 @@ public class HttpServer {
HttpConfiguration config = new HttpConfiguration();
config.setSendServerVersion(false);
ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(config));
config.setSecurePort(this.config.getSettings().httpSecurePort);
config.setSecureScheme("https");
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(config);
SslContextFactory sslContextFactory = new SslContextFactory.Server();
URL keyStoreUrl = getClass().getResource("/keystore.pkcs12");
String keyStoreSrc = System.getProperty("keystore.file", keyStoreUrl.toExternalForm());
String keyStorePassword = System.getProperty("keystore.password", "ctbrecsucks");
sslContextFactory.setKeyStorePath(keyStoreSrc);
sslContextFactory.setKeyStorePassword(keyStorePassword);
sslContextFactory.setTrustStorePath(keyStoreSrc);
sslContextFactory.setTrustStorePassword(keyStorePassword);
// connector for http
ServerConnector http = new ServerConnector(server, httpConnectionFactory);
http.setPort(this.config.getSettings().httpPort);
http.setIdleTimeout(this.config.getSettings().httpTimeout);
server.addConnector(http);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/secured/*");
server.setHandler(context);
// connector for https (TLS)
ServerConnector https = new ServerConnector(server, sslContextFactory, httpConnectionFactory);
https.setPort(this.config.getSettings().httpSecurePort);
https.setIdleTimeout(this.config.getSettings().httpTimeout);
ServletHandler handler = new ServletHandler();
//server.setHandler(handler);
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] { context, handler });
server.setHandler(handlers);
String contextPath = Config.getInstance().getContextPath();
ServletContextHandler basicAuthContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
basicAuthContext.setContextPath(contextPath + "/secured");
ServletContextHandler defaultContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
defaultContext.setContextPath(contextPath);
RecorderServlet recorderServlet = new RecorderServlet(recorder, sites);
ServletHolder holder = new ServletHolder(recorderServlet);
handler.addServletWithMapping(holder, "/rec");
defaultContext.addServlet(holder, "/rec");
HlsServlet hlsServlet = new HlsServlet(this.config);
holder = new ServletHolder(hlsServlet);
handler.addServletWithMapping(holder, "/hls/*");
defaultContext.addServlet(holder, "/hls/*");
if (this.config.getSettings().webinterface) {
LOG.info("Register static file servlet under {}", context.getContextPath());
StaticFileServlet staticFileServlet = new StaticFileServlet("/html");
holder = new ServletHolder(staticFileServlet);
handler.addServletWithMapping(holder, "/static/*");
//context.addServlet(holder, "/");
String staticFileContext = "/static/*";
defaultContext.addServlet(holder, staticFileContext);
LOG.info("Register static file servlet under {}", defaultContext.getContextPath()+staticFileContext);
// servlet to retrieve the HMAC secured by basic auth
String username = this.config.getSettings().webinterfaceUsername;
String password = this.config.getSettings().webinterfacePassword;
context.setSecurityHandler(basicAuth(username, password, "CTB Recorder"));
context.addServlet(new ServletHolder(new HttpServlet() {
basicAuthContext.setSecurityHandler(basicAuth(username, password, "CTB Recorder"));
basicAuthContext.addServlet(new ServletHolder(new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(Objects.equal(username, req.getRemoteUser())) {
if (Objects.equal(username, req.getRemoteUser())) {
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("application/json");
byte[] hmac = HttpServer.this.config.getSettings().key;
byte[] hmac = Optional.ofNullable(HttpServer.this.config.getSettings().key).orElse(new byte[0]);
JSONObject response = new JSONObject();
response.put("hmac", new String(hmac, "utf-8"));
resp.getOutputStream().println(response.toString());
@ -190,12 +207,25 @@ public class HttpServer {
}), "/hmac");
}
server.addConnector(http);
HandlerList handlers = new HandlerList();
if (this.config.getSettings().transportLayerSecurity) {
server.addConnector(https);
handlers.setHandlers(new Handler[] { new SecuredRedirectHandler(), basicAuthContext, defaultContext });
} else {
handlers.setHandlers(new Handler[] { basicAuthContext, defaultContext });
}
server.setHandler(handlers);
try {
server.start();
server.join();
} catch (BindException e) {
LOG.error("Port {} is already in use", http.getPort(), e);
System.exit(1);
} catch (Exception e) {
LOG.error("Server start failed", e);
System.exit(1);
}
}
@ -238,7 +268,8 @@ public class HttpServer {
LOG.debug("Java:\t{} {} {}", System.getProperty("java.vendor"), System.getProperty("java.vm.name"), System.getProperty("java.version"));
try {
LOG.debug("ctbrec server {}", getVersion().toString());
} catch (IOException e) {}
} catch (IOException e) {
}
}
private Version getVersion() throws IOException {

View File

@ -11,27 +11,27 @@
<title>CTB Recorder</title>
<!-- Bootstrap core CSS -->
<link href="/static/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom fonts for this template -->
<link href="/static/vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic" rel="stylesheet" type="text/css">
<!-- Plugin CSS -->
<link href="/static/vendor/magnific-popup/magnific-popup.css" rel="stylesheet" type="text/css">
<link href="/static/vendor/jquery-ui/jquery-ui-1.12.1.css" rel="stylesheet" type="text/css">
<link href="vendor/magnific-popup/magnific-popup.css" rel="stylesheet" type="text/css">
<link href="vendor/jquery-ui/jquery-ui-1.12.1.css" rel="stylesheet" type="text/css">
<!-- Custom styles for this template -->
<link href="/static/freelancer.css" rel="stylesheet">
<link href="freelancer.css" rel="stylesheet">
<!-- Flowplayer -->
<link rel="stylesheet" href="/static/vendor/flowplayer/skin/skin.css">
<link rel="stylesheet" href="vendor/flowplayer/skin/skin.css">
<!-- custom css -->
<link rel="stylesheet" href="/static/custom.css">
<link rel="stylesheet" href="custom.css">
<link rel="shortcut icon" href="/static/favicon.png" type="image/x-icon" />
<link rel="shortcut icon" href="favicon.png" type="image/x-icon" />
<style>
.ui-front {
@ -45,7 +45,7 @@
<!-- Navigation -->
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav" style="padding-pottom: 3rem">
<div class="container">
<a class="navbar-brand js-scroll-trigger" href="/static/index.html"><img src="/static/icon64.png" alt="Logo" />CTBREC</a>
<a class="navbar-brand js-scroll-trigger" href="index.html"><img src="icon64.png" alt="Logo" />CTBREC</a>
<button class="navbar-toggler navbar-toggler-right text-uppercase bg-primary text-white rounded" type="button" data-toggle="collapse"
data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
Menu <i class="fa fa-bars"></i>
@ -183,22 +183,22 @@
</div>
<!-- Bootstrap core JavaScript -->
<script src="/static/vendor/jquery/jquery.min.js"></script>
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Plugin JavaScript -->
<script src="/static/vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
<script src="/static/vendor/jquery-easing/jquery.easing.min.js"></script>
<script src="/static/vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
<script src="/static/vendor/notify.js/notify.min.js"></script>
<script src="vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<script src="vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
<script src="vendor/notify.js/notify.min.js"></script>
<!-- knockout -->
<script src="/static/vendor/knockout/knockout-3.5.0.js"></script>
<script src="/static/vendor/knockout-orderable/knockout.bindings.orderable.js"></script>
<script src="vendor/knockout/knockout-3.5.0.js"></script>
<script src="vendor/knockout-orderable/knockout.bindings.orderable.js"></script>
<!-- Custom scripts for this template -->
<script src="/static/freelancer.min.js"></script>
<script src="freelancer.min.js"></script>
<div id="player-window" class="modal">
<div class="modal-content">
@ -209,10 +209,10 @@
</div>
<!-- HLS MediaSource support -->
<script src="/static/vendor/hls.js/hls.js"></script>
<script src="vendor/hls.js/hls.js"></script>
<!-- CryptoJS for HMAc authentication -->
<script src="/static/vendor/CryptoJS/hmac-sha256.js"></script>
<script src="vendor/CryptoJS/hmac-sha256.js"></script>
<script>
let onlineModels = [];
@ -248,7 +248,7 @@
let action = '{"action": "startByUrl", "model": ' + JSON.stringify(model) + '}';
$.ajax({
type : 'POST',
url : '/rec',
url : '../rec',
dataType : 'json',
async : true,
timeout : 60000,
@ -277,7 +277,7 @@
let action = '{"action": "resume", "model": ' + JSON.stringify(model) + '}';
$.ajax({
type : 'POST',
url : '/rec',
url : '../rec',
dataType : 'json',
async : true,
timeout : 60000,
@ -305,7 +305,7 @@
let action = '{"action": "suspend", "model": ' + JSON.stringify(model) + '}';
$.ajax({
type : 'POST',
url : '/rec',
url : '../rec',
dataType : 'json',
async : true,
timeout : 60000,
@ -333,7 +333,7 @@
let action = '{"action": "stop", "model": ' + JSON.stringify(model) + '}';
$.ajax({
type : 'POST',
url : '/rec',
url : '../rec',
dataType : 'json',
async : true,
timeout : 60000,
@ -363,7 +363,7 @@
let action = '{"action": "delete", "recording": ' + JSON.stringify(recording) + '}';
$.ajax({
type : 'POST',
url : '/rec',
url : '../rec',
dataType : 'json',
async : true,
timeout : 60000,
@ -395,9 +395,12 @@
hls.attachMedia(video);
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
let src = recording.playlist;
let hmacOfPath = CryptoJS.HmacSHA256(src, hmac);
src = '..' + src;
if (hmac.length > 0) {
src += "?hmac=" + CryptoJS.HmacSHA256(src, hmac);
src += "?hmac=" + hmacOfPath;
}
console.log(src);
hls.loadSource(src);
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
console.log("manifest loaded, found " + data.levels.length + " quality level");
@ -432,19 +435,18 @@
$(document).ready(function() {
if (localStorage !== undefined && localStorage.hmac !== undefined) {
console.log('using hmac from cookie');
console.log('using hmac from local storage');
hmac = localStorage.hmac;
} else {
console.log('hmac not found in local storage. requesting hmac from server');
$.ajax({
type : 'GET',
url : '/secured/hmac',
url : '../secured/hmac',
dataType : 'json',
async : true,
timeout : 60000
})
.done(function(data) {
console.log(data);
hmac = data.hmac;
if (localStorage !== undefined) {
console.log('saving hmac to local storage');
@ -479,7 +481,7 @@
let action = '{"action": "listOnline"}';
$.ajax({
type : 'POST',
url : '/rec',
url : '../rec',
dataType : 'json',
async : true,
timeout : 60000,
@ -590,7 +592,7 @@
let action = '{"action": "list"}';
$.ajax({
type : 'POST',
url : '/rec',
url : '../rec',
dataType : 'json',
async : true,
timeout : 60000,
@ -682,7 +684,7 @@
let action = '{"action": "recordings"}';
$.ajax({
type : 'POST',
url : '/rec',
url : '../rec',
dataType : 'json',
async : true,
timeout : 60000,
@ -710,7 +712,7 @@
let action = '{"action": "space"}';
$.ajax({
type : 'POST',
url : '/rec',
url : '../rec',
dataType : 'json',
async : true,
timeout : 60000,
@ -736,7 +738,7 @@
updateRecordings();
});
</script>
<script src="/static/modal.js"></script>
<script src="modal.js"></script>
</body>
</html>

Binary file not shown.