1*bf6873c5SCy Schubert /*
2*bf6873c5SCy Schubert * Run a PAM interaction script for testing.
3*bf6873c5SCy Schubert *
4*bf6873c5SCy Schubert * Provides an interface that loads a PAM interaction script from a file and
5*bf6873c5SCy Schubert * runs through that script, calling the internal PAM module functions and
6*bf6873c5SCy Schubert * checking their results. This allows automation of PAM testing through
7*bf6873c5SCy Schubert * external data files instead of coding everything in C.
8*bf6873c5SCy Schubert *
9*bf6873c5SCy Schubert * The canonical version of this file is maintained in the rra-c-util package,
10*bf6873c5SCy Schubert * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
11*bf6873c5SCy Schubert *
12*bf6873c5SCy Schubert * Written by Russ Allbery <eagle@eyrie.org>
13*bf6873c5SCy Schubert * Copyright 2016, 2018, 2020-2021 Russ Allbery <eagle@eyrie.org>
14*bf6873c5SCy Schubert * Copyright 2011-2012, 2014
15*bf6873c5SCy Schubert * The Board of Trustees of the Leland Stanford Junior University
16*bf6873c5SCy Schubert *
17*bf6873c5SCy Schubert * Permission is hereby granted, free of charge, to any person obtaining a
18*bf6873c5SCy Schubert * copy of this software and associated documentation files (the "Software"),
19*bf6873c5SCy Schubert * to deal in the Software without restriction, including without limitation
20*bf6873c5SCy Schubert * the rights to use, copy, modify, merge, publish, distribute, sublicense,
21*bf6873c5SCy Schubert * and/or sell copies of the Software, and to permit persons to whom the
22*bf6873c5SCy Schubert * Software is furnished to do so, subject to the following conditions:
23*bf6873c5SCy Schubert *
24*bf6873c5SCy Schubert * The above copyright notice and this permission notice shall be included in
25*bf6873c5SCy Schubert * all copies or substantial portions of the Software.
26*bf6873c5SCy Schubert *
27*bf6873c5SCy Schubert * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28*bf6873c5SCy Schubert * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29*bf6873c5SCy Schubert * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
30*bf6873c5SCy Schubert * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31*bf6873c5SCy Schubert * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
32*bf6873c5SCy Schubert * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
33*bf6873c5SCy Schubert * DEALINGS IN THE SOFTWARE.
34*bf6873c5SCy Schubert *
35*bf6873c5SCy Schubert * SPDX-License-Identifier: MIT
36*bf6873c5SCy Schubert */
37*bf6873c5SCy Schubert
38*bf6873c5SCy Schubert #include <config.h>
39*bf6873c5SCy Schubert #include <portable/pam.h>
40*bf6873c5SCy Schubert #include <portable/system.h>
41*bf6873c5SCy Schubert
42*bf6873c5SCy Schubert #include <ctype.h>
43*bf6873c5SCy Schubert #include <dirent.h>
44*bf6873c5SCy Schubert #include <errno.h>
45*bf6873c5SCy Schubert #ifdef HAVE_REGCOMP
46*bf6873c5SCy Schubert # include <regex.h>
47*bf6873c5SCy Schubert #endif
48*bf6873c5SCy Schubert #include <syslog.h>
49*bf6873c5SCy Schubert
50*bf6873c5SCy Schubert #include <tests/fakepam/internal.h>
51*bf6873c5SCy Schubert #include <tests/fakepam/pam.h>
52*bf6873c5SCy Schubert #include <tests/fakepam/script.h>
53*bf6873c5SCy Schubert #include <tests/tap/basic.h>
54*bf6873c5SCy Schubert #include <tests/tap/macros.h>
55*bf6873c5SCy Schubert #include <tests/tap/string.h>
56*bf6873c5SCy Schubert
57*bf6873c5SCy Schubert
58*bf6873c5SCy Schubert /*
59*bf6873c5SCy Schubert * Compare a regex to a string. If regular expression support isn't
60*bf6873c5SCy Schubert * available, we skip this test.
61*bf6873c5SCy Schubert */
62*bf6873c5SCy Schubert #ifdef HAVE_REGCOMP
63*bf6873c5SCy Schubert static void __attribute__((__format__(printf, 3, 4)))
like(const char * wanted,const char * seen,const char * format,...)64*bf6873c5SCy Schubert like(const char *wanted, const char *seen, const char *format, ...)
65*bf6873c5SCy Schubert {
66*bf6873c5SCy Schubert va_list args;
67*bf6873c5SCy Schubert regex_t regex;
68*bf6873c5SCy Schubert char err[BUFSIZ];
69*bf6873c5SCy Schubert int status;
70*bf6873c5SCy Schubert
71*bf6873c5SCy Schubert if (seen == NULL) {
72*bf6873c5SCy Schubert fflush(stderr);
73*bf6873c5SCy Schubert printf("# wanted: /%s/\n# seen: (null)\n", wanted);
74*bf6873c5SCy Schubert va_start(args, format);
75*bf6873c5SCy Schubert okv(0, format, args);
76*bf6873c5SCy Schubert va_end(args);
77*bf6873c5SCy Schubert return;
78*bf6873c5SCy Schubert }
79*bf6873c5SCy Schubert memset(®ex, 0, sizeof(regex));
80*bf6873c5SCy Schubert status = regcomp(®ex, wanted, REG_EXTENDED | REG_NOSUB);
81*bf6873c5SCy Schubert if (status != 0) {
82*bf6873c5SCy Schubert regerror(status, ®ex, err, sizeof(err));
83*bf6873c5SCy Schubert bail("invalid regex /%s/: %s", wanted, err);
84*bf6873c5SCy Schubert }
85*bf6873c5SCy Schubert status = regexec(®ex, seen, 0, NULL, 0);
86*bf6873c5SCy Schubert switch (status) {
87*bf6873c5SCy Schubert case 0:
88*bf6873c5SCy Schubert va_start(args, format);
89*bf6873c5SCy Schubert okv(1, format, args);
90*bf6873c5SCy Schubert va_end(args);
91*bf6873c5SCy Schubert break;
92*bf6873c5SCy Schubert case REG_NOMATCH:
93*bf6873c5SCy Schubert printf("# wanted: /%s/\n# seen: %s\n", wanted, seen);
94*bf6873c5SCy Schubert va_start(args, format);
95*bf6873c5SCy Schubert okv(0, format, args);
96*bf6873c5SCy Schubert va_end(args);
97*bf6873c5SCy Schubert break;
98*bf6873c5SCy Schubert default:
99*bf6873c5SCy Schubert regerror(status, ®ex, err, sizeof(err));
100*bf6873c5SCy Schubert bail("regexec failed for regex /%s/: %s", wanted, err);
101*bf6873c5SCy Schubert }
102*bf6873c5SCy Schubert regfree(®ex);
103*bf6873c5SCy Schubert }
104*bf6873c5SCy Schubert #else /* !HAVE_REGCOMP */
105*bf6873c5SCy Schubert static void
like(const char * wanted,const char * seen,const char * format UNUSED,...)106*bf6873c5SCy Schubert like(const char *wanted, const char *seen, const char *format UNUSED, ...)
107*bf6873c5SCy Schubert {
108*bf6873c5SCy Schubert diag("wanted /%s/", wanted);
109*bf6873c5SCy Schubert diag(" seen %s", seen);
110*bf6873c5SCy Schubert skip("regex support not available");
111*bf6873c5SCy Schubert }
112*bf6873c5SCy Schubert #endif /* !HAVE_REGCOMP */
113*bf6873c5SCy Schubert
114*bf6873c5SCy Schubert
115*bf6873c5SCy Schubert /*
116*bf6873c5SCy Schubert * Compare an expected string with a seen string, used by both output checking
117*bf6873c5SCy Schubert * and prompt checking. This is a separate function because the expected
118*bf6873c5SCy Schubert * string may be a regex, determined by seeing if it starts and ends with a
119*bf6873c5SCy Schubert * slash (/), which may require a regex comparison.
120*bf6873c5SCy Schubert *
121*bf6873c5SCy Schubert * Eventually calls either is_string or ok to report results via TAP.
122*bf6873c5SCy Schubert */
123*bf6873c5SCy Schubert static void __attribute__((__format__(printf, 3, 4)))
compare_string(char * wanted,char * seen,const char * format,...)124*bf6873c5SCy Schubert compare_string(char *wanted, char *seen, const char *format, ...)
125*bf6873c5SCy Schubert {
126*bf6873c5SCy Schubert va_list args;
127*bf6873c5SCy Schubert char *comment, *regex;
128*bf6873c5SCy Schubert size_t length;
129*bf6873c5SCy Schubert
130*bf6873c5SCy Schubert /* Format the comment since we need it regardless. */
131*bf6873c5SCy Schubert va_start(args, format);
132*bf6873c5SCy Schubert bvasprintf(&comment, format, args);
133*bf6873c5SCy Schubert va_end(args);
134*bf6873c5SCy Schubert
135*bf6873c5SCy Schubert /* Check whether the wanted string is a regex. */
136*bf6873c5SCy Schubert length = strlen(wanted);
137*bf6873c5SCy Schubert if (wanted[0] == '/' && wanted[length - 1] == '/') {
138*bf6873c5SCy Schubert regex = bstrndup(wanted + 1, length - 2);
139*bf6873c5SCy Schubert like(regex, seen, "%s", comment);
140*bf6873c5SCy Schubert free(regex);
141*bf6873c5SCy Schubert } else {
142*bf6873c5SCy Schubert is_string(wanted, seen, "%s", comment);
143*bf6873c5SCy Schubert }
144*bf6873c5SCy Schubert free(comment);
145*bf6873c5SCy Schubert }
146*bf6873c5SCy Schubert
147*bf6873c5SCy Schubert
148*bf6873c5SCy Schubert /*
149*bf6873c5SCy Schubert * The PAM conversation function. Takes the prompts struct from the
150*bf6873c5SCy Schubert * configuration and interacts appropriately. If a prompt is of the expected
151*bf6873c5SCy Schubert * type but not the expected string, it still responds; if it's not of the
152*bf6873c5SCy Schubert * expected type, it returns PAM_CONV_ERR.
153*bf6873c5SCy Schubert *
154*bf6873c5SCy Schubert * Currently only handles a single prompt at a time.
155*bf6873c5SCy Schubert */
156*bf6873c5SCy Schubert static int
converse(int num_msg,const struct pam_message ** msg,struct pam_response ** resp,void * appdata_ptr)157*bf6873c5SCy Schubert converse(int num_msg, const struct pam_message **msg,
158*bf6873c5SCy Schubert struct pam_response **resp, void *appdata_ptr)
159*bf6873c5SCy Schubert {
160*bf6873c5SCy Schubert struct prompts *prompts = appdata_ptr;
161*bf6873c5SCy Schubert struct prompt *prompt;
162*bf6873c5SCy Schubert char *message;
163*bf6873c5SCy Schubert size_t length;
164*bf6873c5SCy Schubert int i;
165*bf6873c5SCy Schubert
166*bf6873c5SCy Schubert *resp = bcalloc(num_msg, sizeof(struct pam_response));
167*bf6873c5SCy Schubert for (i = 0; i < num_msg; i++) {
168*bf6873c5SCy Schubert message = bstrdup(msg[i]->msg);
169*bf6873c5SCy Schubert
170*bf6873c5SCy Schubert /* Remove newlines for comparison purposes. */
171*bf6873c5SCy Schubert length = strlen(message);
172*bf6873c5SCy Schubert while (length > 0 && message[length - 1] == '\n')
173*bf6873c5SCy Schubert message[length-- - 1] = '\0';
174*bf6873c5SCy Schubert
175*bf6873c5SCy Schubert /* Check if we've gotten too many prompts but quietly ignore them. */
176*bf6873c5SCy Schubert if (prompts->current >= prompts->size) {
177*bf6873c5SCy Schubert diag("unexpected prompt: %s", message);
178*bf6873c5SCy Schubert free(message);
179*bf6873c5SCy Schubert ok(0, "more prompts than expected");
180*bf6873c5SCy Schubert continue;
181*bf6873c5SCy Schubert }
182*bf6873c5SCy Schubert
183*bf6873c5SCy Schubert /* Be sure everything matches and return the response, if any. */
184*bf6873c5SCy Schubert prompt = &prompts->prompts[prompts->current];
185*bf6873c5SCy Schubert is_int(prompt->style, msg[i]->msg_style, "style of prompt %lu",
186*bf6873c5SCy Schubert (unsigned long) prompts->current + 1);
187*bf6873c5SCy Schubert compare_string(prompt->prompt, message, "value of prompt %lu",
188*bf6873c5SCy Schubert (unsigned long) prompts->current + 1);
189*bf6873c5SCy Schubert free(message);
190*bf6873c5SCy Schubert prompts->current++;
191*bf6873c5SCy Schubert if (prompt->style == msg[i]->msg_style && prompt->response != NULL) {
192*bf6873c5SCy Schubert (*resp)[i].resp = bstrdup(prompt->response);
193*bf6873c5SCy Schubert (*resp)[i].resp_retcode = 0;
194*bf6873c5SCy Schubert }
195*bf6873c5SCy Schubert }
196*bf6873c5SCy Schubert
197*bf6873c5SCy Schubert /*
198*bf6873c5SCy Schubert * Always return success even if the prompts don't match. Otherwise,
199*bf6873c5SCy Schubert * we're likely to abort the conversation in the middle and possibly
200*bf6873c5SCy Schubert * leave passwords set incorrectly.
201*bf6873c5SCy Schubert */
202*bf6873c5SCy Schubert return PAM_SUCCESS;
203*bf6873c5SCy Schubert }
204*bf6873c5SCy Schubert
205*bf6873c5SCy Schubert
206*bf6873c5SCy Schubert /*
207*bf6873c5SCy Schubert * Check the actual PAM output against the expected output. We divide the
208*bf6873c5SCy Schubert * expected and seen output into separate lines and compare each one so that
209*bf6873c5SCy Schubert * we can handle regular expressions and the output priority.
210*bf6873c5SCy Schubert */
211*bf6873c5SCy Schubert static void
check_output(const struct output * wanted,const struct output * seen)212*bf6873c5SCy Schubert check_output(const struct output *wanted, const struct output *seen)
213*bf6873c5SCy Schubert {
214*bf6873c5SCy Schubert size_t i;
215*bf6873c5SCy Schubert
216*bf6873c5SCy Schubert if (wanted == NULL && seen == NULL)
217*bf6873c5SCy Schubert ok(1, "no output");
218*bf6873c5SCy Schubert else if (wanted == NULL) {
219*bf6873c5SCy Schubert for (i = 0; i < seen->count; i++)
220*bf6873c5SCy Schubert diag("unexpected: (%d) %s", seen->lines[i].priority,
221*bf6873c5SCy Schubert seen->lines[i].line);
222*bf6873c5SCy Schubert ok(0, "no output");
223*bf6873c5SCy Schubert } else if (seen == NULL) {
224*bf6873c5SCy Schubert for (i = 0; i < wanted->count; i++) {
225*bf6873c5SCy Schubert is_int(wanted->lines[i].priority, 0, "output priority %lu",
226*bf6873c5SCy Schubert (unsigned long) i + 1);
227*bf6873c5SCy Schubert is_string(wanted->lines[i].line, NULL, "output line %lu",
228*bf6873c5SCy Schubert (unsigned long) i + 1);
229*bf6873c5SCy Schubert }
230*bf6873c5SCy Schubert } else {
231*bf6873c5SCy Schubert for (i = 0; i < wanted->count && i < seen->count; i++) {
232*bf6873c5SCy Schubert is_int(wanted->lines[i].priority, seen->lines[i].priority,
233*bf6873c5SCy Schubert "output priority %lu", (unsigned long) i + 1);
234*bf6873c5SCy Schubert compare_string(wanted->lines[i].line, seen->lines[i].line,
235*bf6873c5SCy Schubert "output line %lu", (unsigned long) i + 1);
236*bf6873c5SCy Schubert }
237*bf6873c5SCy Schubert if (wanted->count > seen->count)
238*bf6873c5SCy Schubert for (i = seen->count; i < wanted->count; i++) {
239*bf6873c5SCy Schubert is_int(wanted->lines[i].priority, 0, "output priority %lu",
240*bf6873c5SCy Schubert (unsigned long) i + 1);
241*bf6873c5SCy Schubert is_string(wanted->lines[i].line, NULL, "output line %lu",
242*bf6873c5SCy Schubert (unsigned long) i + 1);
243*bf6873c5SCy Schubert }
244*bf6873c5SCy Schubert if (seen->count > wanted->count) {
245*bf6873c5SCy Schubert for (i = wanted->count; i < seen->count; i++)
246*bf6873c5SCy Schubert diag("unexpected: (%d) %s", seen->lines[i].priority,
247*bf6873c5SCy Schubert seen->lines[i].line);
248*bf6873c5SCy Schubert ok(0, "unexpected output lines");
249*bf6873c5SCy Schubert } else {
250*bf6873c5SCy Schubert ok(1, "no excess output");
251*bf6873c5SCy Schubert }
252*bf6873c5SCy Schubert }
253*bf6873c5SCy Schubert }
254*bf6873c5SCy Schubert
255*bf6873c5SCy Schubert
256*bf6873c5SCy Schubert /*
257*bf6873c5SCy Schubert * The core of the work. Given the path to a PAM interaction script, which
258*bf6873c5SCy Schubert * may be relative to C_TAP_SOURCE or C_TAP_BUILD, the user (may be NULL), and
259*bf6873c5SCy Schubert * the stored password (may be NULL), run that script, outputting the results
260*bf6873c5SCy Schubert * in TAP format.
261*bf6873c5SCy Schubert */
262*bf6873c5SCy Schubert void
run_script(const char * file,const struct script_config * config)263*bf6873c5SCy Schubert run_script(const char *file, const struct script_config *config)
264*bf6873c5SCy Schubert {
265*bf6873c5SCy Schubert char *path;
266*bf6873c5SCy Schubert struct output *output;
267*bf6873c5SCy Schubert FILE *script;
268*bf6873c5SCy Schubert struct work *work;
269*bf6873c5SCy Schubert struct options *opts;
270*bf6873c5SCy Schubert struct action *action, *oaction;
271*bf6873c5SCy Schubert struct pam_conv conv = {NULL, NULL};
272*bf6873c5SCy Schubert pam_handle_t *pamh;
273*bf6873c5SCy Schubert int status;
274*bf6873c5SCy Schubert size_t i, j;
275*bf6873c5SCy Schubert const char *argv_empty[] = {NULL};
276*bf6873c5SCy Schubert
277*bf6873c5SCy Schubert /* Open and parse the script. */
278*bf6873c5SCy Schubert if (access(file, R_OK) == 0)
279*bf6873c5SCy Schubert path = bstrdup(file);
280*bf6873c5SCy Schubert else {
281*bf6873c5SCy Schubert path = test_file_path(file);
282*bf6873c5SCy Schubert if (path == NULL)
283*bf6873c5SCy Schubert bail("cannot find PAM script %s", file);
284*bf6873c5SCy Schubert }
285*bf6873c5SCy Schubert script = fopen(path, "r");
286*bf6873c5SCy Schubert if (script == NULL)
287*bf6873c5SCy Schubert sysbail("cannot open %s", path);
288*bf6873c5SCy Schubert work = parse_script(script, config);
289*bf6873c5SCy Schubert fclose(script);
290*bf6873c5SCy Schubert diag("Starting %s", file);
291*bf6873c5SCy Schubert if (work->prompts != NULL) {
292*bf6873c5SCy Schubert conv.conv = converse;
293*bf6873c5SCy Schubert conv.appdata_ptr = work->prompts;
294*bf6873c5SCy Schubert }
295*bf6873c5SCy Schubert
296*bf6873c5SCy Schubert /* Initialize PAM. */
297*bf6873c5SCy Schubert status = pam_start("test", config->user, &conv, &pamh);
298*bf6873c5SCy Schubert if (status != PAM_SUCCESS)
299*bf6873c5SCy Schubert sysbail("cannot create PAM handle");
300*bf6873c5SCy Schubert if (config->authtok != NULL)
301*bf6873c5SCy Schubert pamh->authtok = bstrdup(config->authtok);
302*bf6873c5SCy Schubert if (config->oldauthtok != NULL)
303*bf6873c5SCy Schubert pamh->oldauthtok = bstrdup(config->oldauthtok);
304*bf6873c5SCy Schubert
305*bf6873c5SCy Schubert /* Run the actions and check their return status. */
306*bf6873c5SCy Schubert for (action = work->actions; action != NULL; action = action->next) {
307*bf6873c5SCy Schubert if (work->options[action->group].argv == NULL)
308*bf6873c5SCy Schubert status = (*action->call)(pamh, action->flags, 0, argv_empty);
309*bf6873c5SCy Schubert else {
310*bf6873c5SCy Schubert opts = &work->options[action->group];
311*bf6873c5SCy Schubert status = (*action->call)(pamh, action->flags, opts->argc,
312*bf6873c5SCy Schubert (const char **) opts->argv);
313*bf6873c5SCy Schubert }
314*bf6873c5SCy Schubert is_int(action->status, status, "status for %s", action->name);
315*bf6873c5SCy Schubert }
316*bf6873c5SCy Schubert output = pam_output();
317*bf6873c5SCy Schubert check_output(work->output, output);
318*bf6873c5SCy Schubert pam_output_free(output);
319*bf6873c5SCy Schubert
320*bf6873c5SCy Schubert /* If we have a test callback, call it now. */
321*bf6873c5SCy Schubert if (config->callback != NULL)
322*bf6873c5SCy Schubert config->callback(pamh, config, config->data);
323*bf6873c5SCy Schubert
324*bf6873c5SCy Schubert /* Free memory and return. */
325*bf6873c5SCy Schubert pam_end(pamh, work->end_flags);
326*bf6873c5SCy Schubert action = work->actions;
327*bf6873c5SCy Schubert while (action != NULL) {
328*bf6873c5SCy Schubert free(action->name);
329*bf6873c5SCy Schubert oaction = action;
330*bf6873c5SCy Schubert action = action->next;
331*bf6873c5SCy Schubert free(oaction);
332*bf6873c5SCy Schubert }
333*bf6873c5SCy Schubert for (i = 0; i < ARRAY_SIZE(work->options); i++)
334*bf6873c5SCy Schubert if (work->options[i].argv != NULL) {
335*bf6873c5SCy Schubert for (j = 0; work->options[i].argv[j] != NULL; j++)
336*bf6873c5SCy Schubert free(work->options[i].argv[j]);
337*bf6873c5SCy Schubert free(work->options[i].argv);
338*bf6873c5SCy Schubert }
339*bf6873c5SCy Schubert if (work->output)
340*bf6873c5SCy Schubert pam_output_free(work->output);
341*bf6873c5SCy Schubert if (work->prompts != NULL) {
342*bf6873c5SCy Schubert for (i = 0; i < work->prompts->size; i++) {
343*bf6873c5SCy Schubert free(work->prompts->prompts[i].prompt);
344*bf6873c5SCy Schubert free(work->prompts->prompts[i].response);
345*bf6873c5SCy Schubert }
346*bf6873c5SCy Schubert free(work->prompts->prompts);
347*bf6873c5SCy Schubert free(work->prompts);
348*bf6873c5SCy Schubert }
349*bf6873c5SCy Schubert free(work);
350*bf6873c5SCy Schubert free(path);
351*bf6873c5SCy Schubert }
352*bf6873c5SCy Schubert
353*bf6873c5SCy Schubert
354*bf6873c5SCy Schubert /*
355*bf6873c5SCy Schubert * Check a filename for acceptable characters. Returns true if the file
356*bf6873c5SCy Schubert * consists solely of [a-zA-Z0-9-] and false otherwise.
357*bf6873c5SCy Schubert */
358*bf6873c5SCy Schubert static bool
valid_filename(const char * filename)359*bf6873c5SCy Schubert valid_filename(const char *filename)
360*bf6873c5SCy Schubert {
361*bf6873c5SCy Schubert const char *p;
362*bf6873c5SCy Schubert
363*bf6873c5SCy Schubert for (p = filename; *p != '\0'; p++) {
364*bf6873c5SCy Schubert if (*p >= 'A' && *p <= 'Z')
365*bf6873c5SCy Schubert continue;
366*bf6873c5SCy Schubert if (*p >= 'a' && *p <= 'z')
367*bf6873c5SCy Schubert continue;
368*bf6873c5SCy Schubert if (*p >= '0' && *p <= '9')
369*bf6873c5SCy Schubert continue;
370*bf6873c5SCy Schubert if (*p == '-')
371*bf6873c5SCy Schubert continue;
372*bf6873c5SCy Schubert return false;
373*bf6873c5SCy Schubert }
374*bf6873c5SCy Schubert return true;
375*bf6873c5SCy Schubert }
376*bf6873c5SCy Schubert
377*bf6873c5SCy Schubert
378*bf6873c5SCy Schubert /*
379*bf6873c5SCy Schubert * The same as run_script, but run every script found in the given directory,
380*bf6873c5SCy Schubert * skipping file names that contain characters other than alphanumerics and -.
381*bf6873c5SCy Schubert */
382*bf6873c5SCy Schubert void
run_script_dir(const char * dir,const struct script_config * config)383*bf6873c5SCy Schubert run_script_dir(const char *dir, const struct script_config *config)
384*bf6873c5SCy Schubert {
385*bf6873c5SCy Schubert DIR *handle;
386*bf6873c5SCy Schubert struct dirent *entry;
387*bf6873c5SCy Schubert const char *path;
388*bf6873c5SCy Schubert char *file;
389*bf6873c5SCy Schubert
390*bf6873c5SCy Schubert if (access(dir, R_OK) == 0)
391*bf6873c5SCy Schubert path = dir;
392*bf6873c5SCy Schubert else
393*bf6873c5SCy Schubert path = test_file_path(dir);
394*bf6873c5SCy Schubert handle = opendir(path);
395*bf6873c5SCy Schubert if (handle == NULL)
396*bf6873c5SCy Schubert sysbail("cannot open directory %s", dir);
397*bf6873c5SCy Schubert errno = 0;
398*bf6873c5SCy Schubert while ((entry = readdir(handle)) != NULL) {
399*bf6873c5SCy Schubert if (!valid_filename(entry->d_name))
400*bf6873c5SCy Schubert continue;
401*bf6873c5SCy Schubert basprintf(&file, "%s/%s", path, entry->d_name);
402*bf6873c5SCy Schubert run_script(file, config);
403*bf6873c5SCy Schubert free(file);
404*bf6873c5SCy Schubert errno = 0;
405*bf6873c5SCy Schubert }
406*bf6873c5SCy Schubert if (errno != 0)
407*bf6873c5SCy Schubert sysbail("cannot read directory %s", dir);
408*bf6873c5SCy Schubert closedir(handle);
409*bf6873c5SCy Schubert if (path != dir)
410*bf6873c5SCy Schubert test_file_path_free((char *) path);
411*bf6873c5SCy Schubert }
412