Import OpenPAM Hydrangea.
[dragonfly.git] / contrib / openpam / lib / openpam_configure.c
1 /*-
2  * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
3  * Copyright (c) 2004-2007 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 408 2007-12-21 11:36:24Z des $
36  */
37
38 #include <ctype.h>
39 #include <errno.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43
44 #include <security/pam_appl.h>
45
46 #include "openpam_impl.h"
47
48 const char *_pam_facility_name[PAM_NUM_FACILITIES] = {
49         [PAM_ACCOUNT]           = "account",
50         [PAM_AUTH]              = "auth",
51         [PAM_PASSWORD]          = "password",
52         [PAM_SESSION]           = "session",
53 };
54
55 const char *_pam_control_flag_name[PAM_NUM_CONTROL_FLAGS] = {
56         [PAM_BINDING]           = "binding",
57         [PAM_OPTIONAL]          = "optional",
58         [PAM_REQUIRED]          = "required",
59         [PAM_REQUISITE]         = "requisite",
60         [PAM_SUFFICIENT]        = "sufficient",
61 };
62
63 static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
64
65 /*
66  * Matches a word against the first one in a string.
67  * Returns non-zero if they match.
68  */
69 static int
70 match_word(const char *str, const char *word)
71 {
72
73         while (*str && tolower(*str) == tolower(*word))
74                 ++str, ++word;
75         return (*str == ' ' && *word == '\0');
76 }
77
78 /*
79  * Return a pointer to the next word (or the final NUL) in a string.
80  */
81 static const char *
82 next_word(const char *str)
83 {
84
85         /* skip current word */
86         while (*str && *str != ' ')
87                 ++str;
88         /* skip whitespace */
89         while (*str == ' ')
90                 ++str;
91         return (str);
92 }
93
94 /*
95  * Return a malloc()ed copy of the first word in a string.
96  */
97 static char *
98 dup_word(const char *str)
99 {
100         const char *end;
101         char *word;
102
103         for (end = str; *end && *end != ' '; ++end)
104                 /* nothing */ ;
105         if (asprintf(&word, "%.*s", (int)(end - str), str) < 0)
106                 return (NULL);
107         return (word);
108 }
109
110 /*
111  * Return the length of the first word in a string.
112  */
113 static int
114 wordlen(const char *str)
115 {
116         int i;
117
118         for (i = 0; str[i] && str[i] != ' '; ++i)
119                 /* nothing */ ;
120         return (i);
121 }
122
123 typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
124
125 /*
126  * Extracts given chains from a policy file.
127  */
128 static int
129 openpam_read_chain(pam_handle_t *pamh,
130         const char *service,
131         pam_facility_t facility,
132         const char *filename,
133         openpam_style_t style)
134 {
135         pam_chain_t *this, **next;
136         const char *p, *q;
137         int count, i, lineno, ret;
138         pam_facility_t fclt;
139         pam_control_t ctlf;
140         char *line, *name;
141         FILE *f;
142
143         if ((f = fopen(filename, "r")) == NULL) {
144                 openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_NOTICE,
145                     "%s: %m", filename);
146                 return (0);
147         }
148         this = NULL;
149         count = lineno = 0;
150         while ((line = openpam_readline(f, &lineno, NULL)) != NULL) {
151                 p = line;
152
153                 /* match service name */
154                 if (style == pam_conf_style) {
155                         if (!match_word(p, service)) {
156                                 FREE(line);
157                                 continue;
158                         }
159                         p = next_word(p);
160                 }
161
162                 /* match facility name */
163                 for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt)
164                         if (match_word(p, _pam_facility_name[fclt]))
165                                 break;
166                 if (fclt == PAM_NUM_FACILITIES) {
167                         openpam_log(PAM_LOG_NOTICE,
168                             "%s(%d): invalid facility '%.*s' (ignored)",
169                             filename, lineno, wordlen(p), p);
170                         goto fail;
171                 }
172                 if (facility != fclt && facility != PAM_FACILITY_ANY) {
173                         FREE(line);
174                         continue;
175                 }
176                 p = next_word(p);
177
178                 /* include other chain */
179                 if (match_word(p, "include")) {
180                         p = next_word(p);
181                         if (*next_word(p) != '\0')
182                                 openpam_log(PAM_LOG_NOTICE,
183                                     "%s(%d): garbage at end of 'include' line",
184                                     filename, lineno);
185                         if ((name = dup_word(p)) == NULL)
186                                 goto syserr;
187                         ret = openpam_load_chain(pamh, name, fclt);
188                         FREE(name);
189                         if (ret < 0)
190                                 goto fail;
191                         count += ret;
192                         FREE(line);
193                         continue;
194                 }
195
196                 /* allocate new entry */
197                 if ((this = calloc(1, sizeof *this)) == NULL)
198                         goto syserr;
199
200                 /* control flag */
201                 for (ctlf = 0; ctlf < PAM_NUM_CONTROL_FLAGS; ++ctlf)
202                         if (match_word(p, _pam_control_flag_name[ctlf]))
203                                 break;
204                 if (ctlf == PAM_NUM_CONTROL_FLAGS) {
205                         openpam_log(PAM_LOG_ERROR,
206                             "%s(%d): invalid control flag '%.*s'",
207                             filename, lineno, wordlen(p), p);
208                         goto fail;
209                 }
210                 this->flag = ctlf;
211
212                 /* module name */
213                 p = next_word(p);
214                 if (*p == '\0') {
215                         openpam_log(PAM_LOG_ERROR,
216                             "%s(%d): missing module name",
217                             filename, lineno);
218                         goto fail;
219                 }
220                 if ((name = dup_word(p)) == NULL)
221                         goto syserr;
222                 this->module = openpam_load_module(name);
223                 FREE(name);
224                 if (this->module == NULL)
225                         goto fail;
226
227                 /* module options */
228                 p = q = next_word(p);
229                 while (*q != '\0') {
230                         ++this->optc;
231                         q = next_word(q);
232                 }
233                 this->optv = calloc(this->optc + 1, sizeof(char *));
234                 if (this->optv == NULL)
235                         goto syserr;
236                 for (i = 0; i < this->optc; ++i) {
237                         if ((this->optv[i] = dup_word(p)) == NULL)
238                                 goto syserr;
239                         p = next_word(p);
240                 }
241
242                 /* hook it up */
243                 for (next = &pamh->chains[fclt]; *next != NULL;
244                      next = &(*next)->next)
245                         /* nothing */ ;
246                 *next = this;
247                 this = NULL;
248                 ++count;
249
250                 /* next please... */
251                 FREE(line);
252         }
253         if (!feof(f))
254                 goto syserr;
255         fclose(f);
256         return (count);
257  syserr:
258         openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
259  fail:
260         FREE(this);
261         FREE(line);
262         fclose(f);
263         return (-1);
264 }
265
266 static const char *openpam_policy_path[] = {
267         "/etc/pam.d/",
268         "/etc/pam.conf",
269         "/usr/local/etc/pam.d/",
270         "/usr/local/etc/pam.conf",
271         NULL
272 };
273
274 /*
275  * Locates the policy file for a given service and reads the given chains
276  * from it.
277  */
278 static int
279 openpam_load_chain(pam_handle_t *pamh,
280         const char *service,
281         pam_facility_t facility)
282 {
283         const char **path;
284         char *filename;
285         size_t len;
286         int r;
287
288         for (path = openpam_policy_path; *path != NULL; ++path) {
289                 len = strlen(*path);
290                 if ((*path)[len - 1] == '/') {
291                         if (asprintf(&filename, "%s%s", *path, service) < 0) {
292                                 openpam_log(PAM_LOG_ERROR, "asprintf(): %m");
293                                 return (-PAM_BUF_ERR);
294                         }
295                         r = openpam_read_chain(pamh, service, facility,
296                             filename, pam_d_style);
297                         FREE(filename);
298                 } else {
299                         r = openpam_read_chain(pamh, service, facility,
300                             *path, pam_conf_style);
301                 }
302                 if (r != 0)
303                         return (r);
304         }
305         return (0);
306 }
307
308 /*
309  * OpenPAM internal
310  *
311  * Configure a service
312  */
313
314 int
315 openpam_configure(pam_handle_t *pamh,
316         const char *service)
317 {
318         pam_facility_t fclt;
319
320         if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0)
321                 goto load_err;
322
323         for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
324                 if (pamh->chains[fclt] != NULL)
325                         continue;
326                 if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
327                         goto load_err;
328         }
329         return (PAM_SUCCESS);
330  load_err:
331         openpam_clear_chains(pamh->chains);
332         return (PAM_SYSTEM_ERR);
333 }
334
335 /*
336  * NODOC
337  *
338  * Error codes:
339  *      PAM_SYSTEM_ERR
340  */