source: openpam/trunk/lib/openpam_configure.c @ 594

Last change on this file since 594 was 594, checked in by Dag-Erling Smørgrav, 9 years ago

Add an experimental mechanism for enabling / disabling optional features.
Use it to control policy and module file checks. The default settings
correspond to the current behavior: disallow path separators in policy
names, but allow them in module names; verify ownership and permissions
for both policy files and modules.

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