1 /*
2   Copyright 2020 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 <signal.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <string.h>
30 
31 char *SPAWN_PROCESS = NULL;
32 char *SPAWN_PROCESS_ON_SIGNAL = NULL;
33 int REFUSE_TO_DIE = 0;
34 char **NEXT_PROCESS_ARGV = NULL;
35 int NEXT_PROCESS_ARGC = 0;
36 
37 char *PIDFILE = NULL;
38 
spawn_process(const char * program)39 void spawn_process(const char *program)
40 {
41     pid_t pid = fork();
42     if (pid < 0)
43     {
44         printf("Could not fork\n");
45         exit(1);
46     }
47     else if (pid == 0)
48     {
49         const char * args[NEXT_PROCESS_ARGC + 2]; // One for program and one for NULL.
50         args[0] = program;
51         for (int c = 1; c <= NEXT_PROCESS_ARGC; c++)
52         {
53             args[c] = NEXT_PROCESS_ARGV[c-1];
54         }
55         args[NEXT_PROCESS_ARGC + 1] = NULL;
56         execv(program, (char * const *)args);
57 
58         printf("Could not execute %s\n", program);
59         exit(1);
60     }
61 }
62 
signal_handler(int signal)63 void signal_handler(int signal)
64 {
65     (void)signal;
66     // No-op. All the handling is in the main loop
67 }
68 
process_signal(void)69 void process_signal(void)
70 {
71     if (SPAWN_PROCESS_ON_SIGNAL)
72     {
73         // Insert artificial delay so that a match for the agent right after
74         // attempting to kill the daemon will not work. Trying to make it as
75         // difficult as possible for the killing script! :-)
76         sleep(1);
77 
78         spawn_process(SPAWN_PROCESS_ON_SIGNAL);
79     }
80 
81     if (!REFUSE_TO_DIE)
82     {
83         if (PIDFILE)
84         {
85             unlink(PIDFILE);
86         }
87         exit(0);
88     }
89 }
90 
main(int argc,char ** argv)91 int main(int argc, char **argv)
92 {
93     sigset_t mask;
94     sigemptyset(&mask);
95     struct sigaction sig;
96     memset(&sig, 0, sizeof(sig));
97     sig.sa_handler = &signal_handler;
98     sig.sa_mask = mask;
99     const int signals[] = { SIGTERM, SIGQUIT, SIGINT, SIGHUP, 0 };
100 
101     for (int c = 0; signals[c]; c++)
102     {
103         if (sigaction(signals[c], &sig, NULL) != 0)
104         {
105             printf("Unable to set signal handlers\n");
106             return 1;
107         }
108     }
109 
110     for (int c = 1; c < argc; c++)
111     {
112         if (strcmp(argv[c], "--spawn-process") == 0)
113         {
114             if (++c + 1 >= argc)
115             {
116                 printf("%s requires two arguments\n", argv[c]);
117                 return 1;
118             }
119             // The reason for splitting the argument into two parts is to avoid
120             // a false match on a process just because the it has an argument
121             // containing the string we are looking for.
122             SPAWN_PROCESS = malloc(strlen(argv[c]) + strlen(argv[c+1]) + 2);
123             sprintf(SPAWN_PROCESS, "%s/%s", argv[c], argv[c+1]);
124 
125             c++;
126         }
127         else if (strcmp(argv[c], "--spawn-process-on-signal") == 0)
128         {
129             if (++c + 1 >= argc)
130             {
131                 printf("%s requires two arguments\n", argv[c]);
132                 return 1;
133             }
134             // See comment for SPAWN_PROCESS.
135             SPAWN_PROCESS_ON_SIGNAL = malloc(strlen(argv[c]) + strlen(argv[c+1]) + 2);
136             sprintf(SPAWN_PROCESS_ON_SIGNAL, "%s/%s", argv[c], argv[c+1]);
137 
138             c++;
139         }
140         else if (strcmp(argv[c], "--refuse-to-die") == 0)
141         {
142             REFUSE_TO_DIE = 1;
143         }
144         else if (strcmp(argv[c], "--pass-to-next-process") == 0)
145         {
146             // Stops argument processing and passes all remaining arguments to
147             // the spawned process instead.
148             NEXT_PROCESS_ARGV = &argv[++c];
149             NEXT_PROCESS_ARGC = argc - c;
150             break;
151         }
152         else
153         {
154             printf("Unknown argument: %s\n", argv[c]);
155             return 1;
156         }
157     }
158 
159     const char *piddir = getenv("CFTEST_PREFIX");
160     if (piddir)
161     {
162         const char *file = strrchr(argv[0], '/');
163         file++;
164         PIDFILE = malloc(strlen(piddir) + strlen(file) + 6);
165         sprintf(PIDFILE, "%s/%s.pid", piddir, file);
166     }
167 
168     pid_t child = fork();
169     if (child < 0)
170     {
171         printf("Could not fork\n");
172         exit(1);
173     }
174     else if (child > 0)
175     {
176         // Daemonize.
177         if (PIDFILE)
178         {
179             FILE *fptr = fopen(PIDFILE, "w");
180             if (!fptr)
181             {
182                 printf("Could not open file %s\n", PIDFILE);
183                 exit(1);
184             }
185             fprintf(fptr, "%d\n", (int)child);
186             fclose(fptr);
187         }
188 
189         return 0;
190     }
191 
192     if (SPAWN_PROCESS)
193     {
194         spawn_process(SPAWN_PROCESS);
195     }
196 
197     // 60 seconds of consecutive sleep will end the program, just so that we do
198     // in fact terminate if we are left over.
199     while (sleep(60) != 0)
200     {
201         // If we didn't sleep the whole time, then it must be a signal.
202         process_signal();
203     }
204 
205     return 1;
206 }
207