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 2017-2018, 2020 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 <assert.h>
43*bf6873c5SCy Schubert #include <ctype.h>
44*bf6873c5SCy Schubert #include <dirent.h>
45*bf6873c5SCy Schubert #include <errno.h>
46*bf6873c5SCy Schubert #include <syslog.h>
47*bf6873c5SCy Schubert
48*bf6873c5SCy Schubert #include <tests/fakepam/internal.h>
49*bf6873c5SCy Schubert #include <tests/fakepam/script.h>
50*bf6873c5SCy Schubert #include <tests/tap/basic.h>
51*bf6873c5SCy Schubert #include <tests/tap/string.h>
52*bf6873c5SCy Schubert
53*bf6873c5SCy Schubert /* Used for enumerating arrays. */
54*bf6873c5SCy Schubert #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
55*bf6873c5SCy Schubert
56*bf6873c5SCy Schubert /* Mapping of strings to PAM function pointers and group numbers. */
57*bf6873c5SCy Schubert static const struct {
58*bf6873c5SCy Schubert const char *name;
59*bf6873c5SCy Schubert pam_call call;
60*bf6873c5SCy Schubert enum group_type group;
61*bf6873c5SCy Schubert } CALLS[] = {
62*bf6873c5SCy Schubert /* clang-format off */
63*bf6873c5SCy Schubert {"acct_mgmt", pam_sm_acct_mgmt, GROUP_ACCOUNT },
64*bf6873c5SCy Schubert {"authenticate", pam_sm_authenticate, GROUP_AUTH },
65*bf6873c5SCy Schubert {"setcred", pam_sm_setcred, GROUP_AUTH },
66*bf6873c5SCy Schubert {"chauthtok", pam_sm_chauthtok, GROUP_PASSWORD},
67*bf6873c5SCy Schubert {"open_session", pam_sm_open_session, GROUP_SESSION },
68*bf6873c5SCy Schubert {"close_session", pam_sm_close_session, GROUP_SESSION },
69*bf6873c5SCy Schubert /* clang-format on */
70*bf6873c5SCy Schubert };
71*bf6873c5SCy Schubert
72*bf6873c5SCy Schubert /* Mapping of PAM flag names without the leading PAM_ to values. */
73*bf6873c5SCy Schubert static const struct {
74*bf6873c5SCy Schubert const char *name;
75*bf6873c5SCy Schubert int value;
76*bf6873c5SCy Schubert } FLAGS[] = {
77*bf6873c5SCy Schubert /* clang-format off */
78*bf6873c5SCy Schubert {"CHANGE_EXPIRED_AUTHTOK", PAM_CHANGE_EXPIRED_AUTHTOK},
79*bf6873c5SCy Schubert {"DELETE_CRED", PAM_DELETE_CRED },
80*bf6873c5SCy Schubert {"DISALLOW_NULL_AUTHTOK", PAM_DISALLOW_NULL_AUTHTOK },
81*bf6873c5SCy Schubert {"ESTABLISH_CRED", PAM_ESTABLISH_CRED },
82*bf6873c5SCy Schubert {"PRELIM_CHECK", PAM_PRELIM_CHECK },
83*bf6873c5SCy Schubert {"REFRESH_CRED", PAM_REFRESH_CRED },
84*bf6873c5SCy Schubert {"REINITIALIZE_CRED", PAM_REINITIALIZE_CRED },
85*bf6873c5SCy Schubert {"SILENT", PAM_SILENT },
86*bf6873c5SCy Schubert {"UPDATE_AUTHTOK", PAM_UPDATE_AUTHTOK },
87*bf6873c5SCy Schubert /* clang-format on */
88*bf6873c5SCy Schubert };
89*bf6873c5SCy Schubert
90*bf6873c5SCy Schubert /* Mapping of strings to PAM groups. */
91*bf6873c5SCy Schubert static const struct {
92*bf6873c5SCy Schubert const char *name;
93*bf6873c5SCy Schubert enum group_type group;
94*bf6873c5SCy Schubert } GROUPS[] = {
95*bf6873c5SCy Schubert /* clang-format off */
96*bf6873c5SCy Schubert {"account", GROUP_ACCOUNT },
97*bf6873c5SCy Schubert {"auth", GROUP_AUTH },
98*bf6873c5SCy Schubert {"password", GROUP_PASSWORD},
99*bf6873c5SCy Schubert {"session", GROUP_SESSION },
100*bf6873c5SCy Schubert /* clang-format on */
101*bf6873c5SCy Schubert };
102*bf6873c5SCy Schubert
103*bf6873c5SCy Schubert /* Mapping of strings to PAM return values. */
104*bf6873c5SCy Schubert static const struct {
105*bf6873c5SCy Schubert const char *name;
106*bf6873c5SCy Schubert int status;
107*bf6873c5SCy Schubert } RETURNS[] = {
108*bf6873c5SCy Schubert /* clang-format off */
109*bf6873c5SCy Schubert {"PAM_AUTH_ERR", PAM_AUTH_ERR },
110*bf6873c5SCy Schubert {"PAM_AUTHINFO_UNAVAIL", PAM_AUTHINFO_UNAVAIL},
111*bf6873c5SCy Schubert {"PAM_AUTHTOK_ERR", PAM_AUTHTOK_ERR },
112*bf6873c5SCy Schubert {"PAM_DATA_SILENT", PAM_DATA_SILENT },
113*bf6873c5SCy Schubert {"PAM_IGNORE", PAM_IGNORE },
114*bf6873c5SCy Schubert {"PAM_NEW_AUTHTOK_REQD", PAM_NEW_AUTHTOK_REQD},
115*bf6873c5SCy Schubert {"PAM_SESSION_ERR", PAM_SESSION_ERR },
116*bf6873c5SCy Schubert {"PAM_SUCCESS", PAM_SUCCESS },
117*bf6873c5SCy Schubert {"PAM_USER_UNKNOWN", PAM_USER_UNKNOWN },
118*bf6873c5SCy Schubert /* clang-format on */
119*bf6873c5SCy Schubert };
120*bf6873c5SCy Schubert
121*bf6873c5SCy Schubert /* Mapping of PAM prompt styles to their values. */
122*bf6873c5SCy Schubert static const struct {
123*bf6873c5SCy Schubert const char *name;
124*bf6873c5SCy Schubert int style;
125*bf6873c5SCy Schubert } STYLES[] = {
126*bf6873c5SCy Schubert /* clang-format off */
127*bf6873c5SCy Schubert {"echo_off", PAM_PROMPT_ECHO_OFF},
128*bf6873c5SCy Schubert {"echo_on", PAM_PROMPT_ECHO_ON },
129*bf6873c5SCy Schubert {"error_msg", PAM_ERROR_MSG },
130*bf6873c5SCy Schubert {"info", PAM_TEXT_INFO },
131*bf6873c5SCy Schubert /* clang-format on */
132*bf6873c5SCy Schubert };
133*bf6873c5SCy Schubert
134*bf6873c5SCy Schubert /* Mappings of strings to syslog priorities. */
135*bf6873c5SCy Schubert static const struct {
136*bf6873c5SCy Schubert const char *name;
137*bf6873c5SCy Schubert int priority;
138*bf6873c5SCy Schubert } PRIORITIES[] = {
139*bf6873c5SCy Schubert /* clang-format off */
140*bf6873c5SCy Schubert {"DEBUG", LOG_DEBUG },
141*bf6873c5SCy Schubert {"INFO", LOG_INFO },
142*bf6873c5SCy Schubert {"NOTICE", LOG_NOTICE},
143*bf6873c5SCy Schubert {"ERR", LOG_ERR },
144*bf6873c5SCy Schubert {"CRIT", LOG_CRIT },
145*bf6873c5SCy Schubert /* clang-format on */
146*bf6873c5SCy Schubert };
147*bf6873c5SCy Schubert
148*bf6873c5SCy Schubert
149*bf6873c5SCy Schubert /*
150*bf6873c5SCy Schubert * Given a pointer to a string, skip any leading whitespace and return a
151*bf6873c5SCy Schubert * pointer to the first non-whitespace character.
152*bf6873c5SCy Schubert */
153*bf6873c5SCy Schubert static char *
skip_whitespace(char * p)154*bf6873c5SCy Schubert skip_whitespace(char *p)
155*bf6873c5SCy Schubert {
156*bf6873c5SCy Schubert while (isspace((unsigned char) (*p)))
157*bf6873c5SCy Schubert p++;
158*bf6873c5SCy Schubert return p;
159*bf6873c5SCy Schubert }
160*bf6873c5SCy Schubert
161*bf6873c5SCy Schubert
162*bf6873c5SCy Schubert /*
163*bf6873c5SCy Schubert * Read a line from a file into a BUFSIZ buffer, failing if the line was too
164*bf6873c5SCy Schubert * long to fit into the buffer, and returns a copy of that line in newly
165*bf6873c5SCy Schubert * allocated memory. Ignores blank lines and comments. Caller is responsible
166*bf6873c5SCy Schubert * for freeing. Returns NULL on end of file and fails on read errors.
167*bf6873c5SCy Schubert */
168*bf6873c5SCy Schubert static char *
readline(FILE * file)169*bf6873c5SCy Schubert readline(FILE *file)
170*bf6873c5SCy Schubert {
171*bf6873c5SCy Schubert char buffer[BUFSIZ];
172*bf6873c5SCy Schubert char *line, *first;
173*bf6873c5SCy Schubert
174*bf6873c5SCy Schubert do {
175*bf6873c5SCy Schubert line = fgets(buffer, sizeof(buffer), file);
176*bf6873c5SCy Schubert if (line == NULL) {
177*bf6873c5SCy Schubert if (feof(file))
178*bf6873c5SCy Schubert return NULL;
179*bf6873c5SCy Schubert sysbail("cannot read line from script");
180*bf6873c5SCy Schubert }
181*bf6873c5SCy Schubert if (buffer[strlen(buffer) - 1] != '\n')
182*bf6873c5SCy Schubert bail("script line too long");
183*bf6873c5SCy Schubert buffer[strlen(buffer) - 1] = '\0';
184*bf6873c5SCy Schubert first = skip_whitespace(buffer);
185*bf6873c5SCy Schubert } while (first[0] == '#' || first[0] == '\0');
186*bf6873c5SCy Schubert line = bstrdup(buffer);
187*bf6873c5SCy Schubert return line;
188*bf6873c5SCy Schubert }
189*bf6873c5SCy Schubert
190*bf6873c5SCy Schubert
191*bf6873c5SCy Schubert /*
192*bf6873c5SCy Schubert * Given the name of a PAM call, map it to a call enum. This is used later in
193*bf6873c5SCy Schubert * switch statements to determine which function to call. Fails on any
194*bf6873c5SCy Schubert * unrecognized string. If the optional second argument is not NULL, also
195*bf6873c5SCy Schubert * store the group number in that argument.
196*bf6873c5SCy Schubert */
197*bf6873c5SCy Schubert static pam_call
string_to_call(const char * name,enum group_type * group)198*bf6873c5SCy Schubert string_to_call(const char *name, enum group_type *group)
199*bf6873c5SCy Schubert {
200*bf6873c5SCy Schubert size_t i;
201*bf6873c5SCy Schubert
202*bf6873c5SCy Schubert for (i = 0; i < ARRAY_SIZE(CALLS); i++)
203*bf6873c5SCy Schubert if (strcmp(name, CALLS[i].name) == 0) {
204*bf6873c5SCy Schubert if (group != NULL)
205*bf6873c5SCy Schubert *group = CALLS[i].group;
206*bf6873c5SCy Schubert return CALLS[i].call;
207*bf6873c5SCy Schubert }
208*bf6873c5SCy Schubert bail("unrecognized PAM call %s", name);
209*bf6873c5SCy Schubert }
210*bf6873c5SCy Schubert
211*bf6873c5SCy Schubert
212*bf6873c5SCy Schubert /*
213*bf6873c5SCy Schubert * Given a PAM flag value without the leading PAM_, map it to the numeric
214*bf6873c5SCy Schubert * value of that flag. Fails on any unrecognized string.
215*bf6873c5SCy Schubert */
216*bf6873c5SCy Schubert static int
string_to_flag(const char * name)217*bf6873c5SCy Schubert string_to_flag(const char *name)
218*bf6873c5SCy Schubert {
219*bf6873c5SCy Schubert size_t i;
220*bf6873c5SCy Schubert
221*bf6873c5SCy Schubert for (i = 0; i < ARRAY_SIZE(FLAGS); i++)
222*bf6873c5SCy Schubert if (strcmp(name, FLAGS[i].name) == 0)
223*bf6873c5SCy Schubert return FLAGS[i].value;
224*bf6873c5SCy Schubert bail("unrecognized PAM flag %s", name);
225*bf6873c5SCy Schubert }
226*bf6873c5SCy Schubert
227*bf6873c5SCy Schubert
228*bf6873c5SCy Schubert /*
229*bf6873c5SCy Schubert * Given a PAM group name, map it to the array index for the options array for
230*bf6873c5SCy Schubert * that group. Fails on any unrecognized string.
231*bf6873c5SCy Schubert */
232*bf6873c5SCy Schubert static enum group_type
string_to_group(const char * name)233*bf6873c5SCy Schubert string_to_group(const char *name)
234*bf6873c5SCy Schubert {
235*bf6873c5SCy Schubert size_t i;
236*bf6873c5SCy Schubert
237*bf6873c5SCy Schubert for (i = 0; i < ARRAY_SIZE(GROUPS); i++)
238*bf6873c5SCy Schubert if (strcmp(name, GROUPS[i].name) == 0)
239*bf6873c5SCy Schubert return GROUPS[i].group;
240*bf6873c5SCy Schubert bail("unrecognized PAM group %s", name);
241*bf6873c5SCy Schubert }
242*bf6873c5SCy Schubert
243*bf6873c5SCy Schubert
244*bf6873c5SCy Schubert /*
245*bf6873c5SCy Schubert * Given a syslog priority name, map it to the numeric value of that priority.
246*bf6873c5SCy Schubert * Fails on any unrecognized string.
247*bf6873c5SCy Schubert */
248*bf6873c5SCy Schubert static int
string_to_priority(const char * name)249*bf6873c5SCy Schubert string_to_priority(const char *name)
250*bf6873c5SCy Schubert {
251*bf6873c5SCy Schubert size_t i;
252*bf6873c5SCy Schubert
253*bf6873c5SCy Schubert for (i = 0; i < ARRAY_SIZE(PRIORITIES); i++)
254*bf6873c5SCy Schubert if (strcmp(name, PRIORITIES[i].name) == 0)
255*bf6873c5SCy Schubert return PRIORITIES[i].priority;
256*bf6873c5SCy Schubert bail("unrecognized syslog priority %s", name);
257*bf6873c5SCy Schubert }
258*bf6873c5SCy Schubert
259*bf6873c5SCy Schubert
260*bf6873c5SCy Schubert /*
261*bf6873c5SCy Schubert * Given a PAM return status, map it to the actual expected value. Fails on
262*bf6873c5SCy Schubert * any unrecognized string.
263*bf6873c5SCy Schubert */
264*bf6873c5SCy Schubert static int
string_to_status(const char * name)265*bf6873c5SCy Schubert string_to_status(const char *name)
266*bf6873c5SCy Schubert {
267*bf6873c5SCy Schubert size_t i;
268*bf6873c5SCy Schubert
269*bf6873c5SCy Schubert if (name == NULL)
270*bf6873c5SCy Schubert bail("no PAM status on line");
271*bf6873c5SCy Schubert for (i = 0; i < ARRAY_SIZE(RETURNS); i++)
272*bf6873c5SCy Schubert if (strcmp(name, RETURNS[i].name) == 0)
273*bf6873c5SCy Schubert return RETURNS[i].status;
274*bf6873c5SCy Schubert bail("unrecognized PAM status %s", name);
275*bf6873c5SCy Schubert }
276*bf6873c5SCy Schubert
277*bf6873c5SCy Schubert
278*bf6873c5SCy Schubert /*
279*bf6873c5SCy Schubert * Given a PAM prompt style value without the leading PAM_PROMPT_, map it to
280*bf6873c5SCy Schubert * the numeric value of that flag. Fails on any unrecognized string.
281*bf6873c5SCy Schubert */
282*bf6873c5SCy Schubert static int
string_to_style(const char * name)283*bf6873c5SCy Schubert string_to_style(const char *name)
284*bf6873c5SCy Schubert {
285*bf6873c5SCy Schubert size_t i;
286*bf6873c5SCy Schubert
287*bf6873c5SCy Schubert for (i = 0; i < ARRAY_SIZE(STYLES); i++)
288*bf6873c5SCy Schubert if (strcmp(name, STYLES[i].name) == 0)
289*bf6873c5SCy Schubert return STYLES[i].style;
290*bf6873c5SCy Schubert bail("unrecognized PAM prompt style %s", name);
291*bf6873c5SCy Schubert }
292*bf6873c5SCy Schubert
293*bf6873c5SCy Schubert
294*bf6873c5SCy Schubert /*
295*bf6873c5SCy Schubert * We found a section delimiter while parsing another section. Rewind our
296*bf6873c5SCy Schubert * input file back before the section delimiter so that we'll read it again.
297*bf6873c5SCy Schubert * Takes the length of the line we read, which is used to determine how far to
298*bf6873c5SCy Schubert * rewind.
299*bf6873c5SCy Schubert */
300*bf6873c5SCy Schubert static void
rewind_section(FILE * script,size_t length)301*bf6873c5SCy Schubert rewind_section(FILE *script, size_t length)
302*bf6873c5SCy Schubert {
303*bf6873c5SCy Schubert if (fseek(script, -length - 1, SEEK_CUR) != 0)
304*bf6873c5SCy Schubert sysbail("cannot rewind file");
305*bf6873c5SCy Schubert }
306*bf6873c5SCy Schubert
307*bf6873c5SCy Schubert
308*bf6873c5SCy Schubert /*
309*bf6873c5SCy Schubert * Given a string that may contain %-escapes, expand it into the resulting
310*bf6873c5SCy Schubert * value. The following escapes are supported:
311*bf6873c5SCy Schubert *
312*bf6873c5SCy Schubert * %i current UID (not target user UID)
313*bf6873c5SCy Schubert * %n new password
314*bf6873c5SCy Schubert * %p password
315*bf6873c5SCy Schubert * %u username
316*bf6873c5SCy Schubert * %0 user-supplied string
317*bf6873c5SCy Schubert * ...
318*bf6873c5SCy Schubert * %9 user-supplied string
319*bf6873c5SCy Schubert *
320*bf6873c5SCy Schubert * The %* escape is preserved as-is, as it has to be interpreted at the time
321*bf6873c5SCy Schubert * of checking output. Returns the expanded string in newly-allocated memory.
322*bf6873c5SCy Schubert */
323*bf6873c5SCy Schubert static char *
expand_string(const char * template,const struct script_config * config)324*bf6873c5SCy Schubert expand_string(const char *template, const struct script_config *config)
325*bf6873c5SCy Schubert {
326*bf6873c5SCy Schubert size_t length = 0;
327*bf6873c5SCy Schubert const char *p, *extra;
328*bf6873c5SCy Schubert char *output, *out;
329*bf6873c5SCy Schubert char *uid = NULL;
330*bf6873c5SCy Schubert
331*bf6873c5SCy Schubert length = 0;
332*bf6873c5SCy Schubert for (p = template; *p != '\0'; p++) {
333*bf6873c5SCy Schubert if (*p != '%')
334*bf6873c5SCy Schubert length++;
335*bf6873c5SCy Schubert else {
336*bf6873c5SCy Schubert p++;
337*bf6873c5SCy Schubert switch (*p) {
338*bf6873c5SCy Schubert case 'i':
339*bf6873c5SCy Schubert if (uid == NULL)
340*bf6873c5SCy Schubert basprintf(&uid, "%lu", (unsigned long) getuid());
341*bf6873c5SCy Schubert length += strlen(uid);
342*bf6873c5SCy Schubert break;
343*bf6873c5SCy Schubert case 'n':
344*bf6873c5SCy Schubert if (config->newpass == NULL)
345*bf6873c5SCy Schubert bail("new password not set");
346*bf6873c5SCy Schubert length += strlen(config->newpass);
347*bf6873c5SCy Schubert break;
348*bf6873c5SCy Schubert case 'p':
349*bf6873c5SCy Schubert if (config->password == NULL)
350*bf6873c5SCy Schubert bail("password not set");
351*bf6873c5SCy Schubert length += strlen(config->password);
352*bf6873c5SCy Schubert break;
353*bf6873c5SCy Schubert case 'u':
354*bf6873c5SCy Schubert length += strlen(config->user);
355*bf6873c5SCy Schubert break;
356*bf6873c5SCy Schubert case '0':
357*bf6873c5SCy Schubert case '1':
358*bf6873c5SCy Schubert case '2':
359*bf6873c5SCy Schubert case '3':
360*bf6873c5SCy Schubert case '4':
361*bf6873c5SCy Schubert case '5':
362*bf6873c5SCy Schubert case '6':
363*bf6873c5SCy Schubert case '7':
364*bf6873c5SCy Schubert case '8':
365*bf6873c5SCy Schubert case '9':
366*bf6873c5SCy Schubert if (config->extra[*p - '0'] == NULL)
367*bf6873c5SCy Schubert bail("extra script parameter %%%c not set", *p);
368*bf6873c5SCy Schubert length += strlen(config->extra[*p - '0']);
369*bf6873c5SCy Schubert break;
370*bf6873c5SCy Schubert case '*':
371*bf6873c5SCy Schubert length += 2;
372*bf6873c5SCy Schubert break;
373*bf6873c5SCy Schubert default:
374*bf6873c5SCy Schubert length++;
375*bf6873c5SCy Schubert break;
376*bf6873c5SCy Schubert }
377*bf6873c5SCy Schubert }
378*bf6873c5SCy Schubert }
379*bf6873c5SCy Schubert output = bmalloc(length + 1);
380*bf6873c5SCy Schubert for (p = template, out = output; *p != '\0'; p++) {
381*bf6873c5SCy Schubert if (*p != '%')
382*bf6873c5SCy Schubert *out++ = *p;
383*bf6873c5SCy Schubert else {
384*bf6873c5SCy Schubert p++;
385*bf6873c5SCy Schubert switch (*p) {
386*bf6873c5SCy Schubert case 'i':
387*bf6873c5SCy Schubert assert(uid != NULL);
388*bf6873c5SCy Schubert memcpy(out, uid, strlen(uid));
389*bf6873c5SCy Schubert out += strlen(uid);
390*bf6873c5SCy Schubert break;
391*bf6873c5SCy Schubert case 'n':
392*bf6873c5SCy Schubert memcpy(out, config->newpass, strlen(config->newpass));
393*bf6873c5SCy Schubert out += strlen(config->newpass);
394*bf6873c5SCy Schubert break;
395*bf6873c5SCy Schubert case 'p':
396*bf6873c5SCy Schubert memcpy(out, config->password, strlen(config->password));
397*bf6873c5SCy Schubert out += strlen(config->password);
398*bf6873c5SCy Schubert break;
399*bf6873c5SCy Schubert case 'u':
400*bf6873c5SCy Schubert memcpy(out, config->user, strlen(config->user));
401*bf6873c5SCy Schubert out += strlen(config->user);
402*bf6873c5SCy Schubert break;
403*bf6873c5SCy Schubert case '0':
404*bf6873c5SCy Schubert case '1':
405*bf6873c5SCy Schubert case '2':
406*bf6873c5SCy Schubert case '3':
407*bf6873c5SCy Schubert case '4':
408*bf6873c5SCy Schubert case '5':
409*bf6873c5SCy Schubert case '6':
410*bf6873c5SCy Schubert case '7':
411*bf6873c5SCy Schubert case '8':
412*bf6873c5SCy Schubert case '9':
413*bf6873c5SCy Schubert extra = config->extra[*p - '0'];
414*bf6873c5SCy Schubert memcpy(out, extra, strlen(extra));
415*bf6873c5SCy Schubert out += strlen(extra);
416*bf6873c5SCy Schubert break;
417*bf6873c5SCy Schubert case '*':
418*bf6873c5SCy Schubert *out++ = '%';
419*bf6873c5SCy Schubert *out++ = '*';
420*bf6873c5SCy Schubert break;
421*bf6873c5SCy Schubert default:
422*bf6873c5SCy Schubert *out++ = *p;
423*bf6873c5SCy Schubert break;
424*bf6873c5SCy Schubert }
425*bf6873c5SCy Schubert }
426*bf6873c5SCy Schubert }
427*bf6873c5SCy Schubert *out = '\0';
428*bf6873c5SCy Schubert free(uid);
429*bf6873c5SCy Schubert return output;
430*bf6873c5SCy Schubert }
431*bf6873c5SCy Schubert
432*bf6873c5SCy Schubert
433*bf6873c5SCy Schubert /*
434*bf6873c5SCy Schubert * Given a whitespace-delimited string of PAM options, split it into an argv
435*bf6873c5SCy Schubert * array and argc count and store it in the provided option struct.
436*bf6873c5SCy Schubert */
437*bf6873c5SCy Schubert static void
split_options(char * string,struct options * options,const struct script_config * config)438*bf6873c5SCy Schubert split_options(char *string, struct options *options,
439*bf6873c5SCy Schubert const struct script_config *config)
440*bf6873c5SCy Schubert {
441*bf6873c5SCy Schubert char *opt;
442*bf6873c5SCy Schubert size_t size, count;
443*bf6873c5SCy Schubert
444*bf6873c5SCy Schubert for (opt = strtok(string, " "); opt != NULL; opt = strtok(NULL, " ")) {
445*bf6873c5SCy Schubert if (options->argv == NULL) {
446*bf6873c5SCy Schubert options->argv = bcalloc(2, sizeof(const char *));
447*bf6873c5SCy Schubert options->argv[0] = expand_string(opt, config);
448*bf6873c5SCy Schubert options->argc = 1;
449*bf6873c5SCy Schubert } else {
450*bf6873c5SCy Schubert count = (options->argc + 2);
451*bf6873c5SCy Schubert size = sizeof(const char *);
452*bf6873c5SCy Schubert options->argv = breallocarray(options->argv, count, size);
453*bf6873c5SCy Schubert options->argv[options->argc] = expand_string(opt, config);
454*bf6873c5SCy Schubert options->argv[options->argc + 1] = NULL;
455*bf6873c5SCy Schubert options->argc++;
456*bf6873c5SCy Schubert }
457*bf6873c5SCy Schubert }
458*bf6873c5SCy Schubert }
459*bf6873c5SCy Schubert
460*bf6873c5SCy Schubert
461*bf6873c5SCy Schubert /*
462*bf6873c5SCy Schubert * Parse the options section of a PAM script. This consists of one or more
463*bf6873c5SCy Schubert * lines in the format:
464*bf6873c5SCy Schubert *
465*bf6873c5SCy Schubert * <group> = <options>
466*bf6873c5SCy Schubert *
467*bf6873c5SCy Schubert * where options are either option names or option=value pairs, where the
468*bf6873c5SCy Schubert * value may not contain whitespace. Returns an options struct, which stores
469*bf6873c5SCy Schubert * argc and argv values for each group.
470*bf6873c5SCy Schubert *
471*bf6873c5SCy Schubert * Takes the work struct as an argument and puts values into its array.
472*bf6873c5SCy Schubert */
473*bf6873c5SCy Schubert static void
parse_options(FILE * script,struct work * work,const struct script_config * config)474*bf6873c5SCy Schubert parse_options(FILE *script, struct work *work,
475*bf6873c5SCy Schubert const struct script_config *config)
476*bf6873c5SCy Schubert {
477*bf6873c5SCy Schubert char *line, *group, *token;
478*bf6873c5SCy Schubert size_t length = 0;
479*bf6873c5SCy Schubert enum group_type type;
480*bf6873c5SCy Schubert
481*bf6873c5SCy Schubert for (line = readline(script); line != NULL; line = readline(script)) {
482*bf6873c5SCy Schubert length = strlen(line);
483*bf6873c5SCy Schubert group = strtok(line, " ");
484*bf6873c5SCy Schubert if (group == NULL)
485*bf6873c5SCy Schubert bail("malformed script line");
486*bf6873c5SCy Schubert if (group[0] == '[')
487*bf6873c5SCy Schubert break;
488*bf6873c5SCy Schubert type = string_to_group(group);
489*bf6873c5SCy Schubert token = strtok(NULL, " ");
490*bf6873c5SCy Schubert if (token == NULL)
491*bf6873c5SCy Schubert bail("malformed action line");
492*bf6873c5SCy Schubert if (strcmp(token, "=") != 0)
493*bf6873c5SCy Schubert bail("malformed action line near %s", token);
494*bf6873c5SCy Schubert token = strtok(NULL, "");
495*bf6873c5SCy Schubert split_options(token, &work->options[type], config);
496*bf6873c5SCy Schubert free(line);
497*bf6873c5SCy Schubert }
498*bf6873c5SCy Schubert if (line != NULL) {
499*bf6873c5SCy Schubert free(line);
500*bf6873c5SCy Schubert rewind_section(script, length);
501*bf6873c5SCy Schubert }
502*bf6873c5SCy Schubert }
503*bf6873c5SCy Schubert
504*bf6873c5SCy Schubert
505*bf6873c5SCy Schubert /*
506*bf6873c5SCy Schubert * Parse the call portion of a PAM call in the run section of a PAM script.
507*bf6873c5SCy Schubert * This handles parsing the PAM flags that optionally may be given as part of
508*bf6873c5SCy Schubert * the call. Takes the token representing the call and a pointer to the
509*bf6873c5SCy Schubert * action struct to fill in with the call and the option flags.
510*bf6873c5SCy Schubert */
511*bf6873c5SCy Schubert static void
parse_call(char * token,struct action * action)512*bf6873c5SCy Schubert parse_call(char *token, struct action *action)
513*bf6873c5SCy Schubert {
514*bf6873c5SCy Schubert char *flags, *flag;
515*bf6873c5SCy Schubert
516*bf6873c5SCy Schubert action->flags = 0;
517*bf6873c5SCy Schubert flags = strchr(token, '(');
518*bf6873c5SCy Schubert if (flags != NULL) {
519*bf6873c5SCy Schubert *flags = '\0';
520*bf6873c5SCy Schubert flags++;
521*bf6873c5SCy Schubert for (flag = strtok(flags, "|,)"); flag != NULL;
522*bf6873c5SCy Schubert flag = strtok(NULL, "|,)")) {
523*bf6873c5SCy Schubert action->flags |= string_to_flag(flag);
524*bf6873c5SCy Schubert }
525*bf6873c5SCy Schubert }
526*bf6873c5SCy Schubert action->call = string_to_call(token, &action->group);
527*bf6873c5SCy Schubert }
528*bf6873c5SCy Schubert
529*bf6873c5SCy Schubert
530*bf6873c5SCy Schubert /*
531*bf6873c5SCy Schubert * Parse the run section of a PAM script. This consists of one or more lines
532*bf6873c5SCy Schubert * in the format:
533*bf6873c5SCy Schubert *
534*bf6873c5SCy Schubert * <call> = <status>
535*bf6873c5SCy Schubert *
536*bf6873c5SCy Schubert * where <call> is a PAM call and <status> is what it should return. Returns
537*bf6873c5SCy Schubert * a linked list of actions. Fails on any error in parsing.
538*bf6873c5SCy Schubert */
539*bf6873c5SCy Schubert static struct action *
parse_run(FILE * script)540*bf6873c5SCy Schubert parse_run(FILE *script)
541*bf6873c5SCy Schubert {
542*bf6873c5SCy Schubert struct action *head = NULL, *current = NULL, *next;
543*bf6873c5SCy Schubert char *line, *token, *call;
544*bf6873c5SCy Schubert size_t length = 0;
545*bf6873c5SCy Schubert
546*bf6873c5SCy Schubert for (line = readline(script); line != NULL; line = readline(script)) {
547*bf6873c5SCy Schubert length = strlen(line);
548*bf6873c5SCy Schubert token = strtok(line, " ");
549*bf6873c5SCy Schubert if (token[0] == '[')
550*bf6873c5SCy Schubert break;
551*bf6873c5SCy Schubert next = bmalloc(sizeof(struct action));
552*bf6873c5SCy Schubert next->next = NULL;
553*bf6873c5SCy Schubert if (head == NULL)
554*bf6873c5SCy Schubert head = next;
555*bf6873c5SCy Schubert else
556*bf6873c5SCy Schubert current->next = next;
557*bf6873c5SCy Schubert next->name = bstrdup(token);
558*bf6873c5SCy Schubert call = token;
559*bf6873c5SCy Schubert token = strtok(NULL, " ");
560*bf6873c5SCy Schubert if (token == NULL)
561*bf6873c5SCy Schubert bail("malformed action line");
562*bf6873c5SCy Schubert if (strcmp(token, "=") != 0)
563*bf6873c5SCy Schubert bail("malformed action line near %s", token);
564*bf6873c5SCy Schubert token = strtok(NULL, " ");
565*bf6873c5SCy Schubert next->status = string_to_status(token);
566*bf6873c5SCy Schubert parse_call(call, next);
567*bf6873c5SCy Schubert free(line);
568*bf6873c5SCy Schubert current = next;
569*bf6873c5SCy Schubert }
570*bf6873c5SCy Schubert if (head == NULL)
571*bf6873c5SCy Schubert bail("empty run section in script");
572*bf6873c5SCy Schubert if (line != NULL) {
573*bf6873c5SCy Schubert free(line);
574*bf6873c5SCy Schubert rewind_section(script, length);
575*bf6873c5SCy Schubert }
576*bf6873c5SCy Schubert return head;
577*bf6873c5SCy Schubert }
578*bf6873c5SCy Schubert
579*bf6873c5SCy Schubert
580*bf6873c5SCy Schubert /*
581*bf6873c5SCy Schubert * Parse the end section of a PAM script. There is one supported line in the
582*bf6873c5SCy Schubert * format:
583*bf6873c5SCy Schubert *
584*bf6873c5SCy Schubert * flags = <flag>|<flag>
585*bf6873c5SCy Schubert *
586*bf6873c5SCy Schubert * where <flag> is a flag to pass to pam_end. Returns the flags.
587*bf6873c5SCy Schubert */
588*bf6873c5SCy Schubert static int
parse_end(FILE * script)589*bf6873c5SCy Schubert parse_end(FILE *script)
590*bf6873c5SCy Schubert {
591*bf6873c5SCy Schubert char *line, *token, *flag;
592*bf6873c5SCy Schubert size_t length = 0;
593*bf6873c5SCy Schubert int flags = PAM_SUCCESS;
594*bf6873c5SCy Schubert
595*bf6873c5SCy Schubert for (line = readline(script); line != NULL; line = readline(script)) {
596*bf6873c5SCy Schubert length = strlen(line);
597*bf6873c5SCy Schubert token = strtok(line, " ");
598*bf6873c5SCy Schubert if (token[0] == '[')
599*bf6873c5SCy Schubert break;
600*bf6873c5SCy Schubert if (strcmp(token, "flags") != 0)
601*bf6873c5SCy Schubert bail("unknown end setting %s", token);
602*bf6873c5SCy Schubert token = strtok(NULL, " ");
603*bf6873c5SCy Schubert if (token == NULL)
604*bf6873c5SCy Schubert bail("malformed end line");
605*bf6873c5SCy Schubert if (strcmp(token, "=") != 0)
606*bf6873c5SCy Schubert bail("malformed end line near %s", token);
607*bf6873c5SCy Schubert token = strtok(NULL, " ");
608*bf6873c5SCy Schubert flag = strtok(token, "|");
609*bf6873c5SCy Schubert while (flag != NULL) {
610*bf6873c5SCy Schubert flags |= string_to_status(flag);
611*bf6873c5SCy Schubert flag = strtok(NULL, "|");
612*bf6873c5SCy Schubert }
613*bf6873c5SCy Schubert free(line);
614*bf6873c5SCy Schubert }
615*bf6873c5SCy Schubert if (line != NULL) {
616*bf6873c5SCy Schubert free(line);
617*bf6873c5SCy Schubert rewind_section(script, length);
618*bf6873c5SCy Schubert }
619*bf6873c5SCy Schubert return flags;
620*bf6873c5SCy Schubert }
621*bf6873c5SCy Schubert
622*bf6873c5SCy Schubert
623*bf6873c5SCy Schubert /*
624*bf6873c5SCy Schubert * Parse the output section of a PAM script. This consists of zero or more
625*bf6873c5SCy Schubert * lines in the format:
626*bf6873c5SCy Schubert *
627*bf6873c5SCy Schubert * PRIORITY some output information
628*bf6873c5SCy Schubert * PRIORITY /output regex/
629*bf6873c5SCy Schubert *
630*bf6873c5SCy Schubert * where PRIORITY is replaced by the numeric syslog priority corresponding to
631*bf6873c5SCy Schubert * that priority and the rest of the output undergoes %-esacape expansion.
632*bf6873c5SCy Schubert * Returns the accumulated output as a vector.
633*bf6873c5SCy Schubert */
634*bf6873c5SCy Schubert static struct output *
parse_output(FILE * script,const struct script_config * config)635*bf6873c5SCy Schubert parse_output(FILE *script, const struct script_config *config)
636*bf6873c5SCy Schubert {
637*bf6873c5SCy Schubert char *line, *token, *message;
638*bf6873c5SCy Schubert struct output *output;
639*bf6873c5SCy Schubert int priority;
640*bf6873c5SCy Schubert
641*bf6873c5SCy Schubert output = output_new();
642*bf6873c5SCy Schubert if (output == NULL)
643*bf6873c5SCy Schubert sysbail("cannot allocate vector");
644*bf6873c5SCy Schubert for (line = readline(script); line != NULL; line = readline(script)) {
645*bf6873c5SCy Schubert token = strtok(line, " ");
646*bf6873c5SCy Schubert priority = string_to_priority(token);
647*bf6873c5SCy Schubert token = strtok(NULL, "");
648*bf6873c5SCy Schubert if (token == NULL)
649*bf6873c5SCy Schubert bail("malformed line %s", line);
650*bf6873c5SCy Schubert message = expand_string(token, config);
651*bf6873c5SCy Schubert output_add(output, priority, message);
652*bf6873c5SCy Schubert free(message);
653*bf6873c5SCy Schubert free(line);
654*bf6873c5SCy Schubert }
655*bf6873c5SCy Schubert return output;
656*bf6873c5SCy Schubert }
657*bf6873c5SCy Schubert
658*bf6873c5SCy Schubert
659*bf6873c5SCy Schubert /*
660*bf6873c5SCy Schubert * Parse the prompts section of a PAM script. This consists of zero or more
661*bf6873c5SCy Schubert * lines in one of the formats:
662*bf6873c5SCy Schubert *
663*bf6873c5SCy Schubert * type = prompt
664*bf6873c5SCy Schubert * type = /prompt/
665*bf6873c5SCy Schubert * type = prompt|response
666*bf6873c5SCy Schubert * type = /prompt/|response
667*bf6873c5SCy Schubert *
668*bf6873c5SCy Schubert * If the type is error_msg or info, there is no response. Otherwise,
669*bf6873c5SCy Schubert * everything after the last | is taken to be the response that should be
670*bf6873c5SCy Schubert * provided to that prompt. The response undergoes %-escape expansion.
671*bf6873c5SCy Schubert */
672*bf6873c5SCy Schubert static struct prompts *
parse_prompts(FILE * script,const struct script_config * config)673*bf6873c5SCy Schubert parse_prompts(FILE *script, const struct script_config *config)
674*bf6873c5SCy Schubert {
675*bf6873c5SCy Schubert struct prompts *prompts = NULL;
676*bf6873c5SCy Schubert struct prompt *prompt;
677*bf6873c5SCy Schubert char *line, *token, *style, *end;
678*bf6873c5SCy Schubert size_t size, count, i;
679*bf6873c5SCy Schubert size_t length = 0;
680*bf6873c5SCy Schubert
681*bf6873c5SCy Schubert for (line = readline(script); line != NULL; line = readline(script)) {
682*bf6873c5SCy Schubert length = strlen(line);
683*bf6873c5SCy Schubert token = strtok(line, " ");
684*bf6873c5SCy Schubert if (token[0] == '[')
685*bf6873c5SCy Schubert break;
686*bf6873c5SCy Schubert if (prompts == NULL) {
687*bf6873c5SCy Schubert prompts = bcalloc(1, sizeof(struct prompts));
688*bf6873c5SCy Schubert prompts->prompts = bcalloc(1, sizeof(struct prompt));
689*bf6873c5SCy Schubert prompts->allocated = 1;
690*bf6873c5SCy Schubert } else if (prompts->allocated == prompts->size) {
691*bf6873c5SCy Schubert count = prompts->allocated * 2;
692*bf6873c5SCy Schubert size = sizeof(struct prompt);
693*bf6873c5SCy Schubert prompts->prompts = breallocarray(prompts->prompts, count, size);
694*bf6873c5SCy Schubert prompts->allocated = count;
695*bf6873c5SCy Schubert for (i = prompts->size; i < prompts->allocated; i++) {
696*bf6873c5SCy Schubert prompts->prompts[i].prompt = NULL;
697*bf6873c5SCy Schubert prompts->prompts[i].response = NULL;
698*bf6873c5SCy Schubert }
699*bf6873c5SCy Schubert }
700*bf6873c5SCy Schubert prompt = &prompts->prompts[prompts->size];
701*bf6873c5SCy Schubert style = token;
702*bf6873c5SCy Schubert token = strtok(NULL, " ");
703*bf6873c5SCy Schubert if (token == NULL)
704*bf6873c5SCy Schubert bail("malformed prompt line");
705*bf6873c5SCy Schubert if (strcmp(token, "=") != 0)
706*bf6873c5SCy Schubert bail("malformed prompt line near %s", token);
707*bf6873c5SCy Schubert prompt->style = string_to_style(style);
708*bf6873c5SCy Schubert token = strtok(NULL, "");
709*bf6873c5SCy Schubert if (prompt->style == PAM_ERROR_MSG || prompt->style == PAM_TEXT_INFO)
710*bf6873c5SCy Schubert prompt->prompt = expand_string(token, config);
711*bf6873c5SCy Schubert else {
712*bf6873c5SCy Schubert end = strrchr(token, '|');
713*bf6873c5SCy Schubert if (end == NULL)
714*bf6873c5SCy Schubert bail("malformed prompt line near %s", token);
715*bf6873c5SCy Schubert *end = '\0';
716*bf6873c5SCy Schubert prompt->prompt = expand_string(token, config);
717*bf6873c5SCy Schubert token = end + 1;
718*bf6873c5SCy Schubert prompt->response = expand_string(token, config);
719*bf6873c5SCy Schubert }
720*bf6873c5SCy Schubert prompts->size++;
721*bf6873c5SCy Schubert free(line);
722*bf6873c5SCy Schubert }
723*bf6873c5SCy Schubert if (line != NULL) {
724*bf6873c5SCy Schubert free(line);
725*bf6873c5SCy Schubert rewind_section(script, length);
726*bf6873c5SCy Schubert }
727*bf6873c5SCy Schubert return prompts;
728*bf6873c5SCy Schubert }
729*bf6873c5SCy Schubert
730*bf6873c5SCy Schubert
731*bf6873c5SCy Schubert /*
732*bf6873c5SCy Schubert * Parse a PAM interaction script. This handles parsing of the top-level
733*bf6873c5SCy Schubert * section markers and dispatches the parsing to other functions. Returns the
734*bf6873c5SCy Schubert * total work to do as a work struct.
735*bf6873c5SCy Schubert */
736*bf6873c5SCy Schubert struct work *
parse_script(FILE * script,const struct script_config * config)737*bf6873c5SCy Schubert parse_script(FILE *script, const struct script_config *config)
738*bf6873c5SCy Schubert {
739*bf6873c5SCy Schubert struct work *work;
740*bf6873c5SCy Schubert char *line, *token;
741*bf6873c5SCy Schubert
742*bf6873c5SCy Schubert work = bmalloc(sizeof(struct work));
743*bf6873c5SCy Schubert memset(work, 0, sizeof(struct work));
744*bf6873c5SCy Schubert work->end_flags = PAM_SUCCESS;
745*bf6873c5SCy Schubert for (line = readline(script); line != NULL; line = readline(script)) {
746*bf6873c5SCy Schubert token = strtok(line, " ");
747*bf6873c5SCy Schubert if (token[0] != '[')
748*bf6873c5SCy Schubert bail("line outside of section: %s", line);
749*bf6873c5SCy Schubert if (strcmp(token, "[options]") == 0)
750*bf6873c5SCy Schubert parse_options(script, work, config);
751*bf6873c5SCy Schubert else if (strcmp(token, "[run]") == 0)
752*bf6873c5SCy Schubert work->actions = parse_run(script);
753*bf6873c5SCy Schubert else if (strcmp(token, "[end]") == 0)
754*bf6873c5SCy Schubert work->end_flags = parse_end(script);
755*bf6873c5SCy Schubert else if (strcmp(token, "[output]") == 0)
756*bf6873c5SCy Schubert work->output = parse_output(script, config);
757*bf6873c5SCy Schubert else if (strcmp(token, "[prompts]") == 0)
758*bf6873c5SCy Schubert work->prompts = parse_prompts(script, config);
759*bf6873c5SCy Schubert else
760*bf6873c5SCy Schubert bail("unknown section: %s", token);
761*bf6873c5SCy Schubert free(line);
762*bf6873c5SCy Schubert }
763*bf6873c5SCy Schubert if (work->actions == NULL)
764*bf6873c5SCy Schubert bail("no run section defined");
765*bf6873c5SCy Schubert return work;
766*bf6873c5SCy Schubert }
767