1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <pipes.h>
26 #include <rlist.h>
27 #include <buffer.h>
28 #include <signals.h>
29 #include <string_lib.h>
30
PipeTypeIsOk(const char * type)31 bool PipeTypeIsOk(const char *type)
32 {
33 if (type[0] != 'r' && type[0] != 'w')
34 {
35 return false;
36 }
37 else if (type[1] != 't' && type[1] != '+')
38 {
39 if (type[1] == '\0')
40 {
41 return true;
42 }
43 else
44 {
45 return false;
46 }
47 }
48 else if (type[2] == '\0' || type[2] == 't')
49 {
50 return true;
51 }
52 else
53 {
54 return false;
55 }
56 }
57
58 /*******************************************************************/
59 /* Pipe read/write interface, originally in package modules */
60 /*******************************************************************/
61
PipeReadData(const IOData * io,int pipe_timeout_secs,int pipe_termination_check_secs)62 Rlist *PipeReadData(const IOData *io, int pipe_timeout_secs, int pipe_termination_check_secs)
63 {
64 char buff[CF_BUFSIZE] = {0};
65
66 Buffer *data = BufferNew();
67 if (!data)
68 {
69 Log(LOG_LEVEL_VERBOSE,
70 "Unable to allocate buffer for handling pipe responses.");
71 return NULL;
72 }
73
74 int timeout_seconds_left = pipe_timeout_secs;
75
76 while (!IsPendingTermination() && timeout_seconds_left > 0)
77 {
78 int fd = PipeIsReadWriteReady(io, pipe_termination_check_secs);
79
80 if (fd < 0)
81 {
82 Log(LOG_LEVEL_DEBUG,
83 "Error reading data from application pipe %d", fd);
84 break;
85 }
86 else if (fd == io->read_fd)
87 {
88 ssize_t res = read(fd, buff, sizeof(buff) - 1);
89 if (res == -1)
90 {
91 if (errno == EINTR)
92 {
93 continue;
94 }
95 else
96 {
97 Log(LOG_LEVEL_ERR,
98 "Unable to read output from application pipe: %s",
99 GetErrorStr());
100 BufferDestroy(data);
101 return NULL;
102 }
103 }
104 else if (res == 0) /* reached EOF */
105 {
106 break;
107 }
108 Log(LOG_LEVEL_DEBUG, "Data read from application pipe: %zd [%s]",
109 res, buff);
110
111 BufferAppendString(data, buff);
112 memset(buff, 0, sizeof(buff));
113 }
114 else if (fd == 0) /* timeout */
115 {
116 timeout_seconds_left -= pipe_termination_check_secs;
117 continue;
118 }
119 }
120
121 char *read_string = BufferClose(data);
122
123 #ifdef __MINGW32__
124 bool detect_crlf = true;
125 #else
126 bool detect_crlf = false;
127 #endif
128
129 Rlist *response_lines = RlistFromStringSplitLines(read_string, detect_crlf);
130 free(read_string);
131
132 return response_lines;
133 }
134
PipeWrite(IOData * io,const char * data)135 ssize_t PipeWrite(IOData *io, const char *data)
136 {
137 /* If there is nothing to write close writing end of pipe. */
138 if (data == NULL || strlen(data) == 0)
139 {
140 if (io->write_fd >= 0)
141 {
142 cf_pclose_full_duplex_side(io->write_fd);
143 io->write_fd = -1;
144 }
145 return 0;
146 }
147
148 ssize_t wrt = write(io->write_fd, data, strlen(data));
149
150 /* Make sure to close write_fd after sending all data. */
151 if (io->write_fd >= 0)
152 {
153 cf_pclose_full_duplex_side(io->write_fd);
154 io->write_fd = -1;
155 }
156 return wrt;
157 }
158
PipeWriteData(const char * base_cmd,const char * args,const char * data)159 int PipeWriteData(const char *base_cmd, const char *args, const char *data)
160 {
161 assert(base_cmd);
162 assert(args);
163
164 char *command = StringFormat("%s %s", base_cmd, args);
165 IOData io = cf_popen_full_duplex(command, false, true);
166 free(command);
167
168 if (io.write_fd == -1 || io.read_fd == -1)
169 {
170 Log(LOG_LEVEL_VERBOSE, "Error occurred while opening pipes for "
171 "communication with application '%s'.", base_cmd);
172 return -1;
173 }
174
175 Log(LOG_LEVEL_DEBUG, "Opened fds %d and %d for command '%s'.",
176 io.read_fd, io.write_fd, args);
177
178 int res = 0;
179 int written = PipeWrite(&io, data);
180 if (written < 0)
181 {
182 Log(LOG_LEVEL_ERR, "Failed to write to pipe (fd %d): %s",
183 io.write_fd, GetErrorStr());
184 res = -1;
185 }
186 else if ((size_t) written != strlen(data))
187 {
188 Log(LOG_LEVEL_VERBOSE,
189 "Was not able to send whole data to application '%s'.",
190 base_cmd);
191 res = -1;
192 }
193
194 /* If script returns non 0 status */
195 int close = cf_pclose_full_duplex(&io);
196 if (close != EXIT_SUCCESS)
197 {
198 Log(LOG_LEVEL_VERBOSE,
199 "Application '%s' returned with non zero return code: %d",
200 base_cmd, close);
201 res = -1;
202 }
203 return res;
204 }
205
206 /* In some cases the response is expected to be not filled out. Some requests
207 will have response filled only in case of errors. */
PipeReadWriteData(const char * base_cmd,const char * args,const char * request,Rlist ** response,int pipe_timeout_secs,int pipe_termination_check_secs)208 int PipeReadWriteData(const char *base_cmd, const char *args, const char *request,
209 Rlist **response, int pipe_timeout_secs, int pipe_termination_check_secs)
210 {
211 assert(base_cmd);
212 assert(args);
213
214 char *command = StringFormat("%s %s", base_cmd, args);
215 IOData io = cf_popen_full_duplex(command, false, true);
216
217 if (io.write_fd == -1 || io.read_fd == -1)
218 {
219 Log(LOG_LEVEL_INFO, "Some error occurred while communicating with %s", command);
220 free(command);
221 return -1;
222 }
223
224 Log(LOG_LEVEL_DEBUG, "Opened fds %d and %d for command '%s'.",
225 io.read_fd, io.write_fd, command);
226
227 int written = PipeWrite(&io, request);
228 if (written < 0) {
229 Log(LOG_LEVEL_ERR, "Failed to write to pipe (fd %d): %s",
230 io.write_fd, GetErrorStr());
231 return -1;
232 }
233 else if ((size_t) written != strlen(request))
234 {
235 Log(LOG_LEVEL_VERBOSE, "Couldn't send whole data to application '%s'.",
236 base_cmd);
237 free(command);
238 return -1;
239 }
240
241 /* We can have some error message here. */
242 Rlist *res = PipeReadData(&io, pipe_timeout_secs, pipe_termination_check_secs);
243
244 /* If script returns non 0 status */
245 int close = cf_pclose_full_duplex(&io);
246 if (close != EXIT_SUCCESS)
247 {
248 Log(LOG_LEVEL_VERBOSE,
249 "Command '%s' returned with non zero return code: %d",
250 command, close);
251 free(command);
252 RlistDestroy(res);
253 return -1;
254 }
255
256 free(command);
257 *response = res;
258 return 0;
259 }
260
cf_popen_full_duplex_streams(const char * command,bool capture_stderr,bool require_full_path)261 IOData cf_popen_full_duplex_streams(
262 const char *command, bool capture_stderr, bool require_full_path)
263 {
264 IOData ret = cf_popen_full_duplex(
265 command, capture_stderr, require_full_path);
266
267 // On windows, these streams are already set up correctly:
268 if (ret.read_stream == NULL)
269 {
270 ret.read_stream = fdopen(ret.read_fd, "r");
271 }
272 if (ret.write_stream == NULL)
273 {
274 ret.write_stream = fdopen(ret.write_fd, "w");
275 }
276
277 return ret;
278 }
279