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