This commit is contained in:
2024-10-17 13:50:32 -04:00
parent e377ae64a6
commit 27be80e430
21 changed files with 1945 additions and 111 deletions

View File

@@ -1,33 +1,28 @@
package tech.nevets.tvpn;
import com.formdev.flatlaf.FlatDarkLaf;
import tech.nevets.tvpn.ui.UIManager;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(new FlatDarkLaf());
javax.swing.UIManager.setLookAndFeel(new FlatDarkLaf());
} catch (UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
if (args.length == 0) {
System.out.println("No args");
System.exit(0);
}
File f = new File(Utils.APP_DIR + "/temp/");
f.delete();
f.mkdirs();
switch (args[0]) {
case "load" -> WireGuardHandler.loadTunnel(Path.of("C:\\Users\\steven.v\\Desktop\\test.conf"));
case "delete" -> WireGuardHandler.deleteTunnel("test");
case "start" -> WireGuardHandler.startTunnel("test");
case "stop" -> WireGuardHandler.stopTunnel("test");
case "--help", "-h" -> {
System.out.println("Options:\n - load\n - delete\n - start\n - stop");
return;
}
}
UIManager uim = new UIManager();
uim.startUIFrame();
uim.startLoginFrame();
}
}

View File

@@ -1,36 +0,0 @@
/*
* Created by JFormDesigner on Tue Oct 08 20:59:17 EDT 2024
*/
package tech.nevets.tvpn;
import javax.swing.*;
import net.miginfocom.swing.*;
/**
* @author steven
*/
public class UI extends JPanel {
public UI() {
initComponents();
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
//======== this ========
setLayout(new MigLayout(
"hidemode 3",
// columns
"[fill]" +
"[fill]",
// rows
"[]" +
"[]" +
"[]"));
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
}

View File

@@ -1,17 +0,0 @@
JFDML JFormDesigner: "8.2.4.0.393" Java: "17.0.9" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
root: new FormRoot {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3"
"$columnConstraints": "[fill][fill]"
"$rowConstraints": "[][][]"
} ) {
name: "this"
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 400, 300 )
} )
}
}

View File

@@ -1,12 +1,57 @@
package tech.nevets.tvpn;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Map;
public class Utils {
public static final File APP_DIR = new File("./tvpn/");
public static final String WG_CONF_DIR = "C:\\Program Files\\WireGuard\\Data\\Configurations\\";
public static final String API_ENDPOINT_BASE = "http://tvpn.lan/";
static {
if (!APP_DIR.exists()) APP_DIR.mkdirs();
}
public static String get(URI url, Map<String, String> headers) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest.Builder reqBuilder = HttpRequest.newBuilder()
.GET()
.uri(url);
for (Map.Entry<String, String> header : headers.entrySet()) {
reqBuilder.header(header.getKey(), header.getValue());
}
HttpRequest req = reqBuilder.build();
HttpResponse<String> res;
try {
res = client.send(req, HttpResponse.BodyHandlers.ofString());
return res.body();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
return "";
}
}
public static String post(URI url, Map<String, String> headers, HttpRequest.BodyPublisher bodyPub) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest.Builder reqBuilder = HttpRequest.newBuilder()
.POST(bodyPub)
.uri(url);
for (Map.Entry<String, String> header : headers.entrySet()) {
reqBuilder.header(header.getKey(), header.getValue());
}
HttpRequest req = reqBuilder.build();
HttpResponse<String> res;
try {
res = client.send(req, HttpResponse.BodyHandlers.ofString());
return res.body();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
return "";
}
}
}

View File

@@ -1,39 +0,0 @@
package tech.nevets.tvpn;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static tech.nevets.tvpn.Utils.*;
public class WireGuardHandler {
public static void loadTunnel(Path tunnelConfig) throws IOException {
Files.copy(tunnelConfig, Path.of(WG_CONF_DIR + tunnelConfig.getFileName().toString()));
}
public static void deleteTunnel(String tunnelName) throws IOException {
File f = new File(WG_CONF_DIR + tunnelName + ".conf.dpapi");
f.delete();
}
public static void startTunnel(String tunnelName) throws IOException, InterruptedException {
Process proc = new ProcessBuilder()
.directory(APP_DIR)
.command("cmd", "/C", "wireguard.exe", "/installtunnelservice", WG_CONF_DIR + tunnelName + ".conf.dpapi")
.inheritIO()
.start();
System.out.println("Start Tunnel: " + proc.waitFor());
}
public static void stopTunnel(String tunnelName) throws IOException, InterruptedException {
Process proc = new ProcessBuilder()
.directory(APP_DIR)
.command("cmd", "/C", "wireguard.exe", "/uninstalltunnelservice", tunnelName)
.inheritIO()
.start();
System.out.println("Stop Tunnel: " + proc.waitFor());
}
}

View File

@@ -0,0 +1,72 @@
package tech.nevets.tvpn.auth;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import tech.nevets.tvpn.Utils;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpRequest;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class AuthManager {
public static UserToken serverAuth() {
CompletableFuture<String> cf = new CompletableFuture<>();
AuthServer as = new AuthServer(cf);
// TODO create new thread to handle login so the app doesn't hang while logging in
String response = cf.join();
String[] resSplit = response.split(":");
if (resSplit.length != 2) return new UserToken(false, "", 0, "Error");
UserToken userToken = new UserToken(true, resSplit[0], Long.parseLong(resSplit[1]), "");
try {
if (userToken.getToken().isEmpty()) return userToken;
Files.writeString(Path.of(Utils.APP_DIR + "/token.txt"), userToken.getToken());
} catch (IOException e) {
e.printStackTrace();
}
return userToken;
}
public static UserToken httpAuth(String username, String password) {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
String body = "{" +
"\"username\":\"" + username + "\"," +
"\"password\":\"" + password + "\"" +
"}";
String res = Utils.post(URI.create(Utils.API_ENDPOINT_BASE + "api/auth.php"), headers, HttpRequest.BodyPublishers.ofString(body));
JsonObject resObj = new Gson().fromJson(res, JsonObject.class);
UserToken token = new UserToken(resObj.get("isAuthenticated").getAsBoolean(), resObj.get("token").getAsString(), resObj.get("expires").getAsLong(), resObj.get("message").getAsString());
try {
if (token.getToken().isEmpty()) return token;
Files.writeString(Path.of(Utils.APP_DIR + "/token.txt"), token.getToken());
} catch (IOException e) {
e.printStackTrace();
}
return token;
}
public static UserToken checkAuth() {
try {
String token = Files.readString(Path.of(Utils.APP_DIR + "/token.txt"));
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + token);
String res = Utils.get(URI.create(Utils.API_ENDPOINT_BASE + "api/auth.php"), headers);
JsonObject resObj = new Gson().fromJson(res, JsonObject.class);
return new UserToken(resObj.get("isAuthenticated").getAsBoolean(), resObj.get("token").getAsString(), resObj.get("expires").getAsLong(), resObj.get("message").getAsString());
} catch (IOException e) {
System.out.println("No token file found");
return new UserToken(false, "", 0, e.getMessage());
}
}
}

View File

@@ -0,0 +1,76 @@
package tech.nevets.tvpn.auth;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class AuthServer {
private HttpServer server;
public AuthServer(CompletableFuture<String> cf) {
try {
server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 8342), 0);
server.createContext("/", new AuthHandler(cf));
server.setExecutor(null);
server.start();
System.out.println("Server is running on port 8342");
} catch (IOException e) {
e.printStackTrace();
}
}
public void stop() {
server.stop(0);
}
// define a custom HttpHandler
static class AuthHandler implements HttpHandler {
private CompletableFuture<String> cf;
public AuthHandler(CompletableFuture<String> cf) {
this.cf = cf;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
// handle the request
Headers headers = exchange.getRequestHeaders();
List<String> strHeaders = headers.get("Token");
String response;
int status;
String token = "";
if (exchange.getRequestMethod().equals("POST")) {
if (strHeaders == null || strHeaders.isEmpty()) {
response = "Error, not token found.";
status = 412;
} else {
response = "Successfully authenticated, you can now return to the app!";
status = 200;
token = strHeaders.getFirst();
}
} else {
response = "Invalid Request Method";
status = 405;
}
exchange.sendResponseHeaders(status, response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.flush();
os.close();
if (status == 200) {
cf.complete(token);
}
}
}
}

View File

@@ -0,0 +1,49 @@
package tech.nevets.tvpn.auth;
import com.google.gson.Gson;
import tech.nevets.tvpn.Utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class Session {
private final UserToken token;
public static Session loadSession() {
File f = new File(Utils.APP_DIR + "/session.json");
if (f.exists() && f.isFile()) {
Gson g = new Gson();
try {
BufferedReader br = Files.newBufferedReader(Path.of(Utils.APP_DIR + "/session.json"));
return g.fromJson(br, Session.class);
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
public Session(UserToken token) {
this.token = token;
saveSession();
}
public UserToken getToken() {
return this.token;
}
public void invalidateSession() {
this.token.invalidate();
}
public void saveSession() {
try {
Files.writeString(Path.of(Utils.APP_DIR + "/session.json"), new Gson().toJson(token));
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,69 @@
package tech.nevets.tvpn.auth;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import tech.nevets.tvpn.Utils;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import static tech.nevets.tvpn.Utils.API_ENDPOINT_BASE;
public class UserToken {
private boolean isAuthenticated;
private final String token;
private final long expires;
private final String message;
private String username;
private String email;
public UserToken(boolean isAuthenticated, String token, long expires, String message) {
this.isAuthenticated = isAuthenticated;
this.token = token;
this.expires = expires;
this.message = message;
getUserInfo(token);
}
private void getUserInfo(String userToken) {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + userToken);
String response = Utils.get(URI.create(API_ENDPOINT_BASE + "api/user-info.php"), headers);
JsonObject resObj = new Gson().fromJson(response, JsonObject.class);
this.username = resObj.get("username").getAsString();
this.email = resObj.get("email").getAsString();
}
public boolean isAuthenticated() {
return this.isAuthenticated;
}
public void invalidate() {
this.isAuthenticated = false;
}
public void checkExpiry() {
if (System.currentTimeMillis() > expires) isAuthenticated = false;
}
public String getToken() {
return this.token;
}
public String getMessage() {
return this.message;
}
public String getUsername() {
return this.username;
}
public String getEmail() {
return this.email;
}
}

View File

@@ -0,0 +1,161 @@
/*
* Created by JFormDesigner on Wed Oct 09 08:18:13 EDT 2024
*/
package tech.nevets.tvpn.ui;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import net.miginfocom.swing.*;
import tech.nevets.tvpn.auth.AuthManager;
import tech.nevets.tvpn.auth.UserToken;
/**
* @author steven
*/
public class LoginPanel extends JPanel {
private final UIManager uim;
public LoginPanel(UIManager uim) {
this.uim = uim;
SwingWorker<UserToken, Void> worker = new SwingWorker<>() {
@Override
protected UserToken doInBackground() throws Exception {
statusLabel.setText("Attempting login with stored token...");
return AuthManager.checkAuth();
}
@Override
protected void done() {
try {
UserToken token = get();
// Update the UI on the Event Dispatch Thread
if (token.isAuthenticated()) {
uim.getUIPanel().updateUserInfo(token.getUsername(), token.getEmail());
uim.disposeLoginFrame();
} else {
statusLabel.setText("Unable to authenticate with stored token, please login");
}
} catch (Exception ex) {
ex.printStackTrace();
statusLabel.setText("An error occurred during login.");
}
}
};
initComponents();
worker.execute();
}
private void loginBtn(ActionEvent e) {
// Immediately update the statusLabel on the UI thread
this.statusLabel.setText("Logging in...");
// Use SwingWorker to handle the authentication process in the background
SwingWorker<UserToken, Void> worker = new SwingWorker<>() {
@Override
protected UserToken doInBackground() {
// This is done in the background thread, so the UI won't be blocked
return AuthManager.httpAuth(usernameField.getText(), new String(passwordField.getPassword()));
}
@Override
protected void done() {
try {
// Get the result of doInBackground
UserToken token = get();
// Update the UI on the Event Dispatch Thread
if (!token.isAuthenticated()) {
statusLabel.setText(token.getMessage());
passwordField.setText("");
return;
}
uim.getUIPanel().updateUserInfo(token.getUsername(), token.getEmail());
uim.disposeLoginFrame();
} catch (Exception ex) {
ex.printStackTrace();
statusLabel.setText("An error occurred during login.");
}
}
};
// Start the background task
worker.execute();
}
private void enterKeyTyped(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_ENTER) {
loginBtn(new ActionEvent(e.getSource(), e.getID(), "Login"));
}
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
usernameLabel = new JLabel();
usernameField = new JTextField();
passwordLabel = new JLabel();
passwordField = new JPasswordField();
loginBtn = new JButton();
statusLabel = new JLabel();
//======== this ========
setLayout(new MigLayout(
"hidemode 3",
// columns
"[fill]" +
"[grow,fill]",
// rows
"[grow]" +
"[shrink 0,top]" +
"[shrink 0]" +
"[grow,top]" +
"[]"));
//---- usernameLabel ----
usernameLabel.setText("Username");
add(usernameLabel, "cell 0 1");
//---- usernameField ----
usernameField.setMaximumSize(new Dimension(300, 2147483647));
add(usernameField, "cell 1 1,growx");
//---- passwordLabel ----
passwordLabel.setText("Password");
add(passwordLabel, "cell 0 2");
//---- passwordField ----
passwordField.setMaximumSize(new Dimension(300, 2147483647));
passwordField.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
enterKeyTyped(e);
}
});
add(passwordField, "cell 1 2,growx");
//---- loginBtn ----
loginBtn.setText("Login");
loginBtn.setMaximumSize(new Dimension(200, 22));
loginBtn.setMinimumSize(new Dimension(100, 22));
loginBtn.addActionListener(e -> loginBtn(e));
add(loginBtn, "cell 0 3 2 1,alignx center,growx 0");
add(statusLabel, "cell 0 4 2 1");
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
private JLabel usernameLabel;
private JTextField usernameField;
private JLabel passwordLabel;
private JPasswordField passwordField;
private JButton loginBtn;
private JLabel statusLabel;
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
}

View File

@@ -0,0 +1,56 @@
JFDML JFormDesigner: "8.2.4.0.393" Java: "17.0.9" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
root: new FormRoot {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3"
"$columnConstraints": "[fill][grow,fill]"
"$rowConstraints": "[grow][shrink 0,top][shrink 0][grow,top][]"
} ) {
name: "this"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "usernameLabel"
"text": "Username"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1"
} )
add( new FormComponent( "javax.swing.JTextField" ) {
name: "usernameField"
"maximumSize": new java.awt.Dimension( 300, 2147483647 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 1,growx"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "passwordLabel"
"text": "Password"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2"
} )
add( new FormComponent( "javax.swing.JPasswordField" ) {
name: "passwordField"
"maximumSize": new java.awt.Dimension( 300, 2147483647 )
addEvent( new FormEvent( "java.awt.event.KeyListener", "keyTyped", "enterKeyTyped", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 2,growx"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "loginBtn"
"text": "Login"
"maximumSize": new java.awt.Dimension( 200, 22 )
"minimumSize": new java.awt.Dimension( 100, 22 )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "loginBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3 2 1,alignx center,growx 0"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "statusLabel"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 4 2 1"
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 400, 300 )
} )
}
}

View File

@@ -0,0 +1,239 @@
/*
* Created by JFormDesigner on Tue Oct 08 20:59:17 EDT 2024
*/
package tech.nevets.tvpn.ui;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import net.miginfocom.swing.*;
import tech.nevets.tvpn.Utils;
import tech.nevets.tvpn.wg.WireGuardHandler;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
/**
* @author steven
*/
public class UI extends JPanel {
private final UIManager uim;
public UI(UIManager uim) {
initComponents();
createTunnelsUI(WireGuardHandler.getTunnelConfigs());
this.uim = uim;
updateActiveTunnels();
}
public void createTunnelsUI(List<String> tunnels) {
// Clear the list model first in case we're refreshing
listModel.clear();
// Add each tunnel string to the list model, which updates the JList
for (String s : tunnels) {
listModel.addElement("" + s.substring(0, s.length() - 11));
}
}
private void updateActiveTunnels() {
String tunnelName;
if (WireGuardHandler.isActiveTunnel()) {
tunnelName = WireGuardHandler.getActiveTunnels().getFirst();
} else {
tunnelName = "";
}
for (int i = 0; i < listModel.size(); i++) {
String s = listModel.get(i).replace("", "").replace("", "").trim();
String newStr;
if (s.equals(tunnelName)) {
newStr = "" + s;
} else {
newStr = "" + s;
}
listModel.set(i, newStr);
}
tunnelList.updateUI();
}
private String getSelectedTunnel() {
return tunnelList.getSelectedValue().replace("", "").replace("", "").trim();
}
private void loadTunnel(String tunnelName) {
List<String> tunnels = WireGuardHandler.getActiveTunnels();
if (tunnels.size() > 1) { //TODO implement this logic
System.out.println("Too many tunnels active, logic not implemented yet.");
return;
}
if (WireGuardHandler.isActiveTunnel()) {
String tunnel = tunnels.getFirst();
WireGuardHandler.stopTunnel(tunnel);
}
WireGuardHandler.startTunnel(tunnelName);
updateActiveTunnels();
}
public void updateUserInfo(String username, String email) {
userLabel.setText(username);
emailLabel.setText(email);
}
private void loadBtn(ActionEvent e) {
loadTunnel(getSelectedTunnel());
}
private void showConfigBtn(ActionEvent e) {
List<String> activeTunnels = WireGuardHandler.getActiveTunnels();
if (activeTunnels.size() > 1) { //TODO implement this logic
System.out.println("Too many tunnels active, logic not implemented yet.");
return;
}
String originalTunnel = activeTunnels.getFirst();
if (originalTunnel.equals(getSelectedTunnel())) {
try {
String tunnelConfig = Files.readString(Path.of(Utils.APP_DIR + "/" + getSelectedTunnel() + ".conf"));
configArea.setText(tunnelConfig);
} catch (IOException ex) {
ex.printStackTrace();
}
return;
}
loadTunnel(getSelectedTunnel());
WireGuardHandler.getActiveConfig(getSelectedTunnel());
try {
String tunnelConfig = Files.readString(Path.of(Utils.APP_DIR + "/" + getSelectedTunnel() + ".conf"));
configArea.setText(tunnelConfig);
} catch (IOException ex) {
ex.printStackTrace();
}
loadTunnel(originalTunnel);
}
private void stopTunnelBtn(ActionEvent e) {
List<String> tunnels = WireGuardHandler.getActiveTunnels();
for (String tunnel : tunnels) {
WireGuardHandler.stopTunnel(tunnel);
}
updateActiveTunnels();
}
private void importBtn(ActionEvent e) {
JFileChooser fileChooser = new JFileChooser(System.getenv("HOME"));
fileChooser.setFileFilter(new FileNameExtensionFilter("WireGuard Config", "conf"));
fileChooser.showOpenDialog(null);
File f = fileChooser.getSelectedFile();
if (f == null) return;
try {
WireGuardHandler.loadTunnel(Path.of(f.getAbsolutePath()));
} catch (IOException ex) {
ex.printStackTrace();
}
listModel.addElement("" + f.getName().replace(".conf", ""));
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
tunnelsLabel = new JLabel();
userLabel = new JLabel();
tunnelScrollPane = new JScrollPane();
listModel = new DefaultListModel<>();
tunnelList = new JList<>(listModel);
emailLabel = new JLabel();
configScrollPane = new JScrollPane();
configArea = new JTextArea();
showConfigBtn = new JButton();
importBtn = new JButton();
startTunnelBtn = new JButton();
stopTunnelBtn = new JButton();
//======== this ========
setLayout(new MigLayout(
"hidemode 3",
// columns
"[fill]" +
"[fill]" +
"[grow,fill]" +
"[fill]",
// rows
"[]" +
"[]" +
"[]" +
"[]" +
"[]" +
"[]" +
"[]" +
"[]" +
"[]" +
"[]" +
"[]" +
"[]"));
//---- tunnelsLabel ----
tunnelsLabel.setText("Tunnels");
add(tunnelsLabel, "cell 0 0 2 1");
//---- userLabel ----
userLabel.setText("Guest");
add(userLabel, "cell 3 0");
//======== tunnelScrollPane ========
{
tunnelScrollPane.setViewportView(tunnelList);
}
add(tunnelScrollPane, "cell 0 1 2 8,growy");
//---- emailLabel ----
emailLabel.setText("guest@tvpn.lan");
add(emailLabel, "cell 3 1");
//======== configScrollPane ========
{
configScrollPane.setViewportView(configArea);
}
add(configScrollPane, "cell 2 3 2 6,growy");
//---- showConfigBtn ----
showConfigBtn.setText("Show Config");
showConfigBtn.addActionListener(e -> showConfigBtn(e));
add(showConfigBtn, "cell 0 9 2 1");
//---- importBtn ----
importBtn.setText("Import Tunnel");
importBtn.addActionListener(e -> importBtn(e));
add(importBtn, "cell 3 9");
//---- startTunnelBtn ----
startTunnelBtn.setText("Start Tunnel");
startTunnelBtn.addActionListener(e -> loadBtn(e));
add(startTunnelBtn, "cell 0 10 2 1");
//---- stopTunnelBtn ----
stopTunnelBtn.setText("Stop Tunnel");
stopTunnelBtn.addActionListener(e -> stopTunnelBtn(e));
add(stopTunnelBtn, "cell 0 11 2 1");
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
private JLabel tunnelsLabel;
private JLabel userLabel;
private JScrollPane tunnelScrollPane;
private JList<String> tunnelList;
private JLabel emailLabel;
private JScrollPane configScrollPane;
private JTextArea configArea;
private JButton showConfigBtn;
private JButton importBtn;
private JButton startTunnelBtn;
private JButton stopTunnelBtn;
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
private DefaultListModel<String> listModel;
}

View File

@@ -0,0 +1,84 @@
JFDML JFormDesigner: "8.2.4.0.393" Java: "17.0.9" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
root: new FormRoot {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3"
"$columnConstraints": "[fill][fill][grow,fill][fill]"
"$rowConstraints": "[][][][][][][][][][][][]"
} ) {
name: "this"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "tunnelsLabel"
"text": "Tunnels"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0 2 1"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "userLabel"
"text": "Guest"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 3 0"
} )
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "tunnelScrollPane"
add( new FormComponent( "javax.swing.JList" ) {
name: "tunnelList"
auxiliary() {
"JavaCodeGenerator.preCreateCode": "listModel = new DefaultListModel<>();"
"JavaCodeGenerator.typeParameters": "String"
"JavaCodeGenerator.customCreateCode": "new JList<>(listModel);"
}
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1 2 8,growy"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "emailLabel"
"text": "guest@tvpn.lan"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 3 1"
} )
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "configScrollPane"
add( new FormComponent( "javax.swing.JTextArea" ) {
name: "configArea"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 3 2 6,growy"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "showConfigBtn"
"text": "Show Config"
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showConfigBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 9 2 1"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "importBtn"
"text": "Import Tunnel"
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "importBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 3 9"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "startTunnelBtn"
"text": "Start Tunnel"
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "loadBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 10 2 1"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "stopTunnelBtn"
"text": "Stop Tunnel"
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "stopTunnelBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 11 2 1"
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 400, 300 )
} )
}
}

View File

@@ -0,0 +1,59 @@
package tech.nevets.tvpn.ui;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
public class UIManager {
private List<JPanel> panels = new ArrayList<>();
private JFrame loginFrame;
private JFrame uiFrame;
private UI uiPanel;
public void startLoginFrame() {
loginFrame = new JFrame();
JPanel p = new LoginPanel(this);
loginFrame.add(p);
panels.add(p);
loginFrame.pack();
loginFrame.setTitle("Login");
loginFrame.setSize(340, 170);
loginFrame.setResizable(false);
loginFrame.setLocationRelativeTo(null);
loginFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
loginFrame.setVisible(true);
}
protected void disposeLoginFrame() {
loginFrame.dispose();
}
public void startUIFrame() {
uiFrame = new JFrame();
uiPanel = new UI(this);
uiFrame.add(uiPanel);
panels.add(uiPanel);
uiFrame.pack();
uiFrame.setTitle("TVPN");
uiFrame.setSize(720, 480);
uiFrame.setLocationRelativeTo(null);
uiFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
uiFrame.setVisible(true);
}
protected UI getUIPanel() {
return uiPanel;
}
public void startFileChooserFrame(JFileChooser fileChooser) {
JFrame f = new JFrame();
f.add(fileChooser);
f.pack();
f.setTitle("Choose WireGuard Config File");
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setVisible(true);
}
}

View File

@@ -0,0 +1,79 @@
package tech.nevets.tvpn.wg;
import tech.nevets.tvpn.Utils;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
public class WGTesting {
public static void main(String[] args) throws IOException, InterruptedException {
List<String> configs = listConfigs();
//String currentTunnel = getCurrentTunnel();
//System.out.println(currentTunnel);
//pullTunnelConfig(currentTunnel);
//WireGuardHandler.stopTunnel(currentTunnel);
Random r = ThreadLocalRandom.current();
int index = r.nextInt(configs.size());
System.out.println("Index: " + index);
System.out.println("Random Tunnel: " + configs.get(index).substring(0, configs.get(index).length() - 11));
//WireGuardHandler.startTunnel(configs.get(index).substring(0, configs.get(index).length() - 11));
}
public static String getCurrentTunnel() throws IOException {
String[] rSplit = getWGShowLines();
for (String s : rSplit) {
if (s.toLowerCase().contains("interface")) {
return s.split(":")[1].trim();
}
}
return "";
}
public static List<String> getCurrentTunnels() throws IOException {
String[] rSplit = getWGShowLines();
List<String> tunnels = new ArrayList<>();
for (String s : rSplit) {
if (s.toLowerCase().contains("interface")) {
tunnels.add(s.split(":")[1].trim());
}
}
return tunnels;
}
public static String[] getWGShowLines() throws IOException {
ProcessBuilder pb = new ProcessBuilder();
pb.command("cmd", "/C", "wg.exe", "show");
Process p = pb.start();
String result = new String(p.getInputStream().readAllBytes());
return result.split("\n");
}
public static void pullTunnelConfig(String tunnelName) throws IOException {
ProcessBuilder pb = new ProcessBuilder();
pb.redirectOutput(new File(Utils.APP_DIR + "/" + tunnelName + ".conf"));
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.command("cmd", "/C", "wg.exe", "showconf", tunnelName);
pb.start();
}
public static List<String> listConfigs() throws IOException {
List<Path> filePaths = new ArrayList<>();
Path path = Path.of(Utils.WG_CONF_DIR);
Files.walk(path)
.filter(Files::isRegularFile)
.forEach(filePaths::add);
List<String> fileNames = new ArrayList<>();
for(Path p : filePaths) {
String fullPath = p.toString();
fileNames.add(fullPath.substring(fullPath.lastIndexOf("\\") + 1));
}
return fileNames;
}
}

View File

@@ -0,0 +1,144 @@
package tech.nevets.tvpn.wg;
import tech.nevets.tvpn.Utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import static tech.nevets.tvpn.Utils.*;
public class WireGuardHandler {
public static void loadTunnel(Path tunnelConfig) throws IOException {
Files.copy(tunnelConfig, Path.of(WG_CONF_DIR + tunnelConfig.getFileName().toString()));
}
public static boolean deleteTunnel(String tunnelName) {
File f = new File(WG_CONF_DIR + tunnelName + ".conf.dpapi");
return f.delete();
}
public static void startTunnel(String tunnelName) {
try {
Process proc = new ProcessBuilder()
.directory(APP_DIR)
.command("cmd", "/C", "wireguard.exe", "/installtunnelservice", WG_CONF_DIR + tunnelName + ".conf.dpapi")
.inheritIO()
.start();
System.out.println("Start Tunnel: " + proc.waitFor());
if (getWGShow() == null) return;
for (float f = 0; getWGShow().isEmpty(); f += 0.5f) {
//while (getWGShow().isEmpty()) {
if (f >= 12) {
System.out.println("Start tunnel timed out");
return;
}
System.out.println("Waiting for wg start... " + f + "s");
Thread.sleep(500);
}
System.out.println("Tunnel Started");
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
public static void stopTunnel(String tunnelName) {
try {
Process proc = new ProcessBuilder()
.directory(APP_DIR)
.command("cmd", "/C", "wireguard.exe", "/uninstalltunnelservice", tunnelName)
.inheritIO()
.start();
System.out.println("Stop Tunnel: " + proc.waitFor());
if (getWGShow() == null) return;
for (float f = 0; !getWGShow().isEmpty(); f += 0.5f) {
// while (!getWGShow().isEmpty()) {
if (f >= 12) {
System.out.println("Stop tunnel timed out");
return;
}
System.out.println("Waiting for wg stop... " + f + "s");
Thread.sleep(500);
}
System.out.println("Tunnel Stopped");
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
public static List<String> getTunnelConfigs() {
List<Path> filePaths = new ArrayList<>();
Path path = Path.of(Utils.WG_CONF_DIR);
try (Stream<Path> ps = Files.walk(path)) {
ps.filter(Files::isRegularFile)
.forEach(filePaths::add);
List<String> fileNames = new ArrayList<>();
for(Path p : filePaths) {
String fullPath = p.toString();
fileNames.add(fullPath.substring(fullPath.lastIndexOf("\\") + 1));
}
return fileNames;
} catch (IOException e) {
e.printStackTrace();
return new ArrayList<>();
}
}
public static List<String> getActiveTunnels() {
String[] rSplit = getWGShowLines();
List<String> tunnels = new ArrayList<>();
for (String s : rSplit) {
if (s.toLowerCase().contains("interface")) {
tunnels.add(s.split(":")[1].trim());
}
}
return tunnels;
}
private static String getWGShow() {
ProcessBuilder pb = new ProcessBuilder();
pb.command("cmd", "/C", "wg.exe", "show");
try {
Process p = pb.start();
return new String(p.getInputStream().readAllBytes());
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private static String[] getWGShowLines() {
String result = getWGShow();
if (result == null) {
return new String[]{};
}
return result.split("\n");
}
public static void getActiveConfig(String tunnelName) {
ProcessBuilder pb = new ProcessBuilder();
System.out.println("Tunnel Name: " + tunnelName);
String outFile = Utils.APP_DIR + "/" + tunnelName + ".conf";
System.out.println("Out File: " + outFile);
pb.redirectOutput(new File(outFile));
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.command("cmd", "/C", "wg.exe", "showconf", tunnelName);
try {
Process p = pb.start();
p.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
public static boolean isActiveTunnel() {
getActiveTunnels().forEach(System.out::println);
return !getActiveTunnels().isEmpty();
}
}