Merge branch 'vendor/OPENPAM'
[dragonfly.git] / contrib / openpam / lib / openpam_configure.c
1 /*-
2  * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
3  * Copyright (c) 2004-2011 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 500 2011-11-22 12:07:03Z des $
36  */
37
38 #ifdef HAVE_CONFIG_H
39 # include "config.h"
40 #endif
41
42 #include <ctype.h>
43 #include <errno.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47
48 #include <security/pam_appl.h>
49
50 #include "openpam_impl.h"
51 #include "openpam_strlcmp.h"
52
53 static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
54
55 /*
56  * Evaluates to non-zero if the argument is a linear whitespace character.
57  */
58 #define is_lws(ch)                              \
59         (ch == ' ' || ch == '\t')
60
61 /*
62  * Evaluates to non-zero if the argument is a printable ASCII character.
63  * Assumes that the execution character set is a superset of ASCII.
64  */
65 #define is_p(ch) \
66         (ch >= '!' && ch <= '~')
67
68 /*
69  * Returns non-zero if the argument belongs to the POSIX Portable Filename
70  * Character Set.  Assumes that the execution character set is a superset
71  * of ASCII.
72  */
73 #define is_pfcs(ch)                             \
74         ((ch >= '0' && ch <= '9') ||            \
75          (ch >= 'A' && ch <= 'Z') ||            \
76          (ch >= 'a' && ch <= 'z') ||            \
77          ch == '.' || ch == '_' || ch == '-')
78
79 /*
80  * Parse the service name.
81  *
82  * Returns the length of the service name, or 0 if the end of the string
83  * was reached or a disallowed non-whitespace character was encountered.
84  *
85  * If parse_service_name() is successful, it updates *service to point to
86  * the first character of the service name and *line to point one
87  * character past the end.  If it reaches the end of the string, it
88  * updates *line to point to the terminating NUL character and leaves
89  * *service unmodified.  In all other cases, it leaves both *line and
90  * *service unmodified.
91  *
92  * Allowed characters are all characters in the POSIX portable filename
93  * character set.
94  */
95 static int
96 parse_service_name(char **line, char **service)
97 {
98         char *b, *e;
99
100         for (b = *line; *b && is_lws(*b); ++b)
101                 /* nothing */ ;
102         if (!*b) {
103                 *line = b;
104                 return (0);
105         }
106         for (e = b; *e && !is_lws(*e); ++e)
107                 if (!is_pfcs(*e))
108                         return (0);
109         if (e == b)
110                 return (0);
111         *line = e;
112         *service = b;
113         return (e - b);
114 }
115
116 /*
117  * Parse the facility name.
118  *
119  * Returns the corresponding pam_facility_t value, or -1 if the end of the
120  * string was reached, a disallowed non-whitespace character was
121  * encountered, or the first word was not a recognized facility name.
122  *
123  * If parse_facility_name() is successful, it updates *line to point one
124  * character past the end of the facility name.  If it reaches the end of
125  * the string, it updates *line to point to the terminating NUL character.
126  * In all other cases, it leaves *line unmodified.
127  */
128 static pam_facility_t
129 parse_facility_name(char **line)
130 {
131         char *b, *e;
132         int i;
133
134         for (b = *line; *b && is_lws(*b); ++b)
135                 /* nothing */ ;
136         if (!*b) {
137                 *line = b;
138                 return ((pam_facility_t)-1);
139         }
140         for (e = b; *e && !is_lws(*e); ++e)
141                 /* nothing */ ;
142         if (e == b)
143                 return ((pam_facility_t)-1);
144         for (i = 0; i < PAM_NUM_FACILITIES; ++i)
145                 if (strlcmp(pam_facility_name[i], b, e - b) == 0)
146                         break;
147         if (i == PAM_NUM_FACILITIES)
148                 return ((pam_facility_t)-1);
149         *line = e;
150         return (i);
151 }
152
153 /*
154  * Parse the word "include".
155  *
156  * If the next word on the line is "include", parse_include() updates
157  * *line to point one character past "include" and returns 1.  Otherwise,
158  * it leaves *line unmodified and returns 0.
159  */
160 static int
161 parse_include(char **line)
162 {
163         char *b, *e;
164
165         for (b = *line; *b && is_lws(*b); ++b)
166                 /* nothing */ ;
167         if (!*b) {
168                 *line = b;
169                 return (-1);
170         }
171         for (e = b; *e && !is_lws(*e); ++e)
172                 /* nothing */ ;
173         if (e == b)
174                 return (0);
175         if (strlcmp("include", b, e - b) != 0)
176                 return (0);
177         *line = e;
178         return (1);
179 }
180
181 /*
182  * Parse the control flag.
183  *
184  * Returns the corresponding pam_control_t value, or -1 if the end of the
185  * string was reached, a disallowed non-whitespace character was
186  * encountered, or the first word was not a recognized control flag.
187  *
188  * If parse_control_flag() is successful, it updates *line to point one
189  * character past the end of the control flag.  If it reaches the end of
190  * the string, it updates *line to point to the terminating NUL character.
191  * In all other cases, it leaves *line unmodified.
192  */
193 static pam_control_t
194 parse_control_flag(char **line)
195 {
196         char *b, *e;
197         int i;
198
199         for (b = *line; *b && is_lws(*b); ++b)
200                 /* nothing */ ;
201         if (!*b) {
202                 *line = b;
203                 return ((pam_control_t)-1);
204         }
205         for (e = b; *e && !is_lws(*e); ++e)
206                 /* nothing */ ;
207         if (e == b)
208                 return ((pam_control_t)-1);
209         for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
210                 if (strlcmp(pam_control_flag_name[i], b, e - b) == 0)
211                         break;
212         if (i == PAM_NUM_CONTROL_FLAGS)
213                 return ((pam_control_t)-1);
214         *line = e;
215         return (i);
216 }
217
218 /*
219  * Parse a file name.
220  *
221  * Returns the length of the file name, or 0 if the end of the string was
222  * reached or a disallowed non-whitespace character was encountered.
223  *
224  * If parse_filename() is successful, it updates *filename to point to the
225  * first character of the filename and *line to point one character past
226  * the end.  If it reaches the end of the string, it updates *line to
227  * point to the terminating NUL character and leaves *filename unmodified.
228  * In all other cases, it leaves both *line and *filename unmodified.
229  *
230  * Allowed characters are all characters in the POSIX portable filename
231  * character set, plus the path separator (forward slash).
232  */
233 static int
234 parse_filename(char **line, char **filename)
235 {
236         char *b, *e;
237
238         for (b = *line; *b && is_lws(*b); ++b)
239                 /* nothing */ ;
240         if (!*b) {
241                 *line = b;
242                 return (0);
243         }
244         for (e = b; *e && !is_lws(*e); ++e)
245                 if (!is_pfcs(*e) && *e != '/')
246                         return (0);
247         if (e == b)
248                 return (0);
249         *line = e;
250         *filename = b;
251         return (e - b);
252 }
253
254 /*
255  * Parse an option.
256  *
257  * Returns a dynamically allocated string containing the next module
258  * option, or NULL if the end of the string was reached or a disallowed
259  * non-whitespace character was encountered.
260  *
261  * If parse_option() is successful, it updates *line to point one
262  * character past the end of the option.  If it reaches the end of the
263  * string, it updates *line to point to the terminating NUL character.  In
264  * all other cases, it leaves *line unmodified.
265  *
266  * If parse_option() fails to allocate memory, it will return NULL and set
267  * errno to a non-zero value.
268  *
269  * Allowed characters for option names are all characters in the POSIX
270  * portable filename character set.  Allowed characters for option values
271  * are any printable non-whitespace characters.  The option value may be
272  * quoted in either single or double quotes, in which case space
273  * characters and whichever quote character was not used are allowed.
274  * Note that the entire value must be quoted, not just part of it.
275  */
276 static char *
277 parse_option(char **line)
278 {
279         char *nb, *ne, *vb, *ve;
280         unsigned char q = 0;
281         char *option;
282         size_t size;
283
284         errno = 0;
285         for (nb = *line; *nb && is_lws(*nb); ++nb)
286                 /* nothing */ ;
287         if (!*nb) {
288                 *line = nb;
289                 return (NULL);
290         }
291         for (ne = nb; *ne && !is_lws(*ne) && *ne != '='; ++ne)
292                 if (!is_pfcs(*ne))
293                         return (NULL);
294         if (ne == nb)
295                 return (NULL);
296         if (*ne == '=') {
297                 vb = ne + 1;
298                 if (*vb == '"' || *vb == '\'')
299                         q = *vb++;
300                 for (ve = vb;
301                      *ve && *ve != q && (is_p(*ve) || (q && is_lws(*ve)));
302                      ++ve)
303                         /* nothing */ ;
304                 if (q && *ve != q)
305                         /* non-printable character or missing endquote */
306                         return (NULL);
307                 if (q && *(ve + 1) && !is_lws(*(ve + 1)))
308                         /* garbage after value */
309                         return (NULL);
310         } else {
311                 vb = ve = ne;
312         }
313         size = (ne - nb) + 1;
314         if (ve > vb)
315                 size += (ve - vb) + 1;
316         if ((option = malloc(size)) == NULL)
317                 return (NULL);
318         strncpy(option, nb, ne - nb);
319         if (ve > vb) {
320                 option[ne - nb] = '=';
321                 strncpy(option + (ne - nb) + 1, vb, ve - vb);
322         }
323         option[size - 1] = '\0';
324         *line = q ? ve + 1 : ve;
325         return (option);
326 }
327
328 /*
329  * Consume trailing whitespace.
330  *
331  * If there are no non-whitespace characters left on the line, parse_eol()
332  * updates *line to point at the terminating NUL character and returns 0.
333  * Otherwise, it leaves *line unmodified and returns a non-zero value.
334  */
335 static int
336 parse_eol(char **line)
337 {
338         char *p;
339
340         for (p = *line; *p && is_lws(*p); ++p)
341                 /* nothing */ ;
342         if (*p)
343                 return ((unsigned char)*p);
344         *line = p;
345         return (0);
346 }
347
348 typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
349
350 /*
351  * Extracts given chains from a policy file.
352  */
353 static int
354 openpam_parse_chain(pam_handle_t *pamh,
355         const char *service,
356         pam_facility_t facility,
357         const char *filename,
358         openpam_style_t style)
359 {
360         pam_chain_t *this, **next;
361         pam_facility_t fclt;
362         pam_control_t ctlf;
363         char *line, *str, *name;
364         char *option, **optv;
365         int len, lineno, ret;
366         FILE *f;
367
368         if ((f = fopen(filename, "r")) == NULL) {
369                 openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_NOTICE,
370                     "%s: %m", filename);
371                 return (PAM_SUCCESS);
372         }
373         if (openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
374                 fclose(f);
375                 return (PAM_SYSTEM_ERR);
376         }
377         this = NULL;
378         name = NULL;
379         lineno = 0;
380         while ((line = openpam_readline(f, &lineno, NULL)) != NULL) {
381                 /* get service name if necessary */
382                 if (style == pam_conf_style) {
383                         if ((len = parse_service_name(&line, &str)) == 0) {
384                                 openpam_log(PAM_LOG_NOTICE,
385                                     "%s(%d): invalid service name (ignored)",
386                                     filename, lineno);
387                                 FREE(line);
388                                 continue;
389                         }
390                         if (strlcmp(service, str, len) != 0) {
391                                 FREE(line);
392                                 continue;
393                         }
394                 }
395
396                 /* get facility name */
397                 if ((fclt = parse_facility_name(&line)) == (pam_facility_t)-1) {
398                         openpam_log(PAM_LOG_ERROR,
399                             "%s(%d): missing or invalid facility",
400                             filename, lineno);
401                         goto fail;
402                 }
403                 if (facility != fclt && facility != PAM_FACILITY_ANY) {
404                         FREE(line);
405                         continue;
406                 }
407
408                 /* check for "include" */
409                 if (parse_include(&line)) {
410                         if ((len = parse_service_name(&line, &str)) == 0) {
411                                 openpam_log(PAM_LOG_ERROR,
412                                     "%s(%d): missing or invalid filename",
413                                     filename, lineno);
414                                 goto fail;
415                         }
416                         if ((name = strndup(str, len)) == NULL)
417                                 goto syserr;
418                         if (parse_eol(&line) != 0) {
419                                 openpam_log(PAM_LOG_ERROR,
420                                     "%s(%d): garbage at end of line",
421                                     filename, lineno);
422                                 goto fail;
423                         }
424                         ret = openpam_load_chain(pamh, name, fclt);
425                         FREE(name);
426                         if (ret != PAM_SUCCESS)
427                                 goto fail;
428                         FREE(line);
429                         continue;
430                 }
431
432                 /* get control flag */
433                 if ((ctlf = parse_control_flag(&line)) == (pam_control_t)-1) {
434                         openpam_log(PAM_LOG_ERROR,
435                             "%s(%d): missing or invalid control flag",
436                             filename, lineno);
437                         goto fail;
438                 }
439
440                 /* get module name */
441                 if ((len = parse_filename(&line, &str)) == 0) {
442                         openpam_log(PAM_LOG_ERROR,
443                             "%s(%d): missing or invalid module name",
444                             filename, lineno);
445                         goto fail;
446                 }
447                 if ((name = strndup(str, len)) == NULL)
448                         goto syserr;
449
450                 /* allocate new entry */
451                 if ((this = calloc(1, sizeof *this)) == NULL)
452                         goto syserr;
453                 this->flag = ctlf;
454
455                 /* get module options */
456                 if ((this->optv = malloc(sizeof *optv)) == NULL)
457                         goto syserr;
458                 this->optc = 0;
459                 while ((option = parse_option(&line)) != NULL) {
460                         optv = realloc(this->optv,
461                             (this->optc + 2) * sizeof *optv);
462                         if (optv == NULL)
463                                 goto syserr;
464                         this->optv = optv;
465                         this->optv[this->optc++] = option;
466                 }
467                 this->optv[this->optc] = NULL;
468                 if (*line != '\0') {
469                         openpam_log(PAM_LOG_ERROR,
470                             "%s(%d): syntax error in module options",
471                             filename, lineno);
472                         goto fail;
473                 }
474
475                 /* load module */
476                 this->module = openpam_load_module(name);
477                 FREE(name);
478                 if (this->module == NULL)
479                         goto fail;
480
481                 /* hook it up */
482                 for (next = &pamh->chains[fclt]; *next != NULL;
483                      next = &(*next)->next)
484                         /* nothing */ ;
485                 *next = this;
486                 this = NULL;
487
488                 /* next please... */
489                 FREE(line);
490         }
491         if (!feof(f))
492                 goto syserr;
493         fclose(f);
494         return (PAM_SUCCESS);
495 syserr:
496         openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
497 fail:
498         if (this && this->optc) {
499                 while (this->optc--)
500                         FREE(this->optv[this->optc]);
501                 FREE(this->optv);
502         }
503         FREE(this);
504         FREE(line);
505         FREE(name);
506         fclose(f);
507         return (PAM_SYSTEM_ERR);
508 }
509
510 static const char *openpam_policy_path[] = {
511         "/etc/pam.d/",
512         "/etc/pam.conf",
513         "/usr/local/etc/pam.d/",
514         "/usr/local/etc/pam.conf",
515         NULL
516 };
517
518 /*
519  * Locates the policy file for a given service and reads the given chains
520  * from it.
521  */
522 static int
523 openpam_load_chain(pam_handle_t *pamh,
524         const char *service,
525         pam_facility_t facility)
526 {
527         const char **path;
528         char *filename;
529         size_t len;
530         int ret;
531
532         for (path = openpam_policy_path; *path != NULL; ++path) {
533                 len = strlen(*path);
534                 if ((*path)[len - 1] == '/') {
535                         if (asprintf(&filename, "%s%s", *path, service) < 0) {
536                                 openpam_log(PAM_LOG_ERROR, "asprintf(): %m");
537                                 return (PAM_BUF_ERR);
538                         }
539                         ret = openpam_parse_chain(pamh, service, facility,
540                             filename, pam_d_style);
541                         FREE(filename);
542                 } else {
543                         ret = openpam_parse_chain(pamh, service, facility,
544                             *path, pam_conf_style);
545                 }
546                 if (ret != PAM_SUCCESS)
547                         return (ret);
548         }
549         return (PAM_SUCCESS);
550 }
551
552 /*
553  * OpenPAM internal
554  *
555  * Configure a service
556  */
557
558 int
559 openpam_configure(pam_handle_t *pamh,
560         const char *service)
561 {
562         pam_facility_t fclt;
563         const char *p;
564
565         for (p = service; *p; ++p)
566                 if (!is_pfcs(*p))
567                         return (PAM_SYSTEM_ERR);
568
569         if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) != PAM_SUCCESS)
570                 goto load_err;
571
572         for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
573                 if (pamh->chains[fclt] != NULL)
574                         continue;
575                 if (openpam_load_chain(pamh, PAM_OTHER, fclt) != PAM_SUCCESS)
576                         goto load_err;
577         }
578         return (PAM_SUCCESS);
579 load_err:
580         openpam_clear_chains(pamh->chains);
581         return (PAM_SYSTEM_ERR);
582 }
583
584 /*
585  * NODOC
586  *
587  * Error codes:
588  *      PAM_SYSTEM_ERR
589  */