]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnsdist-console.cc
make it less wrong
[thirdparty/pdns.git] / pdns / dnsdist-console.cc
CommitLineData
12471842
PL
1/*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
ffb07158 22#include "dnsdist.hh"
23#include "sodcrypto.hh"
424bdfb1
PD
24
25#if defined (__OpenBSD__)
26#include <readline/readline.h>
27#include <readline/history.h>
28#else
9c8aa826 29#include <editline/readline.h>
424bdfb1
PD
30#endif
31
ffb07158 32#include <fstream>
33#include "dolog.hh"
7e7a5b71 34#include "ext/json11/json11.hpp"
ffb07158 35
f758857a 36vector<pair<struct timeval, string> > g_confDelta;
37
38// MUST BE CALLED UNDER A LOCK - right now the LuaLock
39void feedConfigDelta(const std::string& line)
40{
171fca9a 41 if(line.empty())
42 return;
f758857a 43 struct timeval now;
44 gettimeofday(&now, 0);
45 g_confDelta.push_back({now,line});
46}
47
ffb07158 48void doClient(ComboAddress server, const std::string& command)
49{
9c186303 50 if(g_verbose)
51 cout<<"Connecting to "<<server.toStringWithPort()<<endl;
ffb07158 52 int fd=socket(server.sin4.sin_family, SOCK_STREAM, 0);
6a62c0e3
RG
53 if (fd < 0) {
54 cerr<<"Unable to connect to "<<server.toStringWithPort()<<endl;
55 return;
56 }
ffb07158 57 SConnect(fd, server);
9c186303 58 setTCPNoDelay(fd);
ffb07158 59 SodiumNonce theirs, ours;
60 ours.init();
61
62 writen2(fd, (const char*)ours.value, sizeof(ours.value));
63 readn2(fd, (char*)theirs.value, sizeof(theirs.value));
64
65 if(!command.empty()) {
ffb07158 66 string msg=sodEncryptSym(command, g_key, ours);
a683e8bd 67 putMsgLen32(fd, (uint32_t) msg.length());
ffb07158 68 if(!msg.empty())
69 writen2(fd, msg);
70 uint32_t len;
e4ef64be
RG
71 if(getMsgLen32(fd, &len)) {
72 if (len > 0) {
73 boost::scoped_array<char> resp(new char[len]);
74 readn2(fd, resp.get(), len);
75 msg.assign(resp.get(), len);
76 msg=sodDecryptSym(msg, g_key, theirs);
7e7a5b71 77 cout<<msg;
a43f9501 78 cout.flush();
e4ef64be
RG
79 }
80 }
81 else {
82 cout << "Connection closed by the server." << endl;
83 }
6a62c0e3 84 close(fd);
ffb07158 85 return;
86 }
87
88 set<string> dupper;
89 {
90 ifstream history(".dnsdist_history");
91 string line;
92 while(getline(history, line))
93 add_history(line.c_str());
94 }
95 ofstream history(".dnsdist_history", std::ios_base::app);
96 string lastline;
97 for(;;) {
98 char* sline = readline("> ");
99 rl_bind_key('\t',rl_complete);
100 if(!sline)
101 break;
102
103 string line(sline);
104 if(!line.empty() && line != lastline) {
105 add_history(sline);
106 history << sline <<endl;
107 history.flush();
108 }
109 lastline=line;
110 free(sline);
111
112 if(line=="quit")
113 break;
114
0877da98
RG
115 /* no need to send an empty line to the server */
116 if(line.empty())
117 continue;
118
ffb07158 119 string msg=sodEncryptSym(line, g_key, ours);
a683e8bd 120 putMsgLen32(fd, (uint32_t) msg.length());
ffb07158 121 writen2(fd, msg);
122 uint32_t len;
ff16861c 123 if(!getMsgLen32(fd, &len)) {
ffb07158 124 cout << "Connection closed by the server." << endl;
125 break;
126 }
127
ff16861c
RG
128 if (len > 0) {
129 boost::scoped_array<char> resp(new char[len]);
130 readn2(fd, resp.get(), len);
131 msg.assign(resp.get(), len);
132 msg=sodDecryptSym(msg, g_key, theirs);
7e7a5b71 133 cout<<msg;
134 cout.flush();
ff16861c
RG
135 }
136 else {
137 cout<<endl;
138 }
ffb07158 139 }
6c1ca990 140 close(fd);
ffb07158 141}
142
143void doConsole()
144{
145 set<string> dupper;
146 {
147 ifstream history(".dnsdist_history");
148 string line;
149 while(getline(history, line))
150 add_history(line.c_str());
151 }
152 ofstream history(".dnsdist_history", std::ios_base::app);
153 string lastline;
154 for(;;) {
155 char* sline = readline("> ");
156 rl_bind_key('\t',rl_complete);
157 if(!sline)
158 break;
159
160 string line(sline);
161 if(!line.empty() && line != lastline) {
162 add_history(sline);
163 history << sline <<endl;
164 history.flush();
165 }
166 lastline=line;
167 free(sline);
168
169 if(line=="quit")
170 break;
171
172 string response;
173 try {
7e7a5b71 174 bool withReturn=true;
175 retry:;
176 try {
177 std::lock_guard<std::mutex> lock(g_luamutex);
178 g_outputBuffer.clear();
179 resetLuaSideEffect();
180 auto ret=g_lua.executeCode<
181 boost::optional<
182 boost::variant<
183 string,
184 shared_ptr<DownstreamState>,
185 std::unordered_map<string, double>
186 >
187 >
188 >(withReturn ? ("return "+line) : line);
7e7a5b71 189 if(ret) {
190 if (const auto strValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
191 cout<<(*strValue)->getName()<<endl;
192 }
193 else if (const auto strValue = boost::get<string>(&*ret)) {
194 cout<<*strValue<<endl;
195 }
196 else if(const auto um = boost::get<std::unordered_map<string, double> >(&*ret)) {
197 using namespace json11;
198 Json::object o;
199 for(const auto& v : *um)
200 o[v.first]=v.second;
201 Json out = o;
202 cout<<out.dump()<<endl;
203 }
204 }
205 else
206 cout << g_outputBuffer;
207 if(!getLuaNoSideEffect())
208 feedConfigDelta(line);
ffb07158 209 }
7e7a5b71 210 catch(const LuaContext::SyntaxErrorException&) {
211 if(withReturn) {
212 withReturn=false;
213 goto retry;
214 }
215 throw;
216 }
217 }
218 catch(const LuaContext::WrongTypeException& e) {
219 std::cerr<<"Command returned an object we can't print"<<std::endl;
220 // tried to return something we don't understand
ffb07158 221 }
222 catch(const LuaContext::ExecutionErrorException& e) {
98bac6bc 223 if(!strcmp(e.what(),"invalid key to 'next'"))
224 std::cerr<<"Error parsing parameters, did you forget parameter name?";
225 else
226 std::cerr << e.what();
ffb07158 227 try {
228 std::rethrow_if_nested(e);
98bac6bc 229
d96df9ec 230 std::cerr << std::endl;
ffb07158 231 } catch(const std::exception& e) {
232 // e is the exception that was thrown from inside the lambda
d96df9ec 233 std::cerr << ": " << e.what() << std::endl;
ffb07158 234 }
235 catch(const PDNSException& e) {
236 // e is the exception that was thrown from inside the lambda
d96df9ec 237 std::cerr << ": " << e.reason << std::endl;
ffb07158 238 }
239 }
240 catch(const std::exception& e) {
241 // e is the exception that was thrown from inside the lambda
242 std::cerr << e.what() << std::endl;
243 }
244 }
245}
246/**** CARGO CULT CODE AHEAD ****/
ca4252e0
RG
247const std::vector<ConsoleKeyword> g_consoleKeywords{
248 /* keyword, function, parameters, description */
249 { "addACL", true, "netmask", "add to the ACL set who can use this server" },
250 { "addAction", true, "DNS rule, DNS action", "add a rule" },
251 { "addAnyTCRule", true, "", "generate TC=1 answers to ANY queries received over UDP, moving them to TCP" },
252 { "addDelay", true, "domain, n", "delay answers within that domain by n milliseconds" },
253 { "addDisableValidationRule", true, "DNS rule", "set the CD flags to 1 for all queries matching the specified domain" },
254 { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", [false], [TCP Fast Open queue size]", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter sets SO_REUSEPORT when available. The last parameter sets the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0" },
255 { "addDomainBlock", true, "domain", "block queries within this domain" },
256 { "addDomainSpoof", true, "domain, ip[, ip6]", "generate answers for A/AAAA/ANY queries using the ip parameters" },
257 { "addDynBlocks", true, "addresses, message[, seconds]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default)" },
258 { "addLocal", true, "netmask, [true], [false], [TCP Fast Open queue size]", "add to addresses we listen on. Second optional parameter sets TCP or not. Third optional parameter sets SO_REUSEPORT when available. Last parameter sets the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0" },
259 { "addLuaAction", true, "x, func", "where 'x' is all the combinations from `addPoolRule`, and func is a function with the parameter `dq`, which returns an action to be taken on this packet. Good for rare packets but where you want to do a lot of processing" },
260 { "addNoRecurseRule", true, "domain", "clear the RD flag for all queries matching the specified domain" },
261 { "addPoolRule", true, "domain, pool", "send queries to this domain to that pool" },
262 { "addQPSLimit", true, "domain, n", "limit queries within that domain to n per second" },
263 { "addQPSPoolRule", true, "x, limit, pool", "like `addPoolRule`, but only select at most 'limit' queries/s for this pool, letting the subsequent rules apply otherwise" },
264 { "addResponseAction", true, "DNS rule, DNS response action", "add a response rule" },
265 { "AllowAction", true, "", "let these packets go through" },
788c3243 266 { "AllowResponseAction", true, "", "let these packets go through" },
ca4252e0
RG
267 { "AllRule", true, "", "matches all traffic" },
268 { "AndRule", true, "list of DNS rules", "matches if all sub-rules matches" },
269 { "benchRule", true, "DNS Rule [, iterations [, suffix]]", "bench the specified DNS rule" },
270 { "carbonServer", true, "serverIP, [ourname], [interval]", "report statistics to serverIP using our hostname, or 'ourname' if provided, every 'interval' seconds" },
271 { "controlSocket", true, "addr", "open a control socket on this address / connect to this address in client mode" },
272 { "clearDynBlocks", true, "", "clear all dynamic blocks" },
786e4d8c 273 { "clearQueryCounters", true, "", "clears the query counter buffer" },
ca4252e0
RG
274 { "clearRules", true, "", "remove all current rules" },
275 { "DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
788c3243 276 { "DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
ca4252e0
RG
277 { "delta", true, "", "shows all commands entered that changed the configuration" },
278 { "DisableValidationAction", true, "", "set the CD bit in the question, let it go through" },
279 { "DropAction", true, "", "drop these packets" },
788c3243 280 { "DropResponseAction", true, "", "drop these packets" },
ca4252e0
RG
281 { "dumpStats", true, "", "print all statistics we gather" },
282 { "exceedNXDOMAINs", true, "rate, seconds", "get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds" },
283 { "exceedQRate", true, "rate, seconds", "get set of address that exceed `rate` queries/s over `seconds` seconds" },
284 { "exceedQTypeRate", true, "type, rate, seconds", "get set of address that exceed `rate` queries/s for queries of type `type` over `seconds` seconds" },
285 { "exceedRespByterate", true, "rate, seconds", "get set of addresses that exeeded `rate` bytes/s answers over `seconds` seconds" },
286 { "exceedServFails", true, "rate, seconds", "get set of addresses that exceed `rate` servails/s over `seconds` seconds" },
287 { "firstAvailable", false, "", "picks the server with the lowest `order` that has not exceeded its QPS limit" },
288 { "fixupCase", true, "bool", "if set (default to no), rewrite the first qname of the question part of the answer to match the one from the query. It is only useful when you have a downstream server that messes up the case of the question qname in the answer" },
289 { "generateDNSCryptCertificate", true, "\"/path/to/providerPrivate.key\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", serial, validFrom, validUntil", "generate a new resolver private key and related certificate, valid from the `validFrom` timestamp until the `validUntil` one, signed with the provider private key" },
290 { "generateDNSCryptProviderKeys", true, "\"/path/to/providerPublic.key\", \"/path/to/providerPrivate.key\"", "generate a new provider keypair"},
291 { "getPoolServers", true, "pool", "return servers part of this pool" },
786e4d8c 292 { "getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided" },
ca4252e0
RG
293 { "getResponseRing", true, "", "return the current content of the response ring" },
294 { "getServer", true, "n", "returns server with index n" },
295 { "getServers", true, "", "returns a table with all defined servers" },
296 { "grepq", true, "Netmask|DNS Name|100ms|{\"::1\", \"powerdns.com\", \"100ms\"} [, n]", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms" },
297 { "leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
456fc645 298 { "LogAction", true, "[filename], [binary], [append], [buffered]", "Log a line for each query, to the specified file if any, to the console (require verbose) otherwise. When logging to a file, the `binary` optional parameter specifies whether we log in binary form (default) or in textual form, the `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." },
ca4252e0
RG
299 { "makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting" },
300 { "MaxQPSIPRule", true, "qps, v4Mask=32, v6Mask=64", "matches traffic exceeding the qps limit per subnet" },
301 { "MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit" },
302 { "mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
303 { "mvRule", true, "from, to", "move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule, in which case the rule will be moved to the last position" },
304 { "newDNSName", true, "name", "make a DNSName based on this .-terminated name" },
305 { "newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity" },
306 { "newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`" },
307 { "newRuleAction", true, "DNS rule, DNS action", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`" },
308 { "newServer", true, "{address=\"ip:port\", qps=1000, order=1, weight=10, pool=\"abuse\", retries=5, tcpSendTimeout=30, tcpRecvTimeout=30, checkName=\"a.root-servers.net.\", checkType=\"A\", maxCheckFailures=1, mustResolve=false, useClientSubnet=true, source=\"address|interface name|address@interface\"", "instantiate a server" },
309 { "newServerPolicy", true, "name, function", "create a policy object from a Lua function" },
310 { "newSuffixMatchNode", true, "", "returns a new SuffixMatchNode" },
311 { "NoRecurseAction", true, "", "strip RD bit from the question, let it go through" },
312 { "PoolAction", true, "poolname", "set the packet into the specified pool" },
313 { "printDNSCryptProviderFingerprint", true, "\"/path/to/providerPublic.key\"", "display the fingerprint of the provided resolver public key" },
314 { "RegexRule", true, "regex", "matches the query name against the supplied regex" },
8429ad04 315 { "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" },
ca4252e0
RG
316 { "RemoteLogAction", true, "RemoteLogger", "send the content of this query to a remote logger via Protocol Buffer" },
317 { "RemoteLogResponseAction", true, "RemoteLogger", "send the content of this response to a remote logger via Protocol Buffer" },
318 { "rmResponseRule", true, "n", "remove response rule n" },
319 { "rmRule", true, "n", "remove rule n" },
320 { "rmServer", true, "n", "remove server with index n" },
321 { "roundrobin", false, "", "Simple round robin over available servers" },
322 { "QNameLabelsCountRule", true, "min, max", "matches if the qname has less than `min` or more than `max` labels" },
323 { "QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes" },
324 { "QTypeRule", true, "qtype", "matches queries with the specified qtype" },
788c3243 325 { "RCodeRule", true, "rcode", "matches responses with the specified rcode" },
ca4252e0 326 { "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
56d68fad 327 { "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
ca4252e0
RG
328 { "setDNSSECPool", true, "pool name", "move queries requesting DNSSEC processing to this pool" },
329 { "setECSOverride", true, "bool", "whether to override an existing EDNS Client Subnet value in the query" },
330 { "setECSSourcePrefixV4", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv4 queries" },
331 { "setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries" },
332 { "setKey", true, "key", "set access key to that key" },
333 { "setLocal", true, "netmask, [true], [false], [TCP Fast Open queue size]", "reset list of addresses we listen on to this address. Second optional parameter sets TCP or not. Third optional parameter sets SO_REUSEPORT when available. Last parameter sets the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0." },
334 { "setMaxTCPClientThreads", true, "n", "set the maximum of TCP client threads, handling TCP connections" },
335 { "setMaxTCPQueuedConnections", true, "n", "set the maximum number of TCP connections queued (waiting to be picked up by a client thread)" },
336 { "setMaxUDPOutstanding", true, "n", "set the maximum number of outstanding UDP queries to a given backend server. This can only be set at configuration time and defaults to 10240" },
786e4d8c
RS
337 { "setQueryCount", true, "bool", "set whether queries should be counted" },
338 { "setQueryCountFilter", true, "func", "filter queries that would be counted, where `func` is a function with parameter `dq` which decides whether a query should and how it should be counted" },
ca4252e0
RG
339 { "setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)" },
340 { "setServerPolicy", true, "policy", "set server selection policy to that policy" },
341 { "setServerPolicyLua", true, "name, function", "set server selection policy to one named 'name' and provided by 'function'" },
26a3cdb7 342 { "setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query" },
ca4252e0
RG
343 { "setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds" },
344 { "setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds" },
e0b5e49d 345 { "setUDPTimeout", true, "n", "set the maximum time dnsdist will wait for a response from a backend over UDP, in seconds" },
ca4252e0
RG
346 { "setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged" },
347 { "show", true, "string", "outputs `string`" },
348 { "showACL", true, "", "show our ACL set" },
349 { "showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds" },
350 { "showDynBlocks", true, "", "show dynamic blocks in force" },
351 { "showResponseLatency", true, "", "show a plot of the response time latency distribution" },
352 { "showResponseRules", true, "", "show all defined response rules" },
353 { "showRules", true, "", "show all defined rules" },
354 { "showServerPolicy", true, "", "show name of currently operational server selection policy" },
355 { "showServers", true, "", "output all servers" },
e65ae260 356 { "showTCPStats", true, "", "show some statistics regarding TCP" },
ca4252e0
RG
357 { "showVersion", true, "", "show the current version" },
358 { "shutdown", true, "", "shut down `dnsdist`" },
359 { "SpoofAction", true, "{ip, ...} ", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
360 { "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" },
361 { "testCrypto", true, "", "test of the crypto all works" },
362 { "topBandwidth", true, "top", "show top-`top` clients that consume the most bandwidth over length of ringbuffer" },
363 { "topClients", true, "n", "show top-`n` clients sending the most queries over length of ringbuffer" },
364 { "topQueries", true, "n[, labels]", "show top 'n' queries, as grouped when optionally cut down to 'labels' labels" },
365 { "topResponses", true, "n, kind[, labels]", "show top 'n' responses with RCODE=kind (0=NO Error, 2=ServFail, 3=ServFail), as grouped when optionally cut down to 'labels' labels" },
366 { "topResponseRule", true, "", "move the last response rule to the first position" },
367 { "topRule", true, "", "move the last rule to the first position" },
368 { "topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels" },
369 { "truncateTC", true, "bool", "if set (default) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22" },
8429ad04 370 { "unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter" },
ca4252e0
RG
371 { "webserver", true, "address:port, password [, apiKey [, customHeaders ]])", "launch a webserver with stats on that address with that password" },
372 { "whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter" },
373 { "wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter" },
374};
375
ffb07158 376extern "C" {
377char* my_generator(const char* text, int state)
378{
379 string t(text);
e8130431
RG
380 /* to keep it readable, we try to keep only 4 keywords per line
381 and to start a new line when the first letter changes */
ffb07158 382 static int s_counter=0;
383 int counter=0;
384 if(!state)
385 s_counter=0;
386
ca4252e0
RG
387 for(const auto& keyword : g_consoleKeywords) {
388 if(boost::starts_with(keyword.name, t) && counter++ == s_counter) {
389 std::string value(keyword.name);
ffb07158 390 s_counter++;
ca4252e0
RG
391 if (keyword.function) {
392 value += "(";
393 if (keyword.parameters.empty()) {
394 value += ")";
395 }
396 }
397 return strdup(value.c_str());
ffb07158 398 }
399 }
400 return 0;
401}
402
403char** my_completion( const char * text , int start, int end)
404{
405 char **matches=0;
406 if (start == 0)
407 matches = rl_completion_matches ((char*)text, &my_generator);
d9de8b61
CH
408
409 // skip default filename completion.
410 rl_attempted_completion_over = 1;
411
ffb07158 412 return matches;
413}
414}
415
416void controlClientThread(int fd, ComboAddress client)
417try
418{
9c186303 419 setTCPNoDelay(fd);
ffb07158 420 SodiumNonce theirs;
421 readn2(fd, (char*)theirs.value, sizeof(theirs.value));
422 SodiumNonce ours;
423 ours.init();
424 writen2(fd, (char*)ours.value, sizeof(ours.value));
425
426 for(;;) {
427 uint32_t len;
428 if(!getMsgLen32(fd, &len))
429 break;
0877da98
RG
430
431 if (len == 0) {
432 /* just ACK an empty message
433 with an empty response */
434 putMsgLen32(fd, 0);
435 continue;
436 }
437
ffb07158 438 boost::scoped_array<char> msg(new char[len]);
439 readn2(fd, msg.get(), len);
440
441 string line(msg.get(), len);
442 line = sodDecryptSym(line, g_key, theirs);
443 // cerr<<"Have decrypted line: "<<line<<endl;
444 string response;
445 try {
7e7a5b71 446 bool withReturn=true;
447 retry:;
448 try {
449 std::lock_guard<std::mutex> lock(g_luamutex);
450
451 g_outputBuffer.clear();
452 resetLuaSideEffect();
453 auto ret=g_lua.executeCode<
454 boost::optional<
455 boost::variant<
456 string,
457 shared_ptr<DownstreamState>,
458 std::unordered_map<string, double>
459 >
460 >
461 >(withReturn ? ("return "+line) : line);
ffb07158 462
463 if(ret) {
464 if (const auto strValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
7e7a5b71 465 response=(*strValue)->getName()+"\n";
ffb07158 466 }
467 else if (const auto strValue = boost::get<string>(&*ret)) {
7e7a5b71 468 response=*strValue+"\n";
ffb07158 469 }
7e7a5b71 470 else if(const auto um = boost::get<std::unordered_map<string, double> >(&*ret)) {
471 using namespace json11;
472 Json::object o;
473 for(const auto& v : *um)
474 o[v.first]=v.second;
475 Json out = o;
476 response=out.dump()+"\n";
477 }
ffb07158 478 }
479 else
480 response=g_outputBuffer;
f758857a 481 if(!getLuaNoSideEffect())
482 feedConfigDelta(line);
7e7a5b71 483 }
484 catch(const LuaContext::SyntaxErrorException&) {
485 if(withReturn) {
486 withReturn=false;
487 goto retry;
488 }
489 throw;
490 }
ffb07158 491 }
628377ec
RG
492 catch(const LuaContext::WrongTypeException& e) {
493 response = "Command returned an object we can't print: " +std::string(e.what()) + "\n";
494 // tried to return something we don't understand
495 }
ffb07158 496 catch(const LuaContext::ExecutionErrorException& e) {
98bac6bc 497 if(!strcmp(e.what(),"invalid key to 'next'"))
498 response = "Error: Parsing function parameters, did you forget parameter name?";
499 else
500 response = "Error: " + string(e.what());
ffb07158 501 try {
502 std::rethrow_if_nested(e);
503 } catch(const std::exception& e) {
504 // e is the exception that was thrown from inside the lambda
98bac6bc 505 response+= ": " + string(e.what());
ffb07158 506 }
507 catch(const PDNSException& e) {
508 // e is the exception that was thrown from inside the lambda
98bac6bc 509 response += ": " + string(e.reason);
ffb07158 510 }
511 }
512 catch(const LuaContext::SyntaxErrorException& e) {
513 response = "Error: " + string(e.what()) + ": ";
514 }
515 response = sodEncryptSym(response, g_key, ours);
516 putMsgLen32(fd, response.length());
517 writen2(fd, response.c_str(), response.length());
518 }
519 infolog("Closed control connection from %s", client.toStringWithPort());
520 close(fd);
521 fd=-1;
522}
523catch(std::exception& e)
524{
525 errlog("Got an exception in client connection from %s: %s", client.toStringWithPort(), e.what());
526 if(fd >= 0)
527 close(fd);
528}
529