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