import { Machine, MultiPortMachineInit, SinglePortMachineInit } from "../Independents/Machine.js" import { HostNameTemplate, HostName } from "../Independents/DNS/HostName.js" import { ResponseInit, Request, Response, RequestInit } from "../Independents/Datagram/datagram-types.js" import { CHostOwnershipRecord, Host } from "./Host.js" import { Vagrant } from "./Host.js"; import { Gateway, SpawningError } from "./Gateway.js" import { Resolve } from "../Independents/DNS/Resolve.js"; import { msToTime, processType } from "../Independents/util/misc.js"; import { CRequest, DatagramShort } from "../Independents/Datagram/datagram.js"; import { CBurpastateWrapper, TBurpaState } from "./burpastate.js" import { GatewayProxy } from "./GatewayProxy.js"; import { d } from "functional"; export class HostMachine extends Machine { public state: CBurpastateWrapper; constructor(state: CBurpastateWrapper, burpasysPorts: string[]) { let handler = { onRequest: async (request: Request): Promise => { if (request.method == "POST") { switch (request.path) { // case "/spawn-gateway-and-host": // return processSpawnGatewayAndHost(state, request); case "/children": request = { ...request, body: (typeof (request.body) == "string") ? JSON.parse(request.body) : request.body }; //console.log("RECEIVED SPAWN ALIAS POST REQUEST", request); let ret = this.processSpawnAliasRequest(state.whole, request); //console.log("PROCESSED SPAWN ALIAS", ret); state.save(); return ret; // case "/resolve": // let init = typeof (request.body == "string") ? JSON.parse(request.body) : request.body; // console.log("ASKED TO RESOLVE", init); // let unresolved = new Resolve(init.host, init.dns, init.type, null, init.parent, init.gateway); // let resolved = unresolved.resolveRoot(new HostName(state.whole.persistent.localHost ?? ""), new HostName(state.whole.persistent.localGateway)); //.resolveRoot(); // let resp = resolved ? { // status: 200, // body: resolved // } : { // status: 502, // body: `Cannot further resolve ${unresolved.host}` // } // return resp; } if (request.path.startsWith("/empty-inbox/")) { let box = state.hostName(request.path.substr("/empty-inbox/".length)); let host = state.getHost(request.to); let rec = host.getOwnershipRecord(box.name); //console.error(`Emptying inbox for ${box.name} at ${host.hostId}.`, rec?.self.inbox); rec?.emptyInbox(); return { status: 200 }; } } else if (request.method == "PUT") { if (request.path.startsWith("/children/")) { request = { ...request, body: (typeof (request.body) == "string") ? JSON.parse(request.body) : request.body }; //console.log("RECEIVED SPAWN ALIAS PUT REQUEST", request); request.body.template = request.path.substr("/children/".length); let ret = this.processSpawnAliasRequest(state.whole, request); //console.log("PROCESSED SPAWN ALIAS", ret); return ret; } else if (request.path.startsWith("/resources/")) { let locator = request.path.substr("/resources/".length); let type = request.headers["content-type"]; let headers = type ? { "content-type": type } : undefined; let resp = { body: request.body, headers }; console.log(`Storing ${JSON.stringify(resp)} at /resources/${locator}}`); this.state.whole.transient.resources[locator] = resp; return { status: 201 } } } if (request.method == "GET") { switch (request.path) { case "/state": return processGetState(this.state); case "/": case "/status": return processGetStatus(this.state, request.to); case "/children": return this.processGetChildren(request); case "/gateways": return this.processGetGateways(request); case "/hosts": return this.processGetHosts(request); case "/debug": console.log("Will enter debugger!"); debugger return { status: 200 }; // case "/tenants": // return this.processGetTenants(request); } if (request.path.startsWith("/resources/")) { let locator = request.path.substr("/resources/".length); let response = this.state.whole.transient.resources[locator]; if (response == undefined) { return { status: 404 }; } return response; } } return { body: `404 (Not Found). The url "${request.path}" is not available for ${request.method} at host ${request.to} `, status: 404 }; } }; let init: MultiPortMachineInit = { ports: { } }; for (let port of burpasysPorts) { init.ports[port] = handler; } // init.ports["burpasys"] = handler; super(init); this.state = state; } processSpawnAliasRequest(bs: TBurpaState, request: Request): ResponseInit { // request.to, request.body.template, request.body.secret, request.body.replaces, request.body.gateway //console.log(`SPAWNING ALIAS ${DatagramShort(request, true)}`); let hostTemplate = new HostNameTemplate(request.body.template); let p = hostTemplate.parent; if (p !== "") { let parent = new HostName(hostTemplate.parent); let parentHost = this.state.findLocalHost(parent.name); if (parentHost instanceof Vagrant && !request.body.gateway) { throw new Error(`You must provide a gateway when spawning a vagrant host ${DatagramShort(request)}`); } if (!parentHost) { let errmsg = `Cannot find burpa host ${parent.name} (derived from ${request.body.template}). Not in hosts ${this.state.debugLocalHosts()}`; console.warn(errmsg) return { from: request.to, body: { error: errmsg }, headers: {}, status: 502 // cannot create } } let newName = parentHost.allocateChildName(hostTemplate, request.body.secret); if (newName.isLeft()) { return { from: request.to, body: { error: newName.extract().msg }, headers: {}, status: 409 // cannot create } } let name = newName.extract() as string; let record = parentHost.addChild(name, request.body.secret, true, request.body.gateway); let resp = { from: hostTemplate.parent, reqno: 0, port: "burpasys", body: { host: name, replaces: request.body.replaces, /*, generation */ rootGateway: bs.persistent.rootGateway ?? undefined, rootHost: bs.persistent.rootHost ?? undefined, inbox: hasElements(bs.transient.inboxes[record.self.name]) }, headers: { location: `/children/${name}` }, status: 201 // http created } if (request.body.replaces) { delete parentHost.childHosts[request.body.replaces]; } //h.childHosts[name] = ownership; //console.log("Successfully spawning child name", name); return resp; } else if (hostTemplate.name !== null) { let name = hostTemplate.name; let resp = { from: hostTemplate.parent, reqno: 0, port: "burpasys", body: { host: hostTemplate.name, replaces: request.body.replaces, /*, generation */ rootGateway: bs.persistent.rootGateway ?? undefined, rootHost: bs.persistent.rootHost ?? undefined }, headers: { location: `/children/${name}` }, status: 201 // http created } //console.log("Successfully spawning child name", name); return resp; } else { let resp = { from: hostTemplate.parent, reqno: 0, port: "burpasys", body: `Illegal template "${hostTemplate.nameTemplate}""`, status: 409 // http created } return resp; } } processGetChildren(request: Request): any { let host = this.state.findLocalHost(request.to); if (host) { return { body: Object.values(host.childHosts).map(e => e.getPublicInfo()) }; } return { body: [] } } processGetGateways(request: Request) { return { body: (Object.keys(this.state.hosts.filter(e => e instanceof Gateway))) // .filter(e => e != "local")) } } // processGetTenants(request: Request) { // return { // body: (Object.keys(CurrentTenants)) // } // } processGetHosts(request: Request) { return { body: this.state.hosts.map(e => e.alias.name) } } } /** * Lets you query a Javascript single threaded process for all gateways and vagrants * currently hosted as well as the default local gateway and the default local host. * @param request * @returns The current status */ function processGetStatus(state: CBurpastateWrapper, host: string) { let h = state.findLocalHost(host) as Host; return { body: { Host: host, Children: h.childHosts, Process: { Environment: processType(), SSL: state.whole.persistent.ssl, Config: { LocalGateway: state.whole.persistent.localGateway, LocalHost: state.whole.persistent.localHost, StoredVagrants: localStorage.getItem("Vagrants") }, LocalHosts: cleanHostSet(state.hosts), DownstreamTransports: DebugTransports(state.LocalGateway().extension.downstreamGateways), UpstreamTransport: DebugTransport(state.LocalGateway().extension.upstreamGateway) } } } } /** * Lets you query a Javascript single threaded process for all gateways and vagrants * currently hosted as well as the default local gateway and the default local host. * @param request * @returns The current status */ export function processGetState(state: CBurpastateWrapper) { //let h = state.findLocalHost(host) as Host; let body: any = JSON.parse(JSON.stringify(state.whole.persistent)); // JSON.parse(JSON.stringify(state.whole.persistent)); let gws = state.hosts.filter(e => e instanceof Gateway) as Gateway[]; let x: any = gws.map(gw => { let upstream = gw.extension.upstreamGateway; let ret = { gw: gw.hostId, upstream: upstream ? upstream.Transport.debug : undefined, downstream: gw.extension.downstreamGateways.map(e => e.Transport.debug) }; return ret; }); body.inboxes = state.whole.transient.inboxes; body.transports = Object.fromEntries(x.map((e: any) => [e.gw, { upstream: e.upstream, downstream: e.downstream }])); return { body } } function DebugTransports(gwps: GatewayProxy[]) { return gwps.map(e => { return DebugTransport(e) }) } function DebugTransport(gwp: GatewayProxy) { if (!gwp) { return "none"; } return gwp.Transport.debug; } function cleanHostSet(set: Host[]) { return set.map(e => { return { type: e.constructor.name, ports: Object.keys(e.machine.init.ports), children: Object.values(e.childHosts).map(e => { return e.getPublicInfo(); }) } }) } function hasElements(arr?: any[]) { if (arr) { return arr.length > 0; } return false; } // async function processSpawnGatewayAndHost(state: CBurpastateWrapper, request: Request) { // let { host, secret } = request.body; // let gw = state.LocalGateway(); // let gwname = gw.allocateChildName(new HostNameTemplate(`*-${gw.publicAlias}`), secret); // if (gwname.isRight()) { // gw.addChild(gwname.extract(), secret, ""); // let sr = new CRequest({ // to: new HostName(host).parent, // from: gw.alias.name, // method: "POST", // path: "/children", // body: { template: host, secret } // }) // let response = await gw.sendRequest(sr).current; // return { status: 201, body: { host: response.body.host, gateway: gwname, replacesGateway: request.from } }; // } // return { status: 409, body: (gwname.extract() as SpawningError).msg }; // }