]>
Commit | Line | Data |
---|---|---|
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 | */ | |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
12c86877 BH |
25 | #include <cstring> |
26 | #include <string> | |
27 | #include <map> | |
28 | #include <sys/types.h> | |
29 | #include <sys/un.h> | |
30 | #include <dlfcn.h> | |
12c86877 | 31 | #include <unistd.h> |
040712e0 | 32 | #include <boost/algorithm/string.hpp> |
dd7da6cd | 33 | |
12c86877 BH |
34 | #include <sys/socket.h> |
35 | #include <netinet/in.h> | |
12c86877 BH |
36 | #include <iostream> |
37 | #include <sstream> | |
38 | #include <sys/types.h> | |
39 | #include <signal.h> | |
40 | ||
41 | #include <sys/stat.h> | |
42 | #include <fcntl.h> | |
43 | #include <unistd.h> | |
040712e0 | 44 | #include <boost/algorithm/string.hpp> |
9092419e RG |
45 | #include <thread> |
46 | ||
12c86877 BH |
47 | #include "misc.hh" |
48 | #include "dns.hh" | |
49 | #include "arguments.hh" | |
50 | #include "dnsbackend.hh" | |
51 | #include "dynlistener.hh" | |
52 | #include "dnspacket.hh" | |
53 | #include "logger.hh" | |
54 | #include "statbag.hh" | |
519f5484 | 55 | #include "threadname.hh" |
12c86877 | 56 | |
12c86877 BH |
57 | extern StatBag S; |
58 | ||
040712e0 BH |
59 | DynListener::g_funkdb_t DynListener::s_funcdb; |
60 | DynListener::g_funk_t* DynListener::s_restfunc; | |
61 | ||
e5d684f9 BH |
62 | DynListener::~DynListener() |
63 | { | |
64 | if(!d_socketname.empty()) | |
65 | unlink(d_socketname.c_str()); | |
66 | } | |
67 | ||
040712e0 | 68 | void DynListener::createSocketAndBind(int family, struct sockaddr*local, size_t len) |
12c86877 | 69 | { |
040712e0 | 70 | d_s=socket(family, SOCK_STREAM,0); |
3897b9e1 | 71 | setCloseOnExec(d_s); |
040712e0 BH |
72 | |
73 | if(d_s < 0) { | |
aac40041 | 74 | if (family == AF_UNIX) |
a702a96c | 75 | g_log<<Logger::Error<<"Unable to create control socket at '"<<((struct sockaddr_un*)local)->sun_path<<"', reason: "<<stringerror()<<endl; |
aac40041 | 76 | else |
a702a96c | 77 | g_log<<Logger::Error<<"Unable to create control socket on '"<<((ComboAddress *)local)->toStringWithPort()<<"', reason: "<<stringerror()<<endl; |
040712e0 BH |
78 | exit(1); |
79 | } | |
80 | ||
81 | int tmp=1; | |
82 | if(setsockopt(d_s,SOL_SOCKET,SO_REUSEADDR,(char*)&tmp,sizeof tmp)<0) | |
a702a96c | 83 | throw PDNSException(string("Setsockopt failed on control socket: ")+stringerror()); |
040712e0 BH |
84 | |
85 | if(bind(d_s, local, len) < 0) { | |
aac40041 | 86 | if (family == AF_UNIX) |
a702a96c | 87 | g_log<<Logger::Critical<<"Unable to bind to control socket at '"<<((struct sockaddr_un*)local)->sun_path<<"', reason: "<<stringerror()<<endl; |
aac40041 | 88 | else |
a702a96c | 89 | g_log<<Logger::Critical<<"Unable to bind to control socket on '"<<((ComboAddress *)local)->toStringWithPort()<<"', reason: "<<stringerror()<<endl; |
040712e0 BH |
90 | exit(1); |
91 | } | |
040712e0 | 92 | } |
12c86877 | 93 | |
ce19a815 | 94 | /* this does a simplistic check, if we can connect, we consider it live. If we can't connect because |
95 | of access denied, we must consider it dead, nothing we can do about it. | |
96 | */ | |
97 | bool DynListener::testLive(const string& fname) | |
98 | { | |
99 | struct sockaddr_un addr; | |
100 | int fd = socket(AF_UNIX, SOCK_STREAM, 0); | |
101 | if(fd < 0) { // we'll have bigger issues down the road | |
102 | return false; | |
103 | } | |
104 | ||
76cb4593 | 105 | if (makeUNsockaddr(fname, &addr)) { |
e6a9dde5 | 106 | g_log<<Logger::Critical<<"Unable to open controlsocket, path '"<<fname<<"' is not a valid UNIX socket path."<<endl; |
06dbd1bd CH |
107 | exit(1); |
108 | } | |
ce19a815 | 109 | |
110 | int status = connect(fd, (struct sockaddr*)&addr, sizeof(addr)); | |
ce19a815 | 111 | close(fd); |
112 | return status==0; | |
113 | } | |
114 | ||
040712e0 BH |
115 | void DynListener::listenOnUnixDomain(const string& fname) |
116 | { | |
ce19a815 | 117 | if(testLive(fname)) { |
e6a9dde5 | 118 | g_log<<Logger::Critical<<"Previous controlsocket '"<<fname<<"' is in use"<<endl; |
ce19a815 | 119 | exit(1); |
120 | } | |
040712e0 BH |
121 | int err=unlink(fname.c_str()); |
122 | if(err < 0 && errno!=ENOENT) { | |
a702a96c | 123 | g_log<<Logger::Critical<<"Unable to remove (previous) controlsocket at '"<<fname<<"': "<<stringerror()<<endl; |
040712e0 BH |
124 | exit(1); |
125 | } | |
126 | ||
127 | struct sockaddr_un local; | |
76cb4593 | 128 | if (makeUNsockaddr(fname, &local)) { |
e6a9dde5 | 129 | g_log<<Logger::Critical<<"Unable to bind to controlsocket, path '"<<fname<<"' is not a valid UNIX socket path."<<endl; |
06dbd1bd CH |
130 | exit(1); |
131 | } | |
040712e0 BH |
132 | |
133 | createSocketAndBind(AF_UNIX, (struct sockaddr*)& local, sizeof(local)); | |
134 | d_socketname=fname; | |
135 | if(!arg()["setgid"].empty()) { | |
040712e0 | 136 | if(chmod(fname.c_str(),0660)<0) |
a702a96c | 137 | g_log<<Logger::Error<<"Unable to change group access mode of controlsocket at '"<<fname<<"', reason: "<<stringerror()<<endl; |
2211dac9 | 138 | if(chown(fname.c_str(),static_cast<uid_t>(-1), strToGID(arg()["setgid"]))<0) |
a702a96c | 139 | g_log<<Logger::Error<<"Unable to change group ownership of controlsocket at '"<<fname<<"', reason: "<<stringerror()<<endl; |
040712e0 BH |
140 | } |
141 | ||
df7ca913 | 142 | listen(d_s, 10); |
040712e0 | 143 | |
e6a9dde5 | 144 | g_log<<Logger::Warning<<"Listening on controlsocket in '"<<fname<<"'"<<endl; |
040712e0 BH |
145 | d_nonlocal=true; |
146 | } | |
147 | ||
148 | void DynListener::listenOnTCP(const ComboAddress& local) | |
149 | { | |
201aac4a G |
150 | if (local.isIPv4()) { |
151 | createSocketAndBind(AF_INET, (struct sockaddr*)& local, local.getSocklen()); | |
152 | } else if (local.isIPv6()) { | |
153 | createSocketAndBind(AF_INET6, (struct sockaddr*)& local, local.getSocklen()); | |
154 | } | |
df7ca913 BH |
155 | listen(d_s, 10); |
156 | ||
040712e0 | 157 | d_socketaddress=local; |
e6a9dde5 | 158 | g_log<<Logger::Warning<<"Listening on controlsocket on '"<<local.toStringWithPort()<<"'"<<endl; |
040712e0 BH |
159 | d_nonlocal=true; |
160 | ||
161 | if(!::arg()["tcp-control-range"].empty()) { | |
68b011bd | 162 | d_tcprange.toMasks(::arg()["tcp-control-range"]); |
e6a9dde5 | 163 | g_log<<Logger::Warning<<"Only allowing TCP control from: "<<d_tcprange.toString()<<endl; |
040712e0 BH |
164 | } |
165 | } | |
12c86877 | 166 | |
040712e0 | 167 | |
8a70e507 | 168 | DynListener::DynListener(const ComboAddress& local) : |
eace2c24 | 169 | d_tcp(true) |
040712e0 BH |
170 | { |
171 | listenOnTCP(local); | |
040712e0 BH |
172 | } |
173 | ||
eace2c24 | 174 | DynListener::DynListener(const string &progname) |
040712e0 | 175 | { |
f41ec614 | 176 | |
040712e0 | 177 | if(!progname.empty()) { |
f0f3f0b0 PL |
178 | string socketname = ::arg()["socket-dir"]; |
179 | if (::arg()["socket-dir"].empty()) { | |
180 | if (::arg()["chroot"].empty()) | |
2dfe5b32 | 181 | socketname = std::string(LOCALSTATEDIR) + "/pdns"; |
f0f3f0b0 PL |
182 | else |
183 | socketname = ::arg()["chroot"]; | |
184 | } else if (!::arg()["socket-dir"].empty() && !::arg()["chroot"].empty()) { | |
185 | socketname = ::arg()["chroot"] + ::arg()["socket-dir"]; | |
186 | } | |
187 | socketname += "/"; | |
12c86877 BH |
188 | cleanSlashes(socketname); |
189 | ||
190 | if(!mkdir(socketname.c_str(),0700)) // make /var directory, if needed | |
e6a9dde5 | 191 | g_log<<Logger::Warning<<"Created local state directory '"<<socketname<<"'"<<endl; |
12c86877 | 192 | else if(errno!=EEXIST) { |
e6a9dde5 | 193 | g_log<<Logger::Critical<<"Unable to create socket directory ("<<socketname<<") and it does not exist yet"<<endl; |
12c86877 BH |
194 | exit(1); |
195 | } | |
196 | ||
040712e0 BH |
197 | socketname+=progname+".controlsocket"; |
198 | listenOnUnixDomain(socketname); | |
12c86877 BH |
199 | } |
200 | else | |
040712e0 | 201 | d_nonlocal=false; // we listen on stdin! |
12c86877 BH |
202 | } |
203 | ||
204 | void DynListener::go() | |
205 | { | |
206 | d_ppid=getpid(); | |
9092419e RG |
207 | std::thread listener(std::bind(&DynListener::theListener,this)); |
208 | listener.detach(); | |
12c86877 BH |
209 | } |
210 | ||
211 | string DynListener::getLine() | |
212 | { | |
093d83fd BH |
213 | vector<char> mesg; |
214 | mesg.resize(1024000); | |
215 | ||
192a5a9b | 216 | ssize_t len; |
12c86877 | 217 | |
040712e0 BH |
218 | ComboAddress remote; |
219 | socklen_t remlen=remote.getSocklen(); | |
220 | ||
221 | if(d_nonlocal) { | |
803ca684 BH |
222 | for(;;) { |
223 | d_client=accept(d_s,(sockaddr*)&remote,&remlen); | |
224 | if(d_client<0) { | |
4957a608 | 225 | if(errno!=EINTR) |
a702a96c | 226 | g_log<<Logger::Error<<"Unable to accept controlsocket connection ("<<d_s<<"): "<<stringerror()<<endl; |
4957a608 | 227 | continue; |
803ca684 | 228 | } |
040712e0 | 229 | |
64bcb6be | 230 | if(d_tcp && !d_tcprange.match(&remote)) { // checks if the remote is within the permitted range. |
e6a9dde5 | 231 | g_log<<Logger::Error<<"Access denied to remote "<<remote.toString()<<" because not allowed"<<endl; |
4957a608 BH |
232 | writen2(d_client, "Access denied to "+remote.toString()+"\n"); |
233 | close(d_client); | |
234 | continue; | |
e81974aa BH |
235 | } |
236 | ||
dd7da6cd | 237 | std::shared_ptr<FILE> fp=std::shared_ptr<FILE>(fdopen(dup(d_client), "r"), fclose); |
040712e0 | 238 | if(d_tcp) { |
4957a608 | 239 | if(!fgets(&mesg[0], mesg.size(), fp.get())) { |
a702a96c | 240 | g_log<<Logger::Error<<"Unable to receive password from controlsocket ("<<d_client<<"): "<<stringerror()<<endl; |
4957a608 BH |
241 | close(d_client); |
242 | continue; | |
243 | } | |
244 | string password(&mesg[0]); | |
245 | boost::trim(password); | |
246 | if(password.empty() || password!=arg()["tcp-control-secret"]) { | |
e6a9dde5 | 247 | g_log<<Logger::Error<<"Wrong password on TCP control socket"<<endl; |
4957a608 BH |
248 | writen2(d_client, "Wrong password"); |
249 | ||
250 | close(d_client); | |
251 | continue; | |
252 | } | |
040712e0 | 253 | } |
ce19a815 | 254 | errno=0; |
040712e0 | 255 | if(!fgets(&mesg[0], mesg.size(), fp.get())) { |
ce19a815 | 256 | if(errno) |
a702a96c | 257 | g_log<<Logger::Error<<"Unable to receive line from controlsocket ("<<d_client<<"): "<<stringerror()<<endl; |
4957a608 BH |
258 | close(d_client); |
259 | continue; | |
803ca684 | 260 | } |
093d83fd | 261 | |
040712e0 | 262 | if(strlen(&mesg[0]) == mesg.size()) { |
e6a9dde5 | 263 | g_log<<Logger::Error<<"Line on controlsocket ("<<d_client<<") was too long"<<endl; |
4957a608 BH |
264 | close(d_client); |
265 | continue; | |
093d83fd | 266 | } |
803ca684 | 267 | break; |
cc3afe25 | 268 | } |
12c86877 BH |
269 | } |
270 | else { | |
271 | if(isatty(0)) | |
a652d270 | 272 | if(write(1, "% ", 2) !=2) |
3f81d239 | 273 | throw PDNSException("Writing to console: "+stringerror()); |
e67beab1 | 274 | if((len=read(0, &mesg[0], mesg.size())) < 0) |
3f81d239 | 275 | throw PDNSException("Reading from the control pipe: "+stringerror()); |
12c86877 | 276 | else if(len==0) |
3f81d239 | 277 | throw PDNSException("Guardian exited - going down as well"); |
093d83fd | 278 | |
192a5a9b | 279 | if(static_cast<size_t>(len) == mesg.size()) |
3f81d239 | 280 | throw PDNSException("Line on control console was too long"); |
c7efa8ff | 281 | |
093d83fd | 282 | mesg[len]=0; |
12c86877 | 283 | } |
192a5a9b | 284 | |
093d83fd | 285 | return &mesg[0]; |
12c86877 BH |
286 | } |
287 | ||
e37300dc | 288 | void DynListener::sendlines(const string &l) |
12c86877 | 289 | { |
040712e0 | 290 | if(d_nonlocal) { |
cc3afe25 BH |
291 | unsigned int sent=0; |
292 | int ret; | |
040712e0 BH |
293 | while(sent < l.length()) { |
294 | ret=send(d_client, l.c_str()+sent, l.length()-sent, 0); | |
295 | ||
cc3afe25 | 296 | if(ret<0 || !ret) { |
e6a9dde5 | 297 | g_log<<Logger::Error<<"Error sending data to pdns_control: "<<stringerror()<<endl; |
4957a608 | 298 | break; |
cc3afe25 BH |
299 | } |
300 | sent+=ret; | |
301 | } | |
302 | close(d_client); | |
c7efa8ff | 303 | } else { |
e37300dc RA |
304 | string lines=l; |
305 | if(!lines.empty() && lines[lines.length()-1] != '\n') | |
306 | lines.append("\n"); | |
307 | lines.append(1, '\0'); | |
308 | lines.append(1, '\n'); | |
309 | if((unsigned int)write(1, lines.c_str(), lines.length()) != lines.length()) | |
e6a9dde5 | 310 | g_log<<Logger::Error<<"Error sending data to console: "<<stringerror()<<endl; |
12c86877 BH |
311 | } |
312 | } | |
313 | ||
3c90953e | 314 | void DynListener::registerFunc(const string &name, g_funk_t *gf, const string &usage, const string &args) |
12c86877 | 315 | { |
3c90953e PD |
316 | g_funkwithusage_t e = {gf, args, usage}; |
317 | s_funcdb[name]=e; | |
12c86877 BH |
318 | } |
319 | ||
320 | void DynListener::registerRestFunc(g_funk_t *gf) | |
321 | { | |
040712e0 | 322 | s_restfunc=gf; |
12c86877 BH |
323 | } |
324 | ||
325 | void DynListener::theListener() | |
326 | { | |
9092419e RG |
327 | setThreadName("pdns/ctrlListen"); |
328 | ||
12c86877 | 329 | try { |
e67beab1 | 330 | signal(SIGPIPE,SIG_IGN); |
12c86877 | 331 | |
040712e0 | 332 | for(int n=0;;++n) { |
12c86877 | 333 | string line=getLine(); |
040712e0 | 334 | boost::trim_right(line); |
12c86877 BH |
335 | |
336 | vector<string>parts; | |
337 | stringtok(parts,line," "); | |
338 | if(parts.empty()) { | |
e37300dc | 339 | sendlines("Empty line"); |
4957a608 | 340 | continue; |
12c86877 BH |
341 | } |
342 | ||
eb943953 KM |
343 | try { |
344 | parts[0] = toUpper( parts[0] ); | |
345 | if(s_funcdb.count(parts[0])) | |
346 | sendlines((*(s_funcdb[parts[0]].func))(parts,d_ppid)); | |
347 | else if (parts[0] == "HELP") | |
348 | sendlines(getHelp()); | |
349 | else if(s_restfunc) | |
350 | sendlines((*s_restfunc)(parts,d_ppid)); | |
351 | else | |
352 | sendlines("Unknown command: '"+parts[0]+"'"); | |
353 | } | |
354 | catch(PDNSException &AE) { | |
e6a9dde5 | 355 | g_log<<Logger::Error<<"Non-fatal error in control listener command '"<<line<<"': "<<AE.reason<<endl; |
eb943953 KM |
356 | } |
357 | catch(string &E) { | |
e6a9dde5 | 358 | g_log<<Logger::Error<<"Non-fatal error 2 in control listener command '"<<line<<"': "<<E<<endl; |
eb943953 KM |
359 | } |
360 | catch(std::exception& e) { | |
e6a9dde5 | 361 | g_log<<Logger::Error<<"Non-fatal STL error in control listener command '"<<line<<"': "<<e.what()<<endl; |
eb943953 KM |
362 | } |
363 | catch(...) { | |
e6a9dde5 | 364 | g_log<<Logger::Error<<"Non-fatal error in control listener command '"<<line<<"': unknown exception occurred"<<endl; |
eb943953 | 365 | } |
12c86877 BH |
366 | } |
367 | } | |
c7efa8ff | 368 | catch(PDNSException &AE) { |
e6a9dde5 | 369 | g_log<<Logger::Error<<"Fatal error in control listener: "<<AE.reason<<endl; |
c7efa8ff RA |
370 | } |
371 | catch(string &E) { | |
e6a9dde5 | 372 | g_log<<Logger::Error<<"Fatal error 2 in control listener: "<<E<<endl; |
c7efa8ff RA |
373 | } |
374 | catch(std::exception& e) { | |
e6a9dde5 | 375 | g_log<<Logger::Error<<"Fatal STL error in control listener: "<<e.what()<<endl; |
c7efa8ff RA |
376 | } |
377 | catch(...) { | |
e6a9dde5 | 378 | g_log<<Logger::Error<<"Fatal: unknown exception in control listener occurred"<<endl; |
c7efa8ff | 379 | } |
12c86877 BH |
380 | } |
381 | ||
3c90953e PD |
382 | |
383 | string DynListener::getHelp() | |
384 | { | |
385 | vector<string> funcs; | |
386 | string rest; | |
387 | ||
388 | // s_restfunc, when in guardian mode, is the function that | |
389 | // can pass commands on to the guarded instance | |
390 | // we just pass it HELP and merge it with our own list | |
391 | if(s_restfunc) | |
392 | { | |
393 | vector<string> parts; | |
394 | parts.push_back("HELP"); | |
395 | rest=((*s_restfunc)(parts,d_ppid)); | |
396 | boost::split(funcs, rest, boost::is_any_of("\n")); | |
397 | } | |
398 | ||
399 | const boost::format fmter("%|-32| %||"); | |
400 | ||
c7efa8ff | 401 | for(g_funkdb_t::const_iterator i=s_funcdb.begin();i!=s_funcdb.end();++i) { |
3c90953e PD |
402 | funcs.push_back(str(boost::format(fmter) % (toLower(i->first)+" "+i->second.args) % i->second.usage)); |
403 | } | |
404 | sort(funcs.begin(), funcs.end()); | |
405 | ||
406 | // hack: this removes the duplicate quit method | |
36926e31 | 407 | funcs.resize(unique(funcs.begin(), funcs.end()) - funcs.begin()); |
3c90953e | 408 | return boost::join(funcs, "\n"); |
06dbd1bd | 409 | } |