source: openpam/trunk/lib/libpam/openpam_configure.c @ 745

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

Currently, openpam_policy_path is a hardcoded array of short strings, and
filename is much larger than it needs to be. However, this might not be
the case in the future. To be safe, add a length check after strlcpy().
This should silence a Coverity warning about possible array overflow.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 11.5 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 745 2013-09-21 23:22:48Z des $
36 */
37
38#ifdef HAVE_CONFIG_H
39# include "config.h"
40#endif
41
42#include <sys/param.h>
43
44#include <errno.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48
49#include <security/pam_appl.h>
50
51#include "openpam_impl.h"
52#include "openpam_ctype.h"
53#include "openpam_strlcat.h"
54#include "openpam_strlcpy.h"
55
56static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
57
58/*
59 * Validate a service name.
60 *
61 * Returns a non-zero value if the argument points to a NUL-terminated
62 * string consisting entirely of characters in the POSIX portable filename
63 * character set, excluding the path separator character.
64 */
65static int
66valid_service_name(const char *name)
67{
68        const char *p;
69
70        if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) {
71                /* path separator not allowed */
72                for (p = name; *p != '\0'; ++p)
73                        if (!is_pfcs(*p))
74                                return (0);
75        } else {
76                /* path separator allowed */
77                for (p = name; *p != '\0'; ++p)
78                        if (!is_pfcs(*p) && *p != '/')
79                                return (0);
80        }
81        return (1);
82}
83
84/*
85 * Parse the facility name.
86 *
87 * Returns the corresponding pam_facility_t value, or -1 if the argument
88 * is not a valid facility name.
89 */
90static pam_facility_t
91parse_facility_name(const char *name)
92{
93        int i;
94
95        for (i = 0; i < PAM_NUM_FACILITIES; ++i)
96                if (strcmp(pam_facility_name[i], name) == 0)
97                        return (i);
98        return ((pam_facility_t)-1);
99}
100
101/*
102 * Parse the control flag.
103 *
104 * Returns the corresponding pam_control_t value, or -1 if the argument is
105 * not a valid control flag name.
106 */
107static pam_control_t
108parse_control_flag(const char *name)
109{
110        int i;
111
112        for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
113                if (strcmp(pam_control_flag_name[i], name) == 0)
114                        return (i);
115        return ((pam_control_t)-1);
116}
117
118/*
119 * Validate a file name.
120 *
121 * Returns a non-zero value if the argument points to a NUL-terminated
122 * string consisting entirely of characters in the POSIX portable filename
123 * character set, including the path separator character.
124 */
125static int
126valid_module_name(const char *name)
127{
128        const char *p;
129
130        if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) {
131                /* path separator not allowed */
132                for (p = name; *p != '\0'; ++p)
133                        if (!is_pfcs(*p))
134                                return (0);
135        } else {
136                /* path separator allowed */
137                for (p = name; *p != '\0'; ++p)
138                        if (!is_pfcs(*p) && *p != '/')
139                                return (0);
140        }
141        return (1);
142}
143
144typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
145
146/*
147 * Extracts given chains from a policy file.
148 *
149 * Returns the number of policy entries which were found for the specified
150 * service and facility, or -1 if a system error occurred or a syntax
151 * error was encountered.
152 */
153static int
154openpam_parse_chain(pam_handle_t *pamh,
155        const char *service,
156        pam_facility_t facility,
157        FILE *f,
158        const char *filename,
159        openpam_style_t style)
160{
161        pam_chain_t *this, **next;
162        pam_facility_t fclt;
163        pam_control_t ctlf;
164        char *name, *servicename, *modulename;
165        int count, lineno, ret, serrno;
166        char **wordv, *word;
167        int i, wordc;
168
169        count = 0;
170        this = NULL;
171        name = NULL;
172        lineno = 0;
173        wordc = 0;
174        wordv = NULL;
175        while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) {
176                /* blank line? */
177                if (wordc == 0) {
178                        FREEV(wordc, wordv);
179                        continue;
180                }
181                i = 0;
182
183                /* check service name if necessary */
184                if (style == pam_conf_style &&
185                    strcmp(wordv[i++], service) != 0) {
186                        FREEV(wordc, wordv);
187                        continue;
188                }
189
190                /* check facility name */
191                if ((word = wordv[i++]) == NULL ||
192                    (fclt = parse_facility_name(word)) == (pam_facility_t)-1) {
193                        openpam_log(PAM_LOG_ERROR,
194                            "%s(%d): missing or invalid facility",
195                            filename, lineno);
196                        goto fail;
197                }
198                if (facility != fclt && facility != PAM_FACILITY_ANY) {
199                        FREEV(wordc, wordv);
200                        continue;
201                }
202
203                /* check for "include" */
204                if ((word = wordv[i++]) != NULL &&
205                    strcmp(word, "include") == 0) {
206                        if ((servicename = wordv[i++]) == NULL ||
207                            !valid_service_name(servicename)) {
208                                openpam_log(PAM_LOG_ERROR,
209                                    "%s(%d): missing or invalid service name",
210                                    filename, lineno);
211                                goto fail;
212                        }
213                        if (wordv[i] != NULL) {
214                                openpam_log(PAM_LOG_ERROR,
215                                    "%s(%d): garbage at end of line",
216                                    filename, lineno);
217                                goto fail;
218                        }
219                        ret = openpam_load_chain(pamh, servicename, fclt);
220                        FREEV(wordc, wordv);
221                        if (ret < 0)
222                                goto fail;
223                        continue;
224                }
225
226                /* get control flag */
227                if (word == NULL || /* same word we compared to "include" */
228                    (ctlf = parse_control_flag(word)) == (pam_control_t)-1) {
229                        openpam_log(PAM_LOG_ERROR,
230                            "%s(%d): missing or invalid control flag",
231                            filename, lineno);
232                        goto fail;
233                }
234
235                /* get module name */
236                if ((modulename = wordv[i++]) == NULL ||
237                    !valid_module_name(modulename)) {
238                        openpam_log(PAM_LOG_ERROR,
239                            "%s(%d): missing or invalid module name",
240                            filename, lineno);
241                        goto fail;
242                }
243
244                /* allocate new entry */
245                if ((this = calloc(1, sizeof *this)) == NULL)
246                        goto syserr;
247                this->flag = ctlf;
248
249                /* load module */
250                if ((this->module = openpam_load_module(modulename)) == NULL)
251                        goto fail;
252
253                /*
254                 * The remaining items in wordv are the module's
255                 * arguments.  We could set this->optv = wordv + i, but
256                 * then free(this->optv) wouldn't work.  Instead, we free
257                 * the words we've already consumed, shift the rest up,
258                 * and clear the tail end of the array.
259                 */
260                this->optc = wordc - i;
261                for (i = 0; i < wordc - this->optc; ++i) {
262                        FREE(wordv[i]);
263                }
264                for (i = 0; i < this->optc; ++i) {
265                        wordv[i] = wordv[wordc - this->optc + i];
266                        wordv[wordc - this->optc + i] = NULL;
267                }
268                this->optv = wordv;
269                wordv = NULL;
270                wordc = 0;
271
272                /* hook it up */
273                for (next = &pamh->chains[fclt]; *next != NULL;
274                     next = &(*next)->next)
275                        /* nothing */ ;
276                *next = this;
277                this = NULL;
278                ++count;
279        }
280        /*
281         * The loop ended because openpam_readword() returned NULL, which
282         * can happen for four different reasons: an I/O error (ferror(f)
283         * is true), a memory allocation failure (ferror(f) is false,
284         * errno is non-zero)
285         */
286        if (ferror(f) || errno != 0)
287                goto syserr;
288        if (!feof(f))
289                goto fail;
290        fclose(f);
291        return (count);
292syserr:
293        serrno = errno;
294        openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
295        errno = serrno;
296        /* fall through */
297fail:
298        serrno = errno;
299        if (this && this->optc && this->optv)
300                FREEV(this->optc, this->optv);
301        FREE(this);
302        FREEV(wordc, wordv);
303        FREE(wordv);
304        FREE(name);
305        fclose(f);
306        errno = serrno;
307        return (-1);
308}
309
310/*
311 * Read the specified chains from the specified file.
312 *
313 * Returns 0 if the file exists but does not contain any matching lines.
314 *
315 * Returns -1 and sets errno to ENOENT if the file does not exist.
316 *
317 * Returns -1 and sets errno to some other non-zero value if the file
318 * exists but is unsafe or unreadable, or an I/O error occurs.
319 */
320static int
321openpam_load_file(pam_handle_t *pamh,
322        const char *service,
323        pam_facility_t facility,
324        const char *filename,
325        openpam_style_t style)
326{
327        FILE *f;
328        int ret, serrno;
329
330        /* attempt to open the file */
331        if ((f = fopen(filename, "r")) == NULL) {
332                serrno = errno;
333                openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR,
334                    "%s: %m", filename);
335                errno = serrno;
336                RETURNN(-1);
337        } else {
338                openpam_log(PAM_LOG_DEBUG, "found %s", filename);
339        }
340
341        /* verify type, ownership and permissions */
342        if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) &&
343            openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
344                /* already logged the cause */
345                serrno = errno;
346                fclose(f);
347                errno = serrno;
348                RETURNN(-1);
349        }
350
351        /* parse the file */
352        ret = openpam_parse_chain(pamh, service, facility,
353            f, filename, style);
354        RETURNN(ret);
355}
356
357/*
358 * Locates the policy file for a given service and reads the given chains
359 * from it.
360 *
361 * Returns the number of policy entries which were found for the specified
362 * service and facility, or -1 if a system error occurred or a syntax
363 * error was encountered.
364 */
365static int
366openpam_load_chain(pam_handle_t *pamh,
367        const char *service,
368        pam_facility_t facility)
369{
370        const char *p, **path;
371        char filename[PATH_MAX];
372        size_t len;
373        openpam_style_t style;
374        int ret;
375
376        ENTERS(facility < 0 ? "any" : pam_facility_name[facility]);
377
378        /* either absolute or relative to cwd */
379        if (strchr(service, '/') != NULL) {
380                if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0)
381                        style = pam_conf_style;
382                else
383                        style = pam_d_style;
384                ret = openpam_load_file(pamh, service, facility,
385                    service, style);
386                RETURNN(ret);
387        }
388
389        /* search standard locations */
390        for (path = openpam_policy_path; *path != NULL; ++path) {
391                /* construct filename */
392                len = strlcpy(filename, *path, sizeof filename);
393                if (len >= sizeof filename) {
394                        errno = ENAMETOOLONG;
395                        RETURNN(-1);
396                }
397                if (filename[len - 1] == '/') {
398                        len = strlcat(filename, service, sizeof filename);
399                        if (len >= sizeof filename) {
400                                errno = ENAMETOOLONG;
401                                RETURNN(-1);
402                        }
403                        style = pam_d_style;
404                } else {
405                        style = pam_conf_style;
406                }
407                ret = openpam_load_file(pamh, service, facility,
408                    filename, style);
409                /* the file exists, but an error occurred */
410                if (ret == -1 && errno != ENOENT)
411                        RETURNN(ret);
412                /* in pam.d style, an empty file counts as a hit */
413                if (ret == 0 && style == pam_d_style)
414                        RETURNN(ret);
415        }
416
417        /* no hit */
418        RETURNN(0);
419}
420
421/*
422 * OpenPAM internal
423 *
424 * Configure a service
425 */
426
427int
428openpam_configure(pam_handle_t *pamh,
429        const char *service)
430{
431        pam_facility_t fclt;
432        int serrno;
433
434        ENTERS(service);
435        if (!valid_service_name(service)) {
436                openpam_log(PAM_LOG_ERROR, "invalid service name");
437                RETURNC(PAM_SYSTEM_ERR);
438        }
439        if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0)
440                goto load_err;
441        for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
442                if (pamh->chains[fclt] != NULL)
443                        continue;
444                if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
445                        goto load_err;
446        }
447        RETURNC(PAM_SUCCESS);
448load_err:
449        serrno = errno;
450        openpam_clear_chains(pamh->chains);
451        errno = serrno;
452        RETURNC(PAM_SYSTEM_ERR);
453}
454
455/*
456 * NODOC
457 *
458 * Error codes:
459 *      PAM_SYSTEM_ERR
460 */
Note: See TracBrowser for help on using the repository browser.