]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/openpam/lib/libpam/openpam_configure.c
Fix sendmail improper close-on-exec flag handling. [SA-14:11]
[FreeBSD/releng/10.0.git] / contrib / openpam / lib / libpam / openpam_configure.c
1 /*-
2  * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
3  * Copyright (c) 2004-2014 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 667 2013-03-17 14:24:00Z 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
56 static 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  */
65 static int
66 valid_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  */
90 static pam_facility_t
91 parse_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  */
107 static pam_control_t
108 parse_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  */
125 static int
126 valid_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
144 typedef 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  */
153 static int
154 openpam_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                         errno = EINVAL;
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                                 errno = EINVAL;
213                                 goto fail;
214                         }
215                         if (wordv[i] != NULL) {
216                                 openpam_log(PAM_LOG_ERROR,
217                                     "%s(%d): garbage at end of line",
218                                     filename, lineno);
219                                 errno = EINVAL;
220                                 goto fail;
221                         }
222                         ret = openpam_load_chain(pamh, servicename, fclt);
223                         FREEV(wordc, wordv);
224                         if (ret < 0) {
225                                 /*
226                                  * Bogus errno, but this ensures that the
227                                  * outer loop does not just ignore the
228                                  * error and keep searching.
229                                  */
230                                 if (errno == ENOENT)
231                                         errno = EINVAL;
232                                 goto fail;
233                         }
234                         continue;
235                 }
236
237                 /* get control flag */
238                 if (word == NULL || /* same word we compared to "include" */
239                     (ctlf = parse_control_flag(word)) == (pam_control_t)-1) {
240                         openpam_log(PAM_LOG_ERROR,
241                             "%s(%d): missing or invalid control flag",
242                             filename, lineno);
243                         errno = EINVAL;
244                         goto fail;
245                 }
246
247                 /* get module name */
248                 if ((modulename = wordv[i++]) == NULL ||
249                     !valid_module_name(modulename)) {
250                         openpam_log(PAM_LOG_ERROR,
251                             "%s(%d): missing or invalid module name",
252                             filename, lineno);
253                         errno = EINVAL;
254                         goto fail;
255                 }
256
257                 /* allocate new entry */
258                 if ((this = calloc(1, sizeof *this)) == NULL)
259                         goto syserr;
260                 this->flag = ctlf;
261
262                 /* load module */
263                 if ((this->module = openpam_load_module(modulename)) == NULL) {
264                         if (errno == ENOENT)
265                                 errno = ENOEXEC;
266                         goto fail;
267                 }
268
269                 /*
270                  * The remaining items in wordv are the module's
271                  * arguments.  We could set this->optv = wordv + i, but
272                  * then free(this->optv) wouldn't work.  Instead, we free
273                  * the words we've already consumed, shift the rest up,
274                  * and clear the tail end of the array.
275                  */
276                 this->optc = wordc - i;
277                 for (i = 0; i < wordc - this->optc; ++i) {
278                         FREE(wordv[i]);
279                 }
280                 for (i = 0; i < this->optc; ++i) {
281                         wordv[i] = wordv[wordc - this->optc + i];
282                         wordv[wordc - this->optc + i] = NULL;
283                 }
284                 this->optv = wordv;
285                 wordv = NULL;
286                 wordc = 0;
287
288                 /* hook it up */
289                 for (next = &pamh->chains[fclt]; *next != NULL;
290                      next = &(*next)->next)
291                         /* nothing */ ;
292                 *next = this;
293                 this = NULL;
294                 ++count;
295         }
296         /*
297          * The loop ended because openpam_readword() returned NULL, which
298          * can happen for four different reasons: an I/O error (ferror(f)
299          * is true), a memory allocation failure (ferror(f) is false,
300          * feof(f) is false, errno is non-zero), the file ended with an
301          * unterminated quote or backslash escape (ferror(f) is false,
302          * feof(f) is true, errno is non-zero), or the end of the file was
303          * reached without error (ferror(f) is false, feof(f) is true,
304          * errno is zero).
305          */
306         if (ferror(f) || errno != 0)
307                 goto syserr;
308         if (!feof(f))
309                 goto fail;
310         fclose(f);
311         return (count);
312 syserr:
313         serrno = errno;
314         openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
315         errno = serrno;
316         /* fall through */
317 fail:
318         serrno = errno;
319         if (this && this->optc && this->optv)
320                 FREEV(this->optc, this->optv);
321         FREE(this);
322         FREEV(wordc, wordv);
323         FREE(wordv);
324         FREE(name);
325         fclose(f);
326         errno = serrno;
327         return (-1);
328 }
329
330 /*
331  * Read the specified chains from the specified file.
332  *
333  * Returns 0 if the file exists but does not contain any matching lines.
334  *
335  * Returns -1 and sets errno to ENOENT if the file does not exist.
336  *
337  * Returns -1 and sets errno to some other non-zero value if the file
338  * exists but is unsafe or unreadable, or an I/O error occurs.
339  */
340 static int
341 openpam_load_file(pam_handle_t *pamh,
342         const char *service,
343         pam_facility_t facility,
344         const char *filename,
345         openpam_style_t style)
346 {
347         FILE *f;
348         int ret, serrno;
349
350         /* attempt to open the file */
351         if ((f = fopen(filename, "r")) == NULL) {
352                 serrno = errno;
353                 openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR,
354                     "%s: %m", filename);
355                 errno = serrno;
356                 RETURNN(-1);
357         } else {
358                 openpam_log(PAM_LOG_DEBUG, "found %s", filename);
359         }
360
361         /* verify type, ownership and permissions */
362         if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) &&
363             openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
364                 /* already logged the cause */
365                 serrno = errno;
366                 fclose(f);
367                 errno = serrno;
368                 RETURNN(-1);
369         }
370
371         /* parse the file */
372         ret = openpam_parse_chain(pamh, service, facility,
373             f, filename, style);
374         RETURNN(ret);
375 }
376
377 /*
378  * Locates the policy file for a given service and reads the given chains
379  * from it.
380  *
381  * Returns the number of policy entries which were found for the specified
382  * service and facility, or -1 if a system error occurred or a syntax
383  * error was encountered.
384  */
385 static int
386 openpam_load_chain(pam_handle_t *pamh,
387         const char *service,
388         pam_facility_t facility)
389 {
390         const char *p, **path;
391         char filename[PATH_MAX];
392         size_t len;
393         openpam_style_t style;
394         int ret;
395
396         ENTERS(facility < 0 ? "any" : pam_facility_name[facility]);
397
398         /* either absolute or relative to cwd */
399         if (strchr(service, '/') != NULL) {
400                 if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0)
401                         style = pam_conf_style;
402                 else
403                         style = pam_d_style;
404                 ret = openpam_load_file(pamh, service, facility,
405                     service, style);
406                 RETURNN(ret);
407         }
408
409         /* search standard locations */
410         for (path = openpam_policy_path; *path != NULL; ++path) {
411                 /* construct filename */
412                 len = strlcpy(filename, *path, sizeof filename);
413                 if (filename[len - 1] == '/') {
414                         len = strlcat(filename, service, sizeof filename);
415                         if (len >= sizeof filename) {
416                                 errno = ENAMETOOLONG;
417                                 RETURNN(-1);
418                         }
419                         style = pam_d_style;
420                 } else {
421                         style = pam_conf_style;
422                 }
423                 ret = openpam_load_file(pamh, service, facility,
424                     filename, style);
425                 /* success */
426                 if (ret > 0)
427                         RETURNN(ret);
428                 /* the file exists, but an error occurred */
429                 if (ret == -1 && errno != ENOENT)
430                         RETURNN(ret);
431                 /* in pam.d style, an empty file counts as a hit */
432                 if (ret == 0 && style == pam_d_style)
433                         RETURNN(ret);
434         }
435
436         /* no hit */
437         errno = ENOENT;
438         RETURNN(-1);
439 }
440
441 /*
442  * OpenPAM internal
443  *
444  * Configure a service
445  */
446
447 int
448 openpam_configure(pam_handle_t *pamh,
449         const char *service)
450 {
451         pam_facility_t fclt;
452         int serrno;
453
454         ENTERS(service);
455         if (!valid_service_name(service)) {
456                 openpam_log(PAM_LOG_ERROR, "invalid service name");
457                 RETURNC(PAM_SYSTEM_ERR);
458         }
459         if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) {
460                 if (errno != ENOENT)
461                         goto load_err;
462         }
463         for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
464                 if (pamh->chains[fclt] != NULL)
465                         continue;
466                 if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
467                         goto load_err;
468         }
469         RETURNC(PAM_SUCCESS);
470 load_err:
471         serrno = errno;
472         openpam_clear_chains(pamh->chains);
473         errno = serrno;
474         RETURNC(PAM_SYSTEM_ERR);
475 }
476
477 /*
478  * NODOC
479  *
480  * Error codes:
481  *      PAM_SYSTEM_ERR
482  */