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 <exec_tools.h>
26
27 #include <files_names.h>
28 #include <files_interfaces.h>
29 #include <pipes.h>
30 #include <string_lib.h>
31 #include <misc_lib.h>
32 #include <file_lib.h>
33 #include <generic_agent.h> // CloseLog
34
35 /********************************************************************/
36
GetExecOutput(const char * command,char ** buffer,size_t * buffer_size,ShellType shell,OutputSelect output_select,int * ret_out)37 bool GetExecOutput(const char *command, char **buffer, size_t *buffer_size, ShellType shell, OutputSelect output_select, int *ret_out)
38 /* Buffer initially contains whole exec string */
39 {
40 FILE *pp;
41
42 if (shell == SHELL_TYPE_USE)
43 {
44 pp = cf_popen_sh_select(command, "rt", output_select);
45 }
46 else if (shell == SHELL_TYPE_POWERSHELL)
47 {
48 #ifdef __MINGW32__
49 pp = cf_popen_powershell_select(command, "rt", output_select);
50 #else // !__MINGW32__
51 Log(LOG_LEVEL_ERR, "Powershell is only supported on Windows");
52 return false;
53 #endif // __MINGW32__
54 }
55 else
56 {
57 pp = cf_popen_select(command, "rt", output_select);
58 }
59
60 if (pp == NULL)
61 {
62 Log(LOG_LEVEL_ERR, "Couldn't open pipe to command '%s'. (cf_popen: %s)", command, GetErrorStr());
63 return false;
64 }
65
66 size_t offset = 0;
67 size_t line_size = CF_EXPANDSIZE;
68 size_t attempted_size = 0;
69 char *line = xcalloc(1, line_size);
70
71 while (*buffer_size < CF_MAXSIZE)
72 {
73 ssize_t res = CfReadLine(&line, &line_size, pp);
74 if (res == -1)
75 {
76 if (!feof(pp))
77 {
78 Log(LOG_LEVEL_ERR, "Unable to read output of command '%s'. (fread: %s)", command, GetErrorStr());
79 cf_pclose(pp);
80 free(line);
81 return false;
82 }
83 else
84 {
85 break;
86 }
87 }
88
89 if ((attempted_size = snprintf(*buffer + offset, *buffer_size - offset, "%s\n", line)) >= *buffer_size - offset)
90 {
91 *buffer_size += (attempted_size > CF_EXPANDSIZE ? attempted_size : CF_EXPANDSIZE);
92 *buffer = xrealloc(*buffer, *buffer_size);
93 snprintf(*buffer + offset, *buffer_size - offset, "%s\n", line);
94 }
95
96 offset += strlen(line) + 1;
97 }
98
99 if (offset > 0)
100 {
101 if (Chop(*buffer, *buffer_size) == -1)
102 {
103 Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator");
104 }
105 }
106
107 Log(LOG_LEVEL_DEBUG, "GetExecOutput got '%s'", *buffer);
108
109 if (ret_out != NULL)
110 {
111 *ret_out = cf_pclose(pp);
112 }
113 else
114 {
115 cf_pclose(pp);
116 }
117
118 free(line);
119 return true;
120 }
121
122 /**********************************************************************/
123
ActAsDaemon()124 void ActAsDaemon()
125 {
126 int fd;
127
128 #ifdef HAVE_SETSID
129 if (setsid() == (pid_t) -1)
130 {
131 Log(LOG_LEVEL_WARNING,
132 "Failed to become a session leader while daemonising (setsid: %s)",
133 GetErrorStr());
134 }
135 #endif
136
137 CloseNetwork();
138 CloseLog();
139
140 fflush(NULL);
141
142 /* Close descriptors 0,1,2 and reopen them with /dev/null. */
143
144 fd = open(NULLFILE, O_RDWR, 0);
145 if (fd == -1)
146 {
147 Log(LOG_LEVEL_WARNING, "Could not open '%s', "
148 "stdin/stdout/stderr are still open (open: %s)",
149 NULLFILE, GetErrorStr());
150 }
151 else
152 {
153 if (dup2(fd, STDIN_FILENO) == -1)
154 {
155 Log(LOG_LEVEL_WARNING,
156 "Could not close stdin while daemonising (dup2: %s)",
157 GetErrorStr());
158 }
159
160 if (dup2(fd, STDOUT_FILENO) == -1)
161 {
162 Log(LOG_LEVEL_WARNING,
163 "Could not close stdout while daemonising (dup2: %s)",
164 GetErrorStr());
165 }
166
167 if (dup2(fd, STDERR_FILENO) == -1)
168 {
169 Log(LOG_LEVEL_WARNING,
170 "Could not close stderr while daemonising (dup2: %s)",
171 GetErrorStr());
172 }
173
174 if (fd > STDERR_FILENO)
175 {
176 close(fd);
177 }
178 }
179
180 if (chdir("/"))
181 {
182 Log(LOG_LEVEL_WARNING,
183 "Failed to chdir into '/' directory while daemonising (chdir: %s)",
184 GetErrorStr());
185 }
186 }
187
188 /**********************************************************************/
189
190 /**
191 * Split the command string like "/bin/echo -n Hi!" in two parts -- the
192 * executable ("/bin/echo") and the arguments for the executable ("-n Hi!").
193 *
194 * @param[in] comm the whole command to split
195 * @param[out] exec pointer to **a newly allocated** string with the executable
196 * @param[out] args pointer to **a newly allocated** string with the args
197 *
198 * @note Whitespace between the executable and the arguments is skipped.
199 */
ArgGetExecutableAndArgs(const char * comm,char ** exec,char ** args)200 void ArgGetExecutableAndArgs(const char *comm, char **exec, char **args)
201 {
202 const char *s = comm;
203 while (*s != '\0')
204 {
205 const char *end = NULL;
206
207 if (isspace((int)*s)) /* Skip whitespace */
208 {
209 s++;
210 continue;
211 }
212
213 switch (*s)
214 {
215 case '"': /* Look for matching quote */
216 case '\'':
217 case '`':
218 {
219 char delim = *(s++); /* Skip first delimeter */
220
221 end = strchr(s, delim);
222 break;
223 }
224 default: /* Look for whitespace */
225 end = strpbrk(s, " \f\n\r\t\v");
226 break;
227 }
228
229 if (end == NULL) /* Delimeter was not found, remaining string is the executable */
230 {
231 *exec = xstrdup(s);
232 *args = NULL;
233 return;
234 }
235 else
236 {
237 assert(end > s);
238 const size_t length = end - s;
239 *exec = xstrndup(s, length);
240
241 const char *args_start = end;
242 if (*(args_start + 1) != '\0')
243 {
244 args_start++; /* Skip second delimeter */
245 args_start += strspn(args_start, " \f\n\r\t\v"); /* Skip whitespace */
246 *args = xstrdup(args_start);
247 }
248 else
249 {
250 *args = NULL;
251 }
252 return;
253 }
254 }
255
256 /* was not able to parse/split the command */
257 *exec = NULL;
258 *args = NULL;
259 return;
260 }
261
262 #define INITIAL_ARGS 8
263
ArgSplitCommand(const char * comm)264 char **ArgSplitCommand(const char *comm)
265 {
266 const char *s = comm;
267
268 int argc = 0;
269 int argslen = INITIAL_ARGS;
270 char **args = xmalloc(argslen * sizeof(char *));
271
272 while (*s != '\0')
273 {
274 const char *end;
275 char *arg;
276
277 if (isspace((int)*s)) /* Skip whitespace */
278 {
279 s++;
280 continue;
281 }
282
283 switch (*s)
284 {
285 case '"': /* Look for matching quote */
286 case '\'':
287 case '`':
288 {
289 char delim = *s++; /* Skip first delimeter */
290
291 end = strchr(s, delim);
292 break;
293 }
294 default: /* Look for whitespace */
295 end = strpbrk(s, " \f\n\r\t\v");
296 break;
297 }
298
299 if (end == NULL) /* Delimeter was not found, remaining string is the argument */
300 {
301 arg = xstrdup(s);
302 s += strlen(arg);
303 }
304 else
305 {
306 arg = xstrndup(s, end - s);
307 s = end;
308 if ((*s == '"') || (*s == '\'') || (*s == '`')) /* Skip second delimeter */
309 s++;
310 }
311
312 /* Argument */
313
314 if (argc == argslen)
315 {
316 argslen *= 2;
317 args = xrealloc(args, argslen * sizeof(char *));
318 }
319
320 args[argc++] = arg;
321 }
322
323 /* Trailing NULL */
324
325 if (argc == argslen)
326 {
327 argslen += 1;
328 args = xrealloc(args, argslen * sizeof(char *));
329 }
330 args[argc++] = NULL;
331
332 return args;
333 }
334
335 /**********************************************************************/
336
ArgFree(char ** args)337 void ArgFree(char **args)
338 {
339 if (args != NULL)
340 {
341 for (char **arg = args; *arg; ++arg)
342 {
343 free(*arg);
344 }
345 free(args);
346 }
347 }
348