1 /*
2 * Copyright (c) 2022 Tomas Mraz <tm@t8m.info>
3 * Copyright (c) 2022 Iker Pedrosa <ipedrosa@redhat.com>
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, and the entire permission notice in its entirety,
10 * including the disclaimer of warranties.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote
15 * products derived from this software without specific prior
16 * written permission.
17 *
18 * ALTERNATIVELY, this product may be distributed under the terms of
19 * the GNU Public License, in which case the provisions of the GPL are
20 * required INSTEAD OF the above restrictions. (This clause is
21 * necessary due to a potential bad interaction between the GPL and
22 * the restrictions contained in a BSD-style copyright.)
23 *
24 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
25 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
28 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
34 * OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
36
37 #include "config.h"
38
39 #include <ctype.h>
40 #include <errno.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <syslog.h>
45
46 #include <security/pam_modules.h>
47
48 #include "faillock_config.h"
49 #include "faillock.h"
50
51 #define FAILLOCK_DEFAULT_CONF SCONFIGDIR "/faillock.conf"
52 #ifdef VENDOR_SCONFIGDIR
53 #define VENDOR_FAILLOCK_DEFAULT_CONF VENDOR_SCONFIGDIR "/faillock.conf"
54 #endif
55
56 static void PAM_FORMAT((printf, 3, 4)) PAM_NONNULL((3))
57 config_log(const pam_handle_t *pamh, int priority, const char *fmt, ...)
58 {
59 va_list args;
60
61 va_start(args, fmt);
62 if (pamh) {
63 pam_vsyslog(pamh, priority, fmt, args);
64 } else {
65 char *buf = NULL;
66
67 if (vasprintf(&buf, fmt, args) < 0) {
68 fprintf(stderr, "vasprintf: %m");
69 va_end(args);
70 return;
71 }
72 fprintf(stderr, "%s\n", buf);
73 free(buf);
74 }
75 va_end(args);
76 }
77
78 /* parse a single configuration file */
79 int
80 read_config_file(pam_handle_t *pamh, struct options *opts, const char *cfgfile)
81 {
82 char linebuf[FAILLOCK_CONF_MAX_LINELEN+1];
83 const char *fname = (cfgfile != NULL) ? cfgfile : FAILLOCK_DEFAULT_CONF;
84 FILE *f = fopen(fname, "r");
85
86 #ifdef VENDOR_FAILLOCK_DEFAULT_CONF
87 if (f == NULL && errno == ENOENT && cfgfile == NULL) {
88 /*
89 * If the default configuration file in /etc does not exist,
90 * try the vendor configuration file as fallback.
91 */
92 f = fopen(VENDOR_FAILLOCK_DEFAULT_CONF, "r");
93 }
94 #endif /* VENDOR_FAILLOCK_DEFAULT_CONF */
95
96 if (f == NULL) {
97 /* ignore non-existent default config file */
98 if (errno == ENOENT && cfgfile == NULL)
99 return PAM_SUCCESS;
100 return PAM_SERVICE_ERR;
101 }
102
103 while (fgets(linebuf, sizeof(linebuf), f) != NULL) {
104 size_t len;
105 char *ptr;
106 char *name;
107 int eq;
108
109 len = strlen(linebuf);
110 /* len cannot be 0 unless there is a bug in fgets */
111 if (len && linebuf[len - 1] != '\n' && !feof(f)) {
112 (void) fclose(f);
113 return PAM_SERVICE_ERR;
114 }
115
116 if ((ptr=strchr(linebuf, '#')) != NULL) {
117 *ptr = '\0';
118 } else {
119 ptr = linebuf + len;
120 }
121
122 /* drop terminating whitespace including the \n */
123 while (ptr > linebuf) {
124 if (!isspace(*(ptr-1))) {
125 *ptr = '\0';
126 break;
127 }
128 --ptr;
129 }
130
131 /* skip initial whitespace */
132 for (ptr = linebuf; isspace(*ptr); ptr++);
133 if (*ptr == '\0')
134 continue;
135
136 /* grab the key name */
137 eq = 0;
138 name = ptr;
139 while (*ptr != '\0') {
140 if (isspace(*ptr) || *ptr == '=') {
141 eq = *ptr == '=';
142 *ptr = '\0';
143 ++ptr;
144 break;
145 }
146 ++ptr;
147 }
148
149 /* grab the key value */
150 while (*ptr != '\0') {
151 if (*ptr != '=' || eq) {
152 if (!isspace(*ptr)) {
153 break;
154 }
155 } else {
156 eq = 1;
157 }
158 ++ptr;
159 }
160
161 /* set the key:value pair on opts */
162 set_conf_opt(pamh, opts, name, ptr);
163 }
164
165 (void)fclose(f);
166 return PAM_SUCCESS;
167 }
168
169 void
170 set_conf_opt(pam_handle_t *pamh, struct options *opts, const char *name,
171 const char *value)
172 {
173 if (strcmp(name, "dir") == 0) {
174 if (value[0] != '/') {
175 config_log(pamh, LOG_ERR,
176 "Tally directory is not absolute path (%s); keeping value",
177 value);
178 } else {
179 free(opts->dir);
180 opts->dir = strdup(value);
181 if (opts->dir == NULL) {
182 opts->fatal_error = 1;
183 config_log(pamh, LOG_CRIT, "Error allocating memory: %m");
184 }
185 }
186 }
187 else if (strcmp(name, "deny") == 0) {
188 if (sscanf(value, "%hu", &opts->deny) != 1) {
189 config_log(pamh, LOG_ERR,
190 "Bad number supplied for deny argument");
191 }
192 }
193 else if (strcmp(name, "fail_interval") == 0) {
194 unsigned int temp;
195 if (sscanf(value, "%u", &temp) != 1 ||
196 temp > MAX_TIME_INTERVAL) {
197 config_log(pamh, LOG_ERR,
198 "Bad number supplied for fail_interval argument");
199 } else {
200 opts->fail_interval = temp;
201 }
202 }
203 else if (strcmp(name, "unlock_time") == 0) {
204 unsigned int temp;
205
206 if (strcmp(value, "never") == 0) {
207 opts->unlock_time = 0;
208 }
209 else if (sscanf(value, "%u", &temp) != 1 ||
210 temp > MAX_TIME_INTERVAL) {
211 config_log(pamh, LOG_ERR,
212 "Bad number supplied for unlock_time argument");
213 }
214 else {
215 opts->unlock_time = temp;
216 }
217 }
218 else if (strcmp(name, "root_unlock_time") == 0) {
219 unsigned int temp;
220
221 if (strcmp(value, "never") == 0) {
222 opts->root_unlock_time = 0;
223 }
224 else if (sscanf(value, "%u", &temp) != 1 ||
225 temp > MAX_TIME_INTERVAL) {
226 config_log(pamh, LOG_ERR,
227 "Bad number supplied for root_unlock_time argument");
228 } else {
229 opts->root_unlock_time = temp;
230 }
231 }
232 else if (strcmp(name, "admin_group") == 0) {
233 free(opts->admin_group);
234 opts->admin_group = strdup(value);
235 if (opts->admin_group == NULL) {
236 opts->fatal_error = 1;
237 config_log(pamh, LOG_CRIT, "Error allocating memory: %m");
238 }
239 }
240 else if (strcmp(name, "even_deny_root") == 0) {
241 opts->flags |= FAILLOCK_FLAG_DENY_ROOT;
242 }
243 else if (strcmp(name, "audit") == 0) {
244 opts->flags |= FAILLOCK_FLAG_AUDIT;
245 }
246 else if (strcmp(name, "silent") == 0) {
247 opts->flags |= FAILLOCK_FLAG_SILENT;
248 }
249 else if (strcmp(name, "no_log_info") == 0) {
250 opts->flags |= FAILLOCK_FLAG_NO_LOG_INFO;
251 }
252 else if (strcmp(name, "local_users_only") == 0) {
253 opts->flags |= FAILLOCK_FLAG_LOCAL_ONLY;
254 }
255 else if (strcmp(name, "nodelay") == 0) {
256 opts->flags |= FAILLOCK_FLAG_NO_DELAY;
257 }
258 else {
259 config_log(pamh, LOG_ERR, "Unknown option: %s", name);
260 }
261 }
262
263 const char *get_tally_dir(const struct options *opts)
264 {
265 return (opts->dir != NULL) ? opts->dir : FAILLOCK_DEFAULT_TALLYDIR;
266 }