diff --git a/api/routes/NextNet API/Backend/Lookup.bru b/api/routes/NextNet API/Backend/Lookup.bru index 410e47e..47ef0a1 100644 --- a/api/routes/NextNet API/Backend/Lookup.bru +++ b/api/routes/NextNet API/Backend/Lookup.bru @@ -5,14 +5,13 @@ meta { } post { - url: http://127.0.0.1:3000/api/v1/backends/remove + url: http://127.0.0.1:3000/api/v1/backends/lookup body: json auth: none } body:json { { - "token": "f1b89cc337073476289ade17ffbe7a6419b4bd52aa7ede26114bffd76fa263b5cb1bcaf389462e1d9e7acb7f4b6a7c28152a9cc9af83e3ec862f1892b1", - "id": "2" + "token": "7d69814cdada551dd22521ad97b23b22a106278826a2b4e87dd76246594b56f973894e8265437a5d520ed7258d7c856d0d294e89b1de1a98db7fa4a" } } diff --git a/api/src/backendimpl/index.ts b/api/src/backendimpl/index.ts index 98f6e28..2c022e2 100644 --- a/api/src/backendimpl/index.ts +++ b/api/src/backendimpl/index.ts @@ -1,4 +1,4 @@ -import type { BackendBaseClass } from "./base.js"; +import { BackendBaseClass } from "./base.js"; import { PassyFireBackendProvider } from "./passyfire-reimpl/index.js"; import { SSHBackendProvider } from "./ssh.js"; @@ -7,3 +7,7 @@ export const backendProviders: Record = { ssh: SSHBackendProvider, passyfire: PassyFireBackendProvider, }; + +if (process.env.NODE_ENV != "production") { + backendProviders["dummy"] = BackendBaseClass; +} \ No newline at end of file diff --git a/api/src/backendimpl/ssh.ts b/api/src/backendimpl/ssh.ts index 5541d16..b20eba2 100644 --- a/api/src/backendimpl/ssh.ts +++ b/api/src/backendimpl/ssh.ts @@ -179,7 +179,7 @@ export class SSHBackendProvider implements BackendBaseClass { srcConn.write(chunk); }); - destConn.addListener("close", () => { + destConn.addListener("end", () => { this.clients.splice(this.clients.indexOf(client), 1); srcConn.end(); }); @@ -190,7 +190,7 @@ export class SSHBackendProvider implements BackendBaseClass { srcConn.on("end", () => { this.clients.splice(this.clients.indexOf(client), 1); - destConn.close(); + destConn.end(); }); }, ); diff --git a/api/src/routes/forward/connections.ts b/api/src/routes/forward/connections.ts index 4c73a57..34e5862 100644 --- a/api/src/routes/forward/connections.ts +++ b/api/src/routes/forward/connections.ts @@ -45,19 +45,23 @@ export function route(routeOptions: RouteOptions) { }, }); - if (!forward) + if (!forward) { return res.status(400).send({ error: "Could not find forward entry", }); + } - if (!backends[forward.destProviderID]) + if (!backends[forward.destProviderID]) { return res.status(400).send({ error: "Backend not found", }); + } return { success: true, - data: backends[forward.destProviderID].getAllConnections(), + data: backends[forward.destProviderID].getAllConnections().filter((i) => { + return i.connectionDetails.sourceIP == forward.sourceIP && i.connectionDetails.sourcePort == forward.sourcePort && i.connectionDetails.destPort == forward.destPort; + }), }; }, ); diff --git a/lom/src/commands.ts b/lom/src/commands.ts index 52663ef..41cc586 100644 --- a/lom/src/commands.ts +++ b/lom/src/commands.ts @@ -4,7 +4,7 @@ import { run as connection } from "./commands/connections.js"; import { run as backends } from "./commands/backends.js"; import { run as users } from "./commands/users.js"; -export type PrintLine = (...str: string[]) => void; +export type PrintLine = (...str: any[]) => void; type Command = ( args: string[], diff --git a/lom/src/commands/connections.ts b/lom/src/commands/connections.ts index da87512..db6c198 100644 --- a/lom/src/commands/connections.ts +++ b/lom/src/commands/connections.ts @@ -3,12 +3,25 @@ import type { Axios } from "axios"; import { SSHCommand } from "../libs/patchCommander.js"; import type { PrintLine } from "../commands.js"; +// https://stackoverflow.com/questions/37938504/what-is-the-best-way-to-find-all-items-are-deleted-inserted-from-original-arra +function difference(a: any[], b: any[]) { + return a.filter(x => b.indexOf(x) < 0); +}; + export async function run( argv: string[], println: PrintLine, axios: Axios, - apiKey: string, + token: string, ) { + let resolve: (value: unknown) => void; + let reject: (value: unknown) => void; + + const promise = new Promise((ourResolve, ourReject) => { + resolve = ourResolve; + reject = ourReject; + }); + const program = new SSHCommand(println); program.description("Manages connections for NextNet"); program.version("v0.1.0-preprod"); @@ -76,6 +89,102 @@ export async function run( getInbound.argument("", "Tunnel ID to view inbound connections of"); getInbound.option("-t, --tail", "Live-view of connection list"); + getInbound.action(async(idStr: string, options: { + tail?: boolean + }): Promise => { + type InboundConnectionSuccess = { + success: true, + data: { + ip: string, + port: number, + + connectionDetails: { + sourceIP: string, + sourcePort: number, + destPort: number, + enabled: boolean + } + }[] + }; + + const id = parseInt(idStr); + + if (Number.isNaN(id)) { + println("ID (%s) is not a number\n", idStr); + return; + } + + if (options.tail) { + let previousEntries: string[] = []; + + while (true) { + const response = await axios.post("/api/v1/forward/connections", { + token, + id + }); + + if (response.status != 200) { + if (process.env.NODE_ENV != "production") console.log(response); + + if (response.data.error) { + println(`Error: ${response.data.error}\n`); + } else { + println("Error requesting connections!\n"); + } + + return resolve(null); + } + + const { data }: InboundConnectionSuccess = response.data; + const simplifiedArray: string[] = data.map((i) => `${i.ip}:${i.port}`); + + const insertedItems: string[] = difference(simplifiedArray, previousEntries); + const removedItems: string[] = difference(previousEntries, simplifiedArray); + + insertedItems.forEach((i) => println("CONNECTED: %s\n", i)); + removedItems.forEach((i) => println("DISCONNECTED: %s\n", i)); + + previousEntries = simplifiedArray; + + await new Promise((i) => setTimeout(i, 2000)); + } + } else { + const response = await axios.post("/api/v1/forward/connections", { + token, + id + }); + + if (response.status != 200) { + if (process.env.NODE_ENV != "production") console.log(response); + + if (response.data.error) { + println(`Error: ${response.data.error}\n`); + } else { + println("Error requesting connections!\n"); + } + + return resolve(null); + } + + const { data }: InboundConnectionSuccess = response.data; + + if (data.length == 0) { + println("There are currently no connected clients.\n"); + return resolve(null); + } + + println("Connected clients (for source: %s:%s):\n", data[0].connectionDetails.sourceIP, data[0].connectionDetails.sourcePort); + + for (const entry of data) { + println(" - %s:%s\n", entry.ip, entry.port); + } + + console.log(response.data); + } + + return resolve(null); + }); + const removeTunnel = new SSHCommand(println, "rm"); removeTunnel.description("Removes a tunnel"); removeTunnel.argument("", "Tunnel ID to remove"); @@ -88,4 +197,7 @@ export async function run( program.addCommand(removeTunnel); program.parse(argv); + + if (program.hasRecievedExitSignal) return; + await promise; } diff --git a/lom/src/index.ts b/lom/src/index.ts index b3a72ea..2ea5e13 100644 --- a/lom/src/index.ts +++ b/lom/src/index.ts @@ -80,8 +80,8 @@ server.on("connection", client => { "Welcome to NextNet LOM. Run 'help' to see commands.\r\n\r\n~$ ", ); - function println(...str: string[]) { - stream.write(format(...str).replaceAll("\n", "\r\n")); + function println(...data: any[]) { + stream.write(format(...data).replaceAll("\n", "\r\n")); }; while (true) { diff --git a/lom/src/libs/patchCommander.ts b/lom/src/libs/patchCommander.ts index bbd7e4b..12f909d 100644 --- a/lom/src/libs/patchCommander.ts +++ b/lom/src/libs/patchCommander.ts @@ -35,7 +35,7 @@ export class SSHCommand extends Command { if (process.env.NODE_ENV != "production") { println( - "Caught irrecoverable action (command help call) in patchCommander\n", + "Caught irrecoverable crash (command help call) in patchCommander\n", ); } else { println("Aborted\n"); @@ -46,12 +46,22 @@ export class SSHCommand extends Command { } } - _exit() { + recvExitDispatch() { this.hasRecievedExitSignal = true; + let parentElement = this.parent; + + while (parentElement instanceof SSHCommand) { + parentElement.hasRecievedExitSignal = true; + parentElement = parentElement.parent; + }; + } + + _exit() { + this.recvExitDispatch(); } _exitCallback() { - this.hasRecievedExitSignal = true; + this.recvExitDispatch(); } action(fn: (...args: any[]) => void | Promise): this { @@ -63,7 +73,7 @@ export class SSHCommand extends Command { // @ts-ignore this._actionHandler = async (...args: any[]): Promise => { - if (args[0][0] == "--help" || args[0][0] == "-h") return; + if (this.hasRecievedExitSignal) return; await oldActionHandler(...args); };