1 module beangle.task.server; 2 3 import vibe.core.core; 4 import vibe.core.log; 5 import vibe.http.fileserver : serveStaticFiles; 6 import vibe.http.router : URLRouter; 7 import vibe.http.server; 8 import vibe.http.websockets : WebSocket, handleWebSockets; 9 10 import core.time; 11 import std.conv : to; 12 import std.algorithm; 13 import std.string; 14 import std.array; 15 import beangle.util : removeFromArray; 16 import beangle.task.channel; 17 import beangle.task.config; 18 import beangle.xml; 19 20 //name -> channel 21 private AgentChannel[string] agents; 22 //id -> channel 23 private AdminChannel[string] admins; 24 private ServerSetting setting; 25 26 int startServer(string xml, string[] args){ 27 setting = ServerSetting.parse(xml); 28 auto router = new URLRouter(setting.path); 29 router.get("/ws", handleWebSockets(&handleWebSocketConnection)); 30 router.get("/call/:agent", &callAgent); 31 32 auto hs = new HTTPServerSettings(); 33 hs.port = setting.port; 34 hs.bindAddresses = setting.ips; 35 36 auto listener = listenHTTP(hs, router); 37 return runApplication(&args); 38 } 39 40 /** 41 * Call agent directly 42 */ 43 void callAgent(HTTPServerRequest req, HTTPServerResponse res){ 44 string agentName = req.params.get("agent"); 45 string commands = req.query.get("commands", "").strip(); 46 if (commands.length==0){ 47 res.writeBody("commands needed,using ?commands=some commands in query string", 400); 48 } else { 49 import std.stdio; 50 auto client = req.peer(); 51 if ( client.startsWith("127.0.0.1") || client.startsWith("[0:0:0:0:0:0:0:1]")){ 52 auto caller = new WebCaller(); 53 if (agentName in agents){ 54 res.writeBody(caller.invoke(agents[agentName], commands)); 55 }else { 56 res.writeBody("Cannot find agent named " ~ agentName); 57 } 58 }else { 59 res.writeBody("service for localhost only.", 500); 60 } 61 } 62 } 63 64 bool verify(string name, string secret){ 65 if (name == "admin"){ 66 return secret == setting.secret; 67 }else { 68 if (name in setting.agentSecrets){ 69 return secret == setting.agentSecrets.get(name, "changeit"); 70 }else { 71 return false; 72 } 73 } 74 } 75 76 /** 77 Handle Websocket messages,do message broking and login. 78 */ 79 void handleWebSocketConnection(scope WebSocket socket){ 80 socket.waitForData(1.seconds); 81 string loginText = socket.receiveText; 82 // Server-side validation of results 83 Channel channel = null; 84 bool isAdmin = false; 85 if (loginText !is null && loginText.startsWith("/login ")) { 86 string[] credentials = loginText["/login ".length .. $].split(" "); 87 string name = credentials[0].strip(); 88 string password = (credentials.length==1)? name : credentials[1].strip(); 89 if (!verify(name, password)){ 90 socket.send("invalid secret"); 91 return; 92 }else { 93 if (name == "admin"){ 94 auto newc = AdminChannel.create(name, socket); 95 admins[newc.id] = newc; 96 channel = newc; 97 isAdmin = true; 98 }else { 99 auto newc = AgentChannel.create(name, socket); 100 if (name in agents){ 101 close(agents[name]); 102 } 103 agents[name] = newc; 104 channel = newc; 105 } 106 socket.send(name ~ " is connected."); 107 auto r = cast(HTTPServerRequest)socket.request; 108 logInfo("%s connected @ %s.", name, r.peer()); 109 } 110 }else { 111 socket.send("Invalid name,using /login username password"); 112 socket.close(); 113 return; 114 } 115 // message loop 116 while (socket.waitForData) { 117 if (!socket.connected) break; 118 auto text = socket.receiveText.strip(); 119 if (text == "/close") { 120 break; 121 } 122 if (isAdmin){ 123 if (text == "/sessions"){ 124 auto s = agents.values.map!(x=> x.toString()).join(","); 125 channel.send(s); 126 }else if (text.startsWith("/connect")){ 127 if (text.length > "/connect ".length){ 128 auto agentName = text["/connect ".length .. $]; 129 auto result = agents.get(agentName, null); 130 if (result !is null && result.socket.connected){ 131 AdminChannel admin = cast(AdminChannel)channel; 132 admin.setAgent(result); 133 channel.send(result.id); 134 }else { 135 channel.send(agentName ~" is offline"); 136 } 137 }else { 138 channel.send("using /connect user_name"); 139 } 140 } else { 141 auto admin = cast(AdminChannel)channel; 142 if (text.startsWith("@")){ 143 auto spaceIdx = text.indexOf(' '); 144 if (spaceIdx == -1){ 145 admin.send("using @agentname commands ..."); 146 }else { 147 auto agentName = text[0..spaceIdx].strip(); 148 auto agent = agents[agentName]; 149 if (agent !is null && agent.socket.connected){ 150 agent.invoke(SocketCaller.create(admin), text[spaceIdx..$].strip()); 151 }else { 152 channel.send("offline"); 153 } 154 } 155 }else { 156 if (admin.agent !is null){ 157 if(text == "cd"){ 158 admin.changeWorkdir("~"); 159 admin.send("set workdir " ~ admin.workdir); 160 }else if (text.startsWith("cd ") && text.indexOf(";") == -1){ 161 admin.changeWorkdir(text[3..$].strip()); 162 admin.send("set workdir " ~ admin.workdir); 163 }else { 164 auto prefix = "cd " ~ admin.workdir ~ ";"; 165 admin.agent.invoke(SocketCaller.create(admin), prefix ~ text); 166 } 167 }else { 168 admin.send("using @agentname commands ... or connect a exist agent using /connect"); 169 } 170 } 171 } 172 }else { 173 (cast(AgentChannel)channel).reply(text); 174 } 175 } 176 close(channel); 177 } 178 179 /** Close channel and socket. 180 */ 181 void close(Channel channel){ 182 agents.remove(channel.name); 183 admins.remove(channel.id); 184 if (channel.socket.connected){ 185 channel.send("disconneted."); 186 channel.socket.close(); 187 } 188 logInfo("%s disconnected", channel.name); 189 } 190