]>
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 | |
8bd743a8 BH |
25 | #include "coprocess.hh" |
26 | #include <stdlib.h> | |
27 | #include <unistd.h> | |
28 | #include <string> | |
dc6aa7f5 AV |
29 | #include <cerrno> |
30 | #include <csignal> | |
8bd743a8 BH |
31 | #include <string.h> |
32 | #include <sys/types.h> | |
33 | #include <sys/wait.h> | |
37079a8e | 34 | #include "pdns/utility.hh" |
2f2b0145 | 35 | #include <sys/un.h> |
21e99384 RK |
36 | #include "pdns/misc.hh" |
37 | #include "pdns/pdnsexception.hh" | |
2f2b0145 BH |
38 | #include <sys/stat.h> |
39 | #include <unistd.h> | |
40 | #include <boost/algorithm/string.hpp> | |
41 | #include <vector> | |
8bd743a8 | 42 | |
ff05c7e1 O |
43 | CoProcess::CoProcess(const string& command, int timeout, int infd, int outfd) : |
44 | d_infd(infd), d_outfd(outfd), d_timeout(timeout) | |
8bd743a8 | 45 | { |
dc593046 | 46 | split(d_params, command, boost::is_any_of(" ")); |
8bd743a8 | 47 | |
ff05c7e1 O |
48 | d_argv.resize(d_params.size() + 1); |
49 | d_argv[d_params.size()] = nullptr; | |
8bd743a8 | 50 | |
214fd99e | 51 | for (size_t n = 0; n < d_params.size(); n++) { |
ff05c7e1 | 52 | d_argv[n] = d_params[n].c_str(); |
214fd99e | 53 | } |
bc058b8b | 54 | d_pid = 0; |
8bd743a8 BH |
55 | } |
56 | ||
214fd99e | 57 | void CoProcess::launch() |
8bd743a8 | 58 | { |
8bd743a8 BH |
59 | signal(SIGPIPE, SIG_IGN); |
60 | ||
ff05c7e1 O |
61 | if (access(d_argv[0], X_OK)) // check before fork so we can throw |
62 | throw PDNSException("Command '" + string(d_argv[0]) + "' cannot be executed: " + stringerror()); | |
8bd743a8 | 63 | |
ff05c7e1 O |
64 | if (pipe(d_fd1) < 0 || pipe(d_fd2) < 0) |
65 | throw PDNSException("Unable to open pipe for coprocess: " + string(strerror(errno))); | |
8bd743a8 | 66 | |
ff05c7e1 O |
67 | if ((d_pid = fork()) < 0) |
68 | throw PDNSException("Unable to fork for coprocess: " + stringerror()); | |
69 | else if (d_pid > 0) { // parent speaking | |
214fd99e RG |
70 | // no need to keep this around |
71 | d_argv.clear(); | |
8bd743a8 | 72 | close(d_fd1[0]); |
bc886258 | 73 | setCloseOnExec(d_fd1[1]); |
8bd743a8 | 74 | close(d_fd2[1]); |
bc886258 | 75 | setCloseOnExec(d_fd2[0]); |
214fd99e RG |
76 | |
77 | if (d_timeout) { | |
78 | setNonBlocking(d_fd2[0]); | |
79 | } | |
8bd743a8 | 80 | } |
ff05c7e1 | 81 | else if (!d_pid) { // child |
fb44deff | 82 | signal(SIGCHLD, SIG_DFL); // silence a warning from perl |
8bd743a8 BH |
83 | close(d_fd1[1]); |
84 | close(d_fd2[0]); | |
85 | ||
ff05c7e1 | 86 | if (d_fd1[0] != d_infd) { |
214fd99e | 87 | dup2(d_fd1[0], d_infd); |
8bd743a8 BH |
88 | close(d_fd1[0]); |
89 | } | |
90 | ||
ff05c7e1 | 91 | if (d_fd2[1] != d_outfd) { |
214fd99e | 92 | dup2(d_fd2[1], d_outfd); |
8bd743a8 BH |
93 | close(d_fd2[1]); |
94 | } | |
95 | ||
96 | // stdin & stdout are now connected, fire up our coprocess! | |
ff05c7e1 | 97 | if (execv(d_argv[0], const_cast<char* const*>(d_argv.data())) < 0) // now what |
8bd743a8 BH |
98 | exit(123); |
99 | ||
100 | /* not a lot we can do here. We shouldn't return because that will leave a forked process around. | |
101 | no way to log this either - only thing we can do is make sure that our parent catches this soonest! */ | |
102 | } | |
f7a23c1c | 103 | } |
8bd743a8 | 104 | |
f7a23c1c BH |
105 | CoProcess::~CoProcess() |
106 | { | |
107 | int status; | |
ff05c7e1 O |
108 | if (d_pid) { |
109 | if (!waitpid(d_pid, &status, WNOHANG)) { | |
bc058b8b PD |
110 | kill(d_pid, 9); |
111 | waitpid(d_pid, &status, 0); | |
112 | } | |
013b6194 | 113 | } |
ff05c7e1 | 114 | |
01b7bc4d | 115 | close(d_fd1[1]); |
214fd99e | 116 | close(d_fd2[0]); |
8bd743a8 BH |
117 | } |
118 | ||
119 | void CoProcess::checkStatus() | |
120 | { | |
121 | int status; | |
ff05c7e1 O |
122 | int ret = waitpid(d_pid, &status, WNOHANG); |
123 | if (ret < 0) | |
564db8c5 | 124 | throw PDNSException("Unable to ascertain status of coprocess " + std::to_string(d_pid) + " from " + std::to_string(getpid()) + ": " + string(strerror(errno))); |
ff05c7e1 O |
125 | else if (ret) { |
126 | if (WIFEXITED(status)) { | |
127 | int exitStatus = WEXITSTATUS(status); | |
564db8c5 | 128 | throw PDNSException("Coprocess exited with code " + std::to_string(exitStatus)); |
8bd743a8 | 129 | } |
ff05c7e1 O |
130 | if (WIFSIGNALED(status)) { |
131 | int sig = WTERMSIG(status); | |
564db8c5 | 132 | string reason = "CoProcess died on receiving signal " + std::to_string(sig); |
8bd743a8 | 133 | #ifdef WCOREDUMP |
ff05c7e1 O |
134 | if (WCOREDUMP(status)) |
135 | reason += ". Dumped core"; | |
8bd743a8 | 136 | #endif |
ff05c7e1 | 137 | |
3f81d239 | 138 | throw PDNSException(reason); |
8bd743a8 BH |
139 | } |
140 | } | |
141 | } | |
142 | ||
ff05c7e1 | 143 | void CoProcess::send(const string& snd) |
8bd743a8 BH |
144 | { |
145 | checkStatus(); | |
146 | string line(snd); | |
ff05c7e1 O |
147 | line.append(1, '\n'); |
148 | ||
149 | unsigned int sent = 0; | |
8bd743a8 BH |
150 | int bytes; |
151 | ||
152 | // writen routine - socket may not accept al data in one go | |
ff05c7e1 O |
153 | while (sent < line.size()) { |
154 | bytes = write(d_fd1[1], line.c_str() + sent, line.length() - sent); | |
155 | if (bytes < 0) | |
156 | throw PDNSException("Writing to coprocess failed: " + string(strerror(errno))); | |
8bd743a8 | 157 | |
ff05c7e1 | 158 | sent += bytes; |
8bd743a8 BH |
159 | } |
160 | } | |
161 | ||
ff05c7e1 | 162 | void CoProcess::receive(string& received) |
8bd743a8 | 163 | { |
000f9287 | 164 | received.clear(); |
214fd99e RG |
165 | |
166 | // we might still have some remaining data from our last read | |
167 | if (!d_remaining.empty()) { | |
000f9287 | 168 | received = std::move(d_remaining); |
8bd743a8 BH |
169 | } |
170 | ||
214fd99e RG |
171 | size_t lastPos = 0; |
172 | size_t eolPos; | |
000f9287 AT |
173 | while ((eolPos = received.find('\n', lastPos)) == std::string::npos) { |
174 | size_t existingSize = received.size(); | |
214fd99e | 175 | lastPos = existingSize; |
000f9287 AT |
176 | received.resize(existingSize + 4096); |
177 | ssize_t got = read(d_fd2[0], &received.at(existingSize), 4096); | |
214fd99e RG |
178 | if (got == 0) { |
179 | throw PDNSException("Child closed pipe"); | |
180 | } | |
181 | else if (got < 0) { | |
000f9287 | 182 | received.resize(existingSize); |
214fd99e RG |
183 | int saved = errno; |
184 | if (saved == EINTR) { | |
185 | continue; | |
186 | } | |
187 | if (saved == EAGAIN) { | |
ff05c7e1 | 188 | if (d_timeout) { |
214fd99e | 189 | int ret = waitForData(d_fd2[0], 0, d_timeout * 1000); |
ff05c7e1 O |
190 | if (ret < 0) |
191 | throw PDNSException("Error waiting on data from coprocess: " + string(strerror(saved))); | |
192 | if (!ret) | |
214fd99e RG |
193 | throw PDNSException("Timeout waiting for data from coprocess"); |
194 | } | |
195 | } | |
196 | else { | |
197 | throw PDNSException("Error reading from child's pipe:" + string(strerror(saved))); | |
198 | } | |
ff05c7e1 O |
199 | } |
200 | else { | |
000f9287 | 201 | received.resize(existingSize + static_cast<size_t>(got)); |
214fd99e RG |
202 | } |
203 | } | |
204 | ||
000f9287 | 205 | if (eolPos != received.size() - 1) { |
214fd99e | 206 | /* we have some data remaining after the first '\n', let's keep it for later */ |
000f9287 | 207 | d_remaining.append(received, eolPos + 1, received.size() - eolPos - 1); |
214fd99e RG |
208 | } |
209 | ||
000f9287 | 210 | received.resize(eolPos); |
dc593046 | 211 | boost::trim_right(received); |
8bd743a8 | 212 | } |
04e240ef | 213 | |
ff05c7e1 | 214 | void CoProcess::sendReceive(const string& snd, string& rcv) |
8bd743a8 BH |
215 | { |
216 | checkStatus(); | |
217 | send(snd); | |
218 | receive(rcv); | |
8bd743a8 | 219 | } |
2f2b0145 | 220 | |
ff05c7e1 | 221 | UnixRemote::UnixRemote(const string& path, int timeout) |
2f2b0145 | 222 | { |
f615dd15 | 223 | d_fd = socket(AF_UNIX, SOCK_STREAM, 0); |
ff05c7e1 O |
224 | if (d_fd < 0) |
225 | throw PDNSException("Unable to create UNIX domain socket: " + string(strerror(errno))); | |
2f2b0145 BH |
226 | |
227 | struct sockaddr_un remote; | |
76cb4593 | 228 | if (makeUNsockaddr(path, &remote)) |
ff05c7e1 | 229 | throw PDNSException("Unable to create UNIX domain socket: Path '" + path + "' is not a valid UNIX socket path."); |
2f2b0145 BH |
230 | |
231 | // fcntl(fd, F_SETFL, O_NONBLOCK, &sock); | |
232 | ||
ff05c7e1 O |
233 | if (connect(d_fd, (struct sockaddr*)&remote, sizeof(remote)) < 0) |
234 | unixDie("Unable to connect to remote '" + path + "' using UNIX domain socket"); | |
2f2b0145 | 235 | |
ff05c7e1 | 236 | d_fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(d_fd, "r"), fclose); |
d9dd76b0 BH |
237 | } |
238 | ||
2f2b0145 BH |
239 | void UnixRemote::send(const string& line) |
240 | { | |
241 | string nline(line); | |
242 | nline.append(1, '\n'); | |
243 | writen2(d_fd, nline); | |
244 | } | |
245 | ||
246 | void UnixRemote::receive(string& line) | |
247 | { | |
248 | line.clear(); | |
e30bc0cf | 249 | stringfgets(d_fp.get(), line); |
dc593046 | 250 | boost::trim_right(line); |
2f2b0145 BH |
251 | } |
252 | ||
ff05c7e1 | 253 | void UnixRemote::sendReceive(const string& snd, string& rcv) |
2f2b0145 BH |
254 | { |
255 | // checkStatus(); | |
256 | send(snd); | |
257 | receive(rcv); | |
258 | } | |
259 | ||
260 | bool isUnixSocket(const string& fname) | |
261 | { | |
262 | struct stat st; | |
ff05c7e1 | 263 | if (stat(fname.c_str(), &st) < 0) |
2f2b0145 BH |
264 | return false; // not a unix socket in any case ;-) |
265 | ||
266 | return (st.st_mode & S_IFSOCK) == S_IFSOCK; | |
267 | } | |
268 | ||
8bd743a8 BH |
269 | #ifdef TESTDRIVER |
270 | main() | |
271 | { | |
272 | try { | |
273 | CoProcess cp("./irc.pl"); | |
274 | string reply; | |
275 | cp.sendReceive("www.trilab.com", reply); | |
ff05c7e1 | 276 | cout << "Answered: '" << reply << "'" << endl; |
8bd743a8 | 277 | } |
ff05c7e1 O |
278 | catch (PDNSException& ae) { |
279 | cerr << ae.reason << endl; | |
8bd743a8 | 280 | } |
8bd743a8 BH |
281 | } |
282 | #endif |