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