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