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