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