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  */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "remotebackend.hh"
26 
PipeConnector(std::map<std::string,std::string> optionsMap)27 PipeConnector::PipeConnector(std::map<std::string, std::string> optionsMap) :
28   d_pid(-1)
29 {
30   if (optionsMap.count("command") == 0) {
31     g_log << Logger::Error << "Cannot find 'command' option in connection string" << endl;
32     throw PDNSException();
33   }
34   this->command = optionsMap.find("command")->second;
35   this->options = optionsMap;
36   d_timeout = 2000;
37 
38   if (optionsMap.find("timeout") != optionsMap.end()) {
39     d_timeout = std::stoi(optionsMap.find("timeout")->second);
40   }
41 
42   d_fd1[0] = d_fd1[1] = -1;
43   d_fd2[0] = d_fd2[1] = -1;
44 }
45 
~PipeConnector()46 PipeConnector::~PipeConnector()
47 {
48   int status;
49   // just in case...
50   if (d_pid == -1)
51     return;
52 
53   if (!waitpid(d_pid, &status, WNOHANG)) {
54     kill(d_pid, 9);
55     waitpid(d_pid, &status, 0);
56   }
57 
58   if (d_fd1[1]) {
59     close(d_fd1[1]);
60   }
61 }
62 
launch()63 void PipeConnector::launch()
64 {
65   // no relaunch
66   if (d_pid > 0 && checkStatus())
67     return;
68 
69   std::vector<std::string> v;
70   split(v, command, boost::is_any_of(" "));
71 
72   std::vector<const char*> argv(v.size() + 1);
73   argv[v.size()] = 0;
74 
75   for (size_t n = 0; n < v.size(); n++)
76     argv[n] = v[n].c_str();
77 
78   signal(SIGPIPE, SIG_IGN);
79 
80   if (access(argv[0], X_OK)) // check before fork so we can throw
81     throw PDNSException("Command '" + string(argv[0]) + "' cannot be executed: " + stringerror());
82 
83   if (pipe(d_fd1) < 0 || pipe(d_fd2) < 0)
84     throw PDNSException("Unable to open pipe for coprocess: " + string(strerror(errno)));
85 
86   if ((d_pid = fork()) < 0)
87     throw PDNSException("Unable to fork for coprocess: " + stringerror());
88   else if (d_pid > 0) { // parent speaking
89     close(d_fd1[0]);
90     setCloseOnExec(d_fd1[1]);
91     close(d_fd2[1]);
92     setCloseOnExec(d_fd2[0]);
93     if (!(d_fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(d_fd2[0], "r"), fclose)))
94       throw PDNSException("Unable to associate a file pointer with pipe: " + stringerror());
95     if (d_timeout)
96       setbuf(d_fp.get(), 0); // no buffering please, confuses poll
97   }
98   else if (!d_pid) { // child
99     signal(SIGCHLD, SIG_DFL); // silence a warning from perl
100     close(d_fd1[1]);
101     close(d_fd2[0]);
102 
103     if (d_fd1[0] != 0) {
104       dup2(d_fd1[0], 0);
105       close(d_fd1[0]);
106     }
107 
108     if (d_fd2[1] != 1) {
109       dup2(d_fd2[1], 1);
110       close(d_fd2[1]);
111     }
112 
113     // stdin & stdout are now connected, fire up our coprocess!
114 
115     if (execv(argv[0], const_cast<char* const*>(argv.data())) < 0) // now what
116       exit(123);
117 
118     /* not a lot we can do here. We shouldn't return because that will leave a forked process around.
119        no way to log this either - only thing we can do is make sure that our parent catches this soonest! */
120   }
121 
122   Json::array parameters;
123   Json msg = Json(Json::object{
124     {"method", "initialize"},
125     {"parameters", Json(options)},
126   });
127 
128   this->send(msg);
129   msg = nullptr;
130   if (this->recv(msg) == false) {
131     g_log << Logger::Error << "Failed to initialize coprocess" << std::endl;
132   }
133 }
134 
send_message(const Json & input)135 int PipeConnector::send_message(const Json& input)
136 {
137   auto line = input.dump();
138   launch();
139 
140   line.append(1, '\n');
141 
142   unsigned int sent = 0;
143   int bytes;
144 
145   // writen routine - socket may not accept al data in one go
146   while (sent < line.size()) {
147     bytes = write(d_fd1[1], line.c_str() + sent, line.length() - sent);
148     if (bytes < 0)
149       throw PDNSException("Writing to coprocess failed: " + std::string(strerror(errno)));
150 
151     sent += bytes;
152   }
153   return sent;
154 }
155 
recv_message(Json & output)156 int PipeConnector::recv_message(Json& output)
157 {
158   std::string receive;
159   std::string err;
160   std::string s_output;
161   launch();
162 
163   while (1) {
164     receive.clear();
165     if (d_timeout) {
166       int ret = waitForData(fileno(d_fp.get()), 0, d_timeout * 1000);
167       if (ret < 0)
168         throw PDNSException("Error waiting on data from coprocess: " + stringerror());
169       if (!ret)
170         throw PDNSException("Timeout waiting for data from coprocess");
171     }
172 
173     if (!stringfgets(d_fp.get(), receive))
174       throw PDNSException("Child closed pipe");
175 
176     s_output.append(receive);
177     // see if it can be parsed
178     output = Json::parse(s_output, err);
179     if (output != nullptr)
180       return s_output.size();
181   }
182   return 0;
183 }
184 
checkStatus()185 bool PipeConnector::checkStatus()
186 {
187   int status;
188   int ret = waitpid(d_pid, &status, WNOHANG);
189   if (ret < 0)
190     throw PDNSException("Unable to ascertain status of coprocess " + itoa(d_pid) + " from " + itoa(getpid()) + ": " + string(strerror(errno)));
191   else if (ret) {
192     if (WIFEXITED(status)) {
193       int exitStatus = WEXITSTATUS(status);
194       throw PDNSException("Coprocess exited with code " + itoa(exitStatus));
195     }
196     if (WIFSIGNALED(status)) {
197       int sig = WTERMSIG(status);
198       string reason = "CoProcess died on receiving signal " + itoa(sig);
199 #ifdef WCOREDUMP
200       if (WCOREDUMP(status))
201         reason += ". Dumped core";
202 #endif
203 
204       throw PDNSException(reason);
205     }
206   }
207   return true;
208 }
209