1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2006-2011 Free Software Foundation Europe e.V.
5 
6    This program is Free Software; you can redistribute it and/or
7    modify it under the terms of version three of the GNU Affero General Public
8    License as published by the Free Software Foundation and included
9    in the file LICENSE.
10 
11    This program is distributed in the hope that it will be useful, but
12    WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14    Affero General Public License for more details.
15 
16    You should have received a copy of the GNU Affero General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19    02110-1301, USA.
20 */
21 /*
22  * Manipulation routines for RunScript list
23  *
24  * Eric Bollengier, May 2006
25  */
26 
27 #include "include/bareos.h"
28 #include "include/jcr.h"
29 #include "runscript.h"
30 #include "lib/util.h"
31 
32 /*
33  * This function pointer is set only by the Director (dird.c),
34  * and is not set in the File daemon, because the File
35  * daemon cannot run console commands.
36  */
37 bool (*console_command)(JobControlRecord *jcr, const char *cmd) = NULL;
38 
39 
NewRunscript()40 RunScript *NewRunscript()
41 {
42    Dmsg0(500, "runscript: creating new RunScript object\n");
43    RunScript *cmd = (RunScript *)malloc(sizeof(RunScript));
44    memset(cmd, 0, sizeof(RunScript));
45    cmd->ResetDefault();
46 
47    return cmd;
48 }
49 
ResetDefault(bool free_strings)50 void RunScript::ResetDefault(bool free_strings)
51 {
52    if (free_strings && command) {
53      FreePoolMemory(command);
54    }
55    if (free_strings && target) {
56      FreePoolMemory(target);
57    }
58 
59    target = NULL;
60    command = NULL;
61    on_success = true;
62    on_failure = false;
63    fail_on_error = true;
64    when = SCRIPT_Never;
65    job_code_callback = NULL;
66 }
67 
copy_runscript(RunScript * src)68 RunScript *copy_runscript(RunScript *src)
69 {
70    Dmsg0(500, "runscript: creating new RunScript object from other\n");
71 
72    RunScript *dst = (RunScript *)malloc(sizeof(RunScript));
73    memcpy(dst, src, sizeof(RunScript));
74 
75    dst->command = NULL;
76    dst->target = NULL;
77 
78    dst->SetCommand(src->command, src->cmd_type);
79    dst->SetTarget(src->target);
80 
81    return dst;
82 }
83 
FreeRunscript(RunScript * script)84 void FreeRunscript(RunScript *script)
85 {
86    Dmsg0(500, "runscript: freeing RunScript object\n");
87 
88    if (script->command) {
89       FreePoolMemory(script->command);
90    }
91    if (script->target) {
92       FreePoolMemory(script->target);
93    }
94    free(script);
95 }
96 
ScriptDirAllowed(JobControlRecord * jcr,RunScript * script,alist * allowed_script_dirs)97 static inline bool ScriptDirAllowed(JobControlRecord *jcr, RunScript *script, alist *allowed_script_dirs)
98 {
99    char *bp, *allowed_script_dir = nullptr;
100    bool allowed = false;
101    PoolMem script_dir(PM_FNAME);
102 
103    /*
104     * If there is no explicit list of allowed dirs allow any dir.
105     */
106    if (!allowed_script_dirs) {
107       return true;
108    }
109 
110    /*
111     * Determine the dir the script is in.
112     */
113    PmStrcpy(script_dir, script->command);
114    if ((bp = strrchr(script_dir.c_str(), '/'))) {
115       *bp = '\0';
116    }
117 
118    /*
119     * Make sure there are no relative path elements in script dir by which the
120     * user tries to escape the allowed dir checking. For scripts we only allow
121     * absolute paths.
122     */
123    if (strstr(script_dir.c_str(), "..")) {
124       Dmsg1(200, "ScriptDirAllowed: relative pathnames not allowed: %s\n", script_dir.c_str());
125       return false;
126    }
127 
128    /*
129     * Match the path the script is in against the list of allowed script directories.
130     */
131    foreach_alist(allowed_script_dir, allowed_script_dirs) {
132       if (Bstrcasecmp(script_dir.c_str(), allowed_script_dir)) {
133          allowed = true;
134          break;
135       }
136    }
137 
138    Dmsg2(200, "ScriptDirAllowed: script %s %s allowed by Allowed Script Dir setting",
139          script->command, (allowed) ? "" : "NOT");
140 
141    return allowed;
142 }
143 
RunScripts(JobControlRecord * jcr,alist * runscripts,const char * label,alist * allowed_script_dirs)144 int RunScripts(JobControlRecord *jcr, alist *runscripts, const char *label, alist *allowed_script_dirs)
145 {
146    RunScript *script = nullptr;
147    bool runit;
148    int when;
149 
150    Dmsg2(200, "runscript: running all RunScript object (%s) JobStatus=%c\n", label, jcr->JobStatus);
151 
152    if (strstr(label, NT_("Before"))) {
153       when = SCRIPT_Before;
154    } else if (bstrcmp(label, NT_("ClientAfterVSS"))) {
155       when = SCRIPT_AfterVSS;
156    } else {
157       when = SCRIPT_After;
158    }
159 
160    if (runscripts == NULL) {
161       Dmsg0(100, "runscript: WARNING RUNSCRIPTS list is NULL\n");
162       return 0;
163    }
164 
165    foreach_alist(script, runscripts) {
166       Dmsg2(200, "runscript: try to run %s:%s\n", NPRT(script->target), NPRT(script->command));
167       runit = false;
168 
169       if ((script->when & SCRIPT_Before) && (when & SCRIPT_Before)) {
170          if ((script->on_success && (jcr->JobStatus == JS_Running || jcr->JobStatus == JS_Created)) ||
171              (script->on_failure && (JobCanceled(jcr) || jcr->JobStatus == JS_Differences))) {
172             Dmsg4(200, "runscript: Run it because SCRIPT_Before (%s,%i,%i,%c)\n",
173                   script->command, script->on_success, script->on_failure, jcr->JobStatus );
174             runit = true;
175          }
176       }
177 
178       if ((script->when & SCRIPT_AfterVSS) && (when & SCRIPT_AfterVSS)) {
179          if ((script->on_success && (jcr->JobStatus == JS_Blocked)) ||
180              (script->on_failure && JobCanceled(jcr))) {
181             Dmsg4(200, "runscript: Run it because SCRIPT_AfterVSS (%s,%i,%i,%c)\n",
182                   script->command, script->on_success, script->on_failure, jcr->JobStatus );
183             runit = true;
184          }
185       }
186 
187       if ((script->when & SCRIPT_After) && (when & SCRIPT_After)) {
188          if ((script->on_success && jcr->IsTerminatedOk()) ||
189              (script->on_failure && (JobCanceled(jcr) || jcr->JobStatus == JS_Differences))) {
190             Dmsg4(200, "runscript: Run it because SCRIPT_After (%s,%i,%i,%c)\n",
191                   script->command, script->on_success, script->on_failure, jcr->JobStatus );
192             runit = true;
193          }
194       }
195 
196       if (!script->IsLocal()) {
197          runit = false;
198       }
199 
200       /*
201        * We execute it
202        */
203       if (runit) {
204          if (!ScriptDirAllowed(jcr, script, allowed_script_dirs)) {
205             Dmsg1(200, "runscript: Not running script %s because its not in one of the allowed scripts dirs\n",
206                   script->command);
207             Jmsg(jcr, M_ERROR, 0, _("Runscript: run %s \"%s\" could not execute, "
208                                     "not in one of the allowed scripts dirs\n"), label, script->command);
209             jcr->setJobStatus(JS_ErrorTerminated);
210             goto bail_out;
211          }
212 
213          script->run(jcr, label);
214       }
215    }
216 
217 bail_out:
218    return 1;
219 }
220 
IsLocal()221 bool RunScript::IsLocal()
222 {
223    if (!target || bstrcmp(target, "")) {
224       return true;
225    } else {
226       return false;
227    }
228 }
229 
230 /* set this->command to cmd */
SetCommand(const char * cmd,int acmd_type)231 void RunScript::SetCommand(const char *cmd, int acmd_type)
232 {
233    Dmsg1(500, "runscript: setting command = %s\n", NPRT(cmd));
234 
235    if (!cmd) {
236       return;
237    }
238 
239    if (!command) {
240       command = GetPoolMemory(PM_FNAME);
241    }
242 
243    PmStrcpy(command, cmd);
244    cmd_type = acmd_type;
245 }
246 
247 /* set this->target to client_name */
SetTarget(const char * client_name)248 void RunScript::SetTarget(const char *client_name)
249 {
250    Dmsg1(500, "runscript: setting target = %s\n", NPRT(client_name));
251 
252    if (!client_name) {
253       return;
254    }
255 
256    if (!target) {
257       target = GetPoolMemory(PM_FNAME);
258    }
259 
260    PmStrcpy(target, client_name);
261 }
262 
run(JobControlRecord * jcr,const char * name)263 bool RunScript::run(JobControlRecord *jcr, const char *name)
264 {
265    Dmsg1(100, "runscript: running a RunScript object type=%d\n", cmd_type);
266    POOLMEM *ecmd = GetPoolMemory(PM_FNAME);
267    int status;
268    Bpipe *bpipe;
269    PoolMem line(PM_NAME);
270 
271    ecmd = edit_job_codes(jcr, ecmd, this->command, "", this->job_code_callback);
272    Dmsg1(100, "runscript: running '%s'...\n", ecmd);
273    Jmsg(jcr, M_INFO, 0, _("%s: run %s \"%s\"\n"),
274         cmd_type==SHELL_CMD?"shell command":"console command", name, ecmd);
275 
276    switch (cmd_type) {
277    case SHELL_CMD:
278       bpipe = OpenBpipe(ecmd, 0, "r");
279       FreePoolMemory(ecmd);
280 
281       if (bpipe == NULL) {
282          BErrNo be;
283          Jmsg(jcr, M_ERROR, 0, _("Runscript: %s could not execute. ERR=%s\n"), name,
284             be.bstrerror());
285          goto bail_out;
286       }
287 
288       while (fgets(line.c_str(), line.size(), bpipe->rfd)) {
289          StripTrailingJunk(line.c_str());
290          Jmsg(jcr, M_INFO, 0, _("%s: %s\n"), name, line.c_str());
291       }
292 
293       status = CloseBpipe(bpipe);
294 
295       if (status != 0) {
296          BErrNo be;
297          Jmsg(jcr, M_ERROR, 0, _("Runscript: %s returned non-zero status=%d. ERR=%s\n"), name,
298             be.code(status), be.bstrerror(status));
299          goto bail_out;
300       }
301 
302       Dmsg0(100, "runscript OK\n");
303       break;
304    case CONSOLE_CMD:
305       if (console_command) {                 /* can we run console command? */
306          if (!console_command(jcr, ecmd)) {  /* yes, do so */
307             goto bail_out;
308          }
309       }
310       break;
311    }
312    return true;
313 
314 bail_out:
315    /* cancel running job properly */
316    if (fail_on_error) {
317       jcr->setJobStatus(JS_ErrorTerminated);
318    }
319    Dmsg1(100, "runscript failed. fail_on_error=%d\n", fail_on_error);
320    return false;
321 }
322 
FreeRunscripts(alist * runscripts)323 void FreeRunscripts(alist *runscripts)
324 {
325    Dmsg0(500, "runscript: freeing all RUNSCRIPTS object\n");
326 
327    RunScript *elt = nullptr;
328    foreach_alist(elt, runscripts) {
329       FreeRunscript(elt);
330    }
331 }
332 
debug()333 void RunScript::debug()
334 {
335    Dmsg0(200, "runscript: debug\n");
336    Dmsg0(200,  _(" --> RunScript\n"));
337    Dmsg1(200,  _("  --> Command=%s\n"), NPRT(command));
338    Dmsg1(200,  _("  --> Target=%s\n"),  NPRT(target));
339    Dmsg1(200,  _("  --> RunOnSuccess=%u\n"),  on_success);
340    Dmsg1(200,  _("  --> RunOnFailure=%u\n"),  on_failure);
341    Dmsg1(200,  _("  --> FailJobOnError=%u\n"),  fail_on_error);
342    Dmsg1(200,  _("  --> RunWhen=%u\n"),  when);
343 }
344 
SetJobCodeCallback(job_code_callback_t arg_job_code_callback)345 void RunScript::SetJobCodeCallback(job_code_callback_t arg_job_code_callback)
346 {
347    this->job_code_callback = arg_job_code_callback;
348 }
349