openpam_configure.c revision 1.2 1 1.2 christos /* $NetBSD: openpam_configure.c,v 1.2 2014/10/24 18:17:56 christos Exp $ */
2 1.2 christos
3 1.1 christos /*-
4 1.1 christos * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
5 1.1 christos * Copyright (c) 2004-2014 Dag-Erling Smrgrav
6 1.1 christos * All rights reserved.
7 1.1 christos *
8 1.1 christos * This software was developed for the FreeBSD Project by ThinkSec AS and
9 1.1 christos * Network Associates Laboratories, the Security Research Division of
10 1.1 christos * Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
11 1.1 christos * ("CBOSS"), as part of the DARPA CHATS research program.
12 1.1 christos *
13 1.1 christos * Redistribution and use in source and binary forms, with or without
14 1.1 christos * modification, are permitted provided that the following conditions
15 1.1 christos * are met:
16 1.1 christos * 1. Redistributions of source code must retain the above copyright
17 1.1 christos * notice, this list of conditions and the following disclaimer.
18 1.1 christos * 2. Redistributions in binary form must reproduce the above copyright
19 1.1 christos * notice, this list of conditions and the following disclaimer in the
20 1.1 christos * documentation and/or other materials provided with the distribution.
21 1.1 christos * 3. The name of the author may not be used to endorse or promote
22 1.1 christos * products derived from this software without specific prior written
23 1.1 christos * permission.
24 1.1 christos *
25 1.1 christos * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
26 1.1 christos * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 1.1 christos * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 1.1 christos * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
29 1.1 christos * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 1.1 christos * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 1.1 christos * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 1.1 christos * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 1.1 christos * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 1.1 christos * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 1.1 christos * SUCH DAMAGE.
36 1.1 christos *
37 1.1 christos * Id: openpam_configure.c 796 2014-06-03 21:30:08Z des
38 1.1 christos */
39 1.1 christos
40 1.1 christos #ifdef HAVE_CONFIG_H
41 1.1 christos # include "config.h"
42 1.1 christos #endif
43 1.1 christos
44 1.2 christos #include <sys/cdefs.h>
45 1.2 christos __RCSID("$NetBSD: openpam_configure.c,v 1.2 2014/10/24 18:17:56 christos Exp $");
46 1.2 christos
47 1.1 christos #include <sys/param.h>
48 1.1 christos
49 1.1 christos #include <errno.h>
50 1.1 christos #include <stdio.h>
51 1.1 christos #include <stdlib.h>
52 1.1 christos #include <string.h>
53 1.1 christos
54 1.1 christos #include <security/pam_appl.h>
55 1.1 christos
56 1.1 christos #include "openpam_impl.h"
57 1.1 christos #include "openpam_ctype.h"
58 1.1 christos #include "openpam_strlcat.h"
59 1.1 christos #include "openpam_strlcpy.h"
60 1.1 christos
61 1.1 christos static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
62 1.1 christos
63 1.1 christos /*
64 1.1 christos * Validate a service name.
65 1.1 christos *
66 1.1 christos * Returns a non-zero value if the argument points to a NUL-terminated
67 1.1 christos * string consisting entirely of characters in the POSIX portable filename
68 1.1 christos * character set, excluding the path separator character.
69 1.1 christos */
70 1.1 christos static int
71 1.1 christos valid_service_name(const char *name)
72 1.1 christos {
73 1.1 christos const char *p;
74 1.1 christos
75 1.1 christos if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) {
76 1.1 christos /* path separator not allowed */
77 1.1 christos for (p = name; *p != '\0'; ++p)
78 1.1 christos if (!is_pfcs(*p))
79 1.1 christos return (0);
80 1.1 christos } else {
81 1.1 christos /* path separator allowed */
82 1.1 christos for (p = name; *p != '\0'; ++p)
83 1.1 christos if (!is_pfcs(*p) && *p != '/')
84 1.1 christos return (0);
85 1.1 christos }
86 1.1 christos return (1);
87 1.1 christos }
88 1.1 christos
89 1.1 christos /*
90 1.1 christos * Parse the facility name.
91 1.1 christos *
92 1.1 christos * Returns the corresponding pam_facility_t value, or -1 if the argument
93 1.1 christos * is not a valid facility name.
94 1.1 christos */
95 1.1 christos static pam_facility_t
96 1.1 christos parse_facility_name(const char *name)
97 1.1 christos {
98 1.1 christos int i;
99 1.1 christos
100 1.1 christos for (i = 0; i < PAM_NUM_FACILITIES; ++i)
101 1.1 christos if (strcmp(pam_facility_name[i], name) == 0)
102 1.1 christos return (i);
103 1.1 christos return ((pam_facility_t)-1);
104 1.1 christos }
105 1.1 christos
106 1.1 christos /*
107 1.1 christos * Parse the control flag.
108 1.1 christos *
109 1.1 christos * Returns the corresponding pam_control_t value, or -1 if the argument is
110 1.1 christos * not a valid control flag name.
111 1.1 christos */
112 1.1 christos static pam_control_t
113 1.1 christos parse_control_flag(const char *name)
114 1.1 christos {
115 1.2 christos pam_control_t i;
116 1.1 christos
117 1.2 christos for (i = PAM_BINDING; i < PAM_NUM_CONTROL_FLAGS; ++i)
118 1.1 christos if (strcmp(pam_control_flag_name[i], name) == 0)
119 1.1 christos return (i);
120 1.1 christos return ((pam_control_t)-1);
121 1.1 christos }
122 1.1 christos
123 1.1 christos /*
124 1.1 christos * Validate a file name.
125 1.1 christos *
126 1.1 christos * Returns a non-zero value if the argument points to a NUL-terminated
127 1.1 christos * string consisting entirely of characters in the POSIX portable filename
128 1.1 christos * character set, including the path separator character.
129 1.1 christos */
130 1.1 christos static int
131 1.1 christos valid_module_name(const char *name)
132 1.1 christos {
133 1.1 christos const char *p;
134 1.1 christos
135 1.1 christos if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) {
136 1.1 christos /* path separator not allowed */
137 1.1 christos for (p = name; *p != '\0'; ++p)
138 1.1 christos if (!is_pfcs(*p))
139 1.1 christos return (0);
140 1.1 christos } else {
141 1.1 christos /* path separator allowed */
142 1.1 christos for (p = name; *p != '\0'; ++p)
143 1.1 christos if (!is_pfcs(*p) && *p != '/')
144 1.1 christos return (0);
145 1.1 christos }
146 1.1 christos return (1);
147 1.1 christos }
148 1.1 christos
149 1.1 christos typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
150 1.1 christos
151 1.1 christos /*
152 1.1 christos * Extracts given chains from a policy file.
153 1.1 christos *
154 1.1 christos * Returns the number of policy entries which were found for the specified
155 1.1 christos * service and facility, or -1 if a system error occurred or a syntax
156 1.1 christos * error was encountered.
157 1.1 christos */
158 1.1 christos static int
159 1.1 christos openpam_parse_chain(pam_handle_t *pamh,
160 1.1 christos const char *service,
161 1.1 christos pam_facility_t facility,
162 1.1 christos FILE *f,
163 1.1 christos const char *filename,
164 1.1 christos openpam_style_t style)
165 1.1 christos {
166 1.1 christos pam_chain_t *this, **next;
167 1.1 christos pam_facility_t fclt;
168 1.1 christos pam_control_t ctlf;
169 1.1 christos char *name, *servicename, *modulename;
170 1.1 christos int count, lineno, ret, serrno;
171 1.1 christos char **wordv, *word;
172 1.1 christos int i, wordc;
173 1.1 christos
174 1.1 christos count = 0;
175 1.1 christos this = NULL;
176 1.1 christos name = NULL;
177 1.1 christos lineno = 0;
178 1.1 christos wordc = 0;
179 1.1 christos wordv = NULL;
180 1.1 christos while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) {
181 1.1 christos /* blank line? */
182 1.1 christos if (wordc == 0) {
183 1.1 christos FREEV(wordc, wordv);
184 1.1 christos continue;
185 1.1 christos }
186 1.1 christos i = 0;
187 1.1 christos
188 1.1 christos /* check service name if necessary */
189 1.1 christos if (style == pam_conf_style &&
190 1.1 christos strcmp(wordv[i++], service) != 0) {
191 1.1 christos FREEV(wordc, wordv);
192 1.1 christos continue;
193 1.1 christos }
194 1.1 christos
195 1.1 christos /* check facility name */
196 1.1 christos if ((word = wordv[i++]) == NULL ||
197 1.1 christos (fclt = parse_facility_name(word)) == (pam_facility_t)-1) {
198 1.1 christos openpam_log(PAM_LOG_ERROR,
199 1.1 christos "%s(%d): missing or invalid facility",
200 1.1 christos filename, lineno);
201 1.1 christos errno = EINVAL;
202 1.1 christos goto fail;
203 1.1 christos }
204 1.1 christos if (facility != fclt && facility != PAM_FACILITY_ANY) {
205 1.1 christos FREEV(wordc, wordv);
206 1.1 christos continue;
207 1.1 christos }
208 1.1 christos
209 1.1 christos /* check for "include" */
210 1.1 christos if ((word = wordv[i++]) != NULL &&
211 1.1 christos strcmp(word, "include") == 0) {
212 1.1 christos if ((servicename = wordv[i++]) == NULL ||
213 1.1 christos !valid_service_name(servicename)) {
214 1.1 christos openpam_log(PAM_LOG_ERROR,
215 1.1 christos "%s(%d): missing or invalid service name",
216 1.1 christos filename, lineno);
217 1.1 christos errno = EINVAL;
218 1.1 christos goto fail;
219 1.1 christos }
220 1.1 christos if (wordv[i] != NULL) {
221 1.1 christos openpam_log(PAM_LOG_ERROR,
222 1.1 christos "%s(%d): garbage at end of line",
223 1.1 christos filename, lineno);
224 1.1 christos errno = EINVAL;
225 1.1 christos goto fail;
226 1.1 christos }
227 1.1 christos ret = openpam_load_chain(pamh, servicename, fclt);
228 1.1 christos FREEV(wordc, wordv);
229 1.1 christos if (ret < 0) {
230 1.1 christos /*
231 1.1 christos * Bogus errno, but this ensures that the
232 1.1 christos * outer loop does not just ignore the
233 1.1 christos * error and keep searching.
234 1.1 christos */
235 1.1 christos if (errno == ENOENT)
236 1.1 christos errno = EINVAL;
237 1.1 christos goto fail;
238 1.1 christos }
239 1.1 christos continue;
240 1.1 christos }
241 1.1 christos
242 1.1 christos /* get control flag */
243 1.1 christos if (word == NULL || /* same word we compared to "include" */
244 1.1 christos (ctlf = parse_control_flag(word)) == (pam_control_t)-1) {
245 1.1 christos openpam_log(PAM_LOG_ERROR,
246 1.1 christos "%s(%d): missing or invalid control flag",
247 1.1 christos filename, lineno);
248 1.1 christos errno = EINVAL;
249 1.1 christos goto fail;
250 1.1 christos }
251 1.1 christos
252 1.1 christos /* get module name */
253 1.1 christos if ((modulename = wordv[i++]) == NULL ||
254 1.1 christos !valid_module_name(modulename)) {
255 1.1 christos openpam_log(PAM_LOG_ERROR,
256 1.1 christos "%s(%d): missing or invalid module name",
257 1.1 christos filename, lineno);
258 1.1 christos errno = EINVAL;
259 1.1 christos goto fail;
260 1.1 christos }
261 1.1 christos
262 1.1 christos /* allocate new entry */
263 1.2 christos if ((this = calloc((size_t)1, sizeof *this)) == NULL)
264 1.1 christos goto syserr;
265 1.1 christos this->flag = ctlf;
266 1.1 christos
267 1.1 christos /* load module */
268 1.1 christos if ((this->module = openpam_load_module(modulename)) == NULL) {
269 1.1 christos if (errno == ENOENT)
270 1.1 christos errno = ENOEXEC;
271 1.1 christos goto fail;
272 1.1 christos }
273 1.1 christos
274 1.1 christos /*
275 1.1 christos * The remaining items in wordv are the module's
276 1.1 christos * arguments. We could set this->optv = wordv + i, but
277 1.1 christos * then free(this->optv) wouldn't work. Instead, we free
278 1.1 christos * the words we've already consumed, shift the rest up,
279 1.1 christos * and clear the tail end of the array.
280 1.1 christos */
281 1.1 christos this->optc = wordc - i;
282 1.1 christos for (i = 0; i < wordc - this->optc; ++i) {
283 1.1 christos FREE(wordv[i]);
284 1.1 christos }
285 1.1 christos for (i = 0; i < this->optc; ++i) {
286 1.1 christos wordv[i] = wordv[wordc - this->optc + i];
287 1.1 christos wordv[wordc - this->optc + i] = NULL;
288 1.1 christos }
289 1.1 christos this->optv = wordv;
290 1.1 christos wordv = NULL;
291 1.1 christos wordc = 0;
292 1.1 christos
293 1.1 christos /* hook it up */
294 1.1 christos for (next = &pamh->chains[fclt]; *next != NULL;
295 1.1 christos next = &(*next)->next)
296 1.1 christos /* nothing */ ;
297 1.1 christos *next = this;
298 1.1 christos this = NULL;
299 1.1 christos ++count;
300 1.1 christos }
301 1.1 christos /*
302 1.1 christos * The loop ended because openpam_readword() returned NULL, which
303 1.1 christos * can happen for four different reasons: an I/O error (ferror(f)
304 1.1 christos * is true), a memory allocation failure (ferror(f) is false,
305 1.1 christos * feof(f) is false, errno is non-zero), the file ended with an
306 1.1 christos * unterminated quote or backslash escape (ferror(f) is false,
307 1.1 christos * feof(f) is true, errno is non-zero), or the end of the file was
308 1.1 christos * reached without error (ferror(f) is false, feof(f) is true,
309 1.1 christos * errno is zero).
310 1.1 christos */
311 1.1 christos if (ferror(f) || errno != 0)
312 1.1 christos goto syserr;
313 1.1 christos if (!feof(f))
314 1.1 christos goto fail;
315 1.1 christos fclose(f);
316 1.1 christos return (count);
317 1.1 christos syserr:
318 1.1 christos serrno = errno;
319 1.1 christos openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
320 1.1 christos errno = serrno;
321 1.1 christos /* fall through */
322 1.1 christos fail:
323 1.1 christos serrno = errno;
324 1.1 christos if (this && this->optc && this->optv)
325 1.1 christos FREEV(this->optc, this->optv);
326 1.1 christos FREE(this);
327 1.1 christos FREEV(wordc, wordv);
328 1.1 christos FREE(wordv);
329 1.1 christos FREE(name);
330 1.1 christos fclose(f);
331 1.1 christos errno = serrno;
332 1.1 christos return (-1);
333 1.1 christos }
334 1.1 christos
335 1.1 christos /*
336 1.1 christos * Read the specified chains from the specified file.
337 1.1 christos *
338 1.1 christos * Returns 0 if the file exists but does not contain any matching lines.
339 1.1 christos *
340 1.1 christos * Returns -1 and sets errno to ENOENT if the file does not exist.
341 1.1 christos *
342 1.1 christos * Returns -1 and sets errno to some other non-zero value if the file
343 1.1 christos * exists but is unsafe or unreadable, or an I/O error occurs.
344 1.1 christos */
345 1.1 christos static int
346 1.1 christos openpam_load_file(pam_handle_t *pamh,
347 1.1 christos const char *service,
348 1.1 christos pam_facility_t facility,
349 1.1 christos const char *filename,
350 1.1 christos openpam_style_t style)
351 1.1 christos {
352 1.1 christos FILE *f;
353 1.1 christos int ret, serrno;
354 1.1 christos
355 1.1 christos /* attempt to open the file */
356 1.1 christos if ((f = fopen(filename, "r")) == NULL) {
357 1.1 christos serrno = errno;
358 1.1 christos openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR,
359 1.1 christos "%s: %m", filename);
360 1.1 christos errno = serrno;
361 1.1 christos RETURNN(-1);
362 1.1 christos } else {
363 1.1 christos openpam_log(PAM_LOG_DEBUG, "found %s", filename);
364 1.1 christos }
365 1.1 christos
366 1.1 christos /* verify type, ownership and permissions */
367 1.1 christos if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) &&
368 1.1 christos openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
369 1.1 christos /* already logged the cause */
370 1.1 christos serrno = errno;
371 1.1 christos fclose(f);
372 1.1 christos errno = serrno;
373 1.1 christos RETURNN(-1);
374 1.1 christos }
375 1.1 christos
376 1.1 christos /* parse the file */
377 1.1 christos ret = openpam_parse_chain(pamh, service, facility,
378 1.1 christos f, filename, style);
379 1.1 christos RETURNN(ret);
380 1.1 christos }
381 1.1 christos
382 1.1 christos /*
383 1.1 christos * Locates the policy file for a given service and reads the given chains
384 1.1 christos * from it.
385 1.1 christos *
386 1.1 christos * Returns the number of policy entries which were found for the specified
387 1.1 christos * service and facility, or -1 if a system error occurred or a syntax
388 1.1 christos * error was encountered.
389 1.1 christos */
390 1.1 christos static int
391 1.1 christos openpam_load_chain(pam_handle_t *pamh,
392 1.1 christos const char *service,
393 1.1 christos pam_facility_t facility)
394 1.1 christos {
395 1.1 christos const char *p, **path;
396 1.1 christos char filename[PATH_MAX];
397 1.1 christos size_t len;
398 1.1 christos openpam_style_t style;
399 1.1 christos int ret;
400 1.1 christos
401 1.1 christos ENTERS(facility < 0 ? "any" : pam_facility_name[facility]);
402 1.1 christos
403 1.1 christos /* either absolute or relative to cwd */
404 1.1 christos if (strchr(service, '/') != NULL) {
405 1.1 christos if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0)
406 1.1 christos style = pam_conf_style;
407 1.1 christos else
408 1.1 christos style = pam_d_style;
409 1.1 christos ret = openpam_load_file(pamh, service, facility,
410 1.1 christos service, style);
411 1.1 christos RETURNN(ret);
412 1.1 christos }
413 1.1 christos
414 1.1 christos /* search standard locations */
415 1.1 christos for (path = openpam_policy_path; *path != NULL; ++path) {
416 1.1 christos /* construct filename */
417 1.1 christos len = strlcpy(filename, *path, sizeof filename);
418 1.1 christos if (filename[len - 1] == '/') {
419 1.1 christos len = strlcat(filename, service, sizeof filename);
420 1.1 christos if (len >= sizeof filename) {
421 1.1 christos errno = ENAMETOOLONG;
422 1.1 christos RETURNN(-1);
423 1.1 christos }
424 1.1 christos style = pam_d_style;
425 1.1 christos } else {
426 1.1 christos style = pam_conf_style;
427 1.1 christos }
428 1.1 christos ret = openpam_load_file(pamh, service, facility,
429 1.1 christos filename, style);
430 1.1 christos /* success */
431 1.1 christos if (ret > 0)
432 1.1 christos RETURNN(ret);
433 1.1 christos /* the file exists, but an error occurred */
434 1.1 christos if (ret == -1 && errno != ENOENT)
435 1.1 christos RETURNN(ret);
436 1.1 christos /* in pam.d style, an empty file counts as a hit */
437 1.1 christos if (ret == 0 && style == pam_d_style)
438 1.1 christos RETURNN(ret);
439 1.1 christos }
440 1.1 christos
441 1.1 christos /* no hit */
442 1.1 christos errno = ENOENT;
443 1.1 christos RETURNN(-1);
444 1.1 christos }
445 1.1 christos
446 1.1 christos /*
447 1.1 christos * OpenPAM internal
448 1.1 christos *
449 1.1 christos * Configure a service
450 1.1 christos */
451 1.1 christos
452 1.1 christos int
453 1.1 christos openpam_configure(pam_handle_t *pamh,
454 1.1 christos const char *service)
455 1.1 christos {
456 1.1 christos pam_facility_t fclt;
457 1.1 christos int serrno;
458 1.1 christos
459 1.1 christos ENTERS(service);
460 1.1 christos if (!valid_service_name(service)) {
461 1.1 christos openpam_log(PAM_LOG_ERROR, "invalid service name");
462 1.1 christos RETURNC(PAM_SYSTEM_ERR);
463 1.1 christos }
464 1.1 christos if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) {
465 1.1 christos if (errno != ENOENT)
466 1.1 christos goto load_err;
467 1.1 christos }
468 1.1 christos for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
469 1.1 christos if (pamh->chains[fclt] != NULL)
470 1.1 christos continue;
471 1.1 christos if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
472 1.1 christos goto load_err;
473 1.1 christos }
474 1.2 christos #ifdef __NetBSD__
475 1.2 christos /*
476 1.2 christos * On NetBSD we require the AUTH chain to have a binding,
477 1.2 christos * a required, or requisite module.
478 1.2 christos */
479 1.2 christos {
480 1.2 christos pam_chain_t *this = pamh->chains[PAM_AUTH];
481 1.2 christos for (; this != NULL; this = this->next)
482 1.2 christos if (this->flag == PAM_BINDING ||
483 1.2 christos this->flag == PAM_REQUIRED ||
484 1.2 christos this->flag == PAM_REQUISITE)
485 1.2 christos break;
486 1.2 christos if (this == NULL) {
487 1.2 christos openpam_log(PAM_LOG_ERROR,
488 1.2 christos "No required, requisite, or binding component "
489 1.2 christos "in service %s, facility %s",
490 1.2 christos service, pam_facility_name[PAM_AUTH]);
491 1.2 christos goto load_err;
492 1.2 christos }
493 1.2 christos }
494 1.2 christos #endif
495 1.1 christos RETURNC(PAM_SUCCESS);
496 1.1 christos load_err:
497 1.1 christos serrno = errno;
498 1.1 christos openpam_clear_chains(pamh->chains);
499 1.1 christos errno = serrno;
500 1.1 christos RETURNC(PAM_SYSTEM_ERR);
501 1.1 christos }
502 1.1 christos
503 1.1 christos /*
504 1.1 christos * NODOC
505 1.1 christos *
506 1.1 christos * Error codes:
507 1.1 christos * PAM_SYSTEM_ERR
508 1.1 christos */
509