8ca6cc6f7875e2a2fdd3d25cb40d471e66d1be31
[freeradius.git] / src / modules / rlm_pam / rlm_pam.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  * @file rlm_pam.c
20  * @brief Interfaces with the PAM library to allow auth via PAM.
21  *
22  * @note This was taken from the hacks that miguel a.l. paraz <map@iphil.net>
23  *      did on radiusd-cistron-1.5.3 and migrated to a separate file.
24  *      That, in fact, was again based on the original stuff from
25  *      Jeph Blaize <jblaize@kiva.net> done in May 1997.
26  *
27  * @copyright 2000,2006  The FreeRADIUS server project
28  * @copyright 1997  Jeph Blaize <jblaize@kiva.net>
29  * @copyright 1999  miguel a.l. paraz <map@iphil.net>
30  */
31 RCSID("$Id$")
32
33 #include <freeradius-devel/radiusd.h>
34 #include <freeradius-devel/modules.h>
35
36 #include "config.h"
37
38 #ifdef HAVE_SECURITY_PAM_APPL_H
39 #  include <security/pam_appl.h>
40 #endif
41
42 #ifdef HAVE_PAM_PAM_APPL_H
43 #  include <pam/pam_appl.h>
44 #endif
45
46 #ifdef HAVE_SYSLOG_H
47 #  include <syslog.h>
48 #endif
49
50 typedef struct rlm_pam_t {
51         char const *pam_auth_name;
52 } rlm_pam_t;
53
54 static const CONF_PARSER module_config[] = {
55         { "pam_auth", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_pam_t, pam_auth_name), "radiusd" },
56         { NULL, -1, 0, NULL, NULL }
57 };
58
59 typedef struct rlm_pam_data_t {
60         REQUEST         *request;       //!< The current request.
61         char const      *username;      //!< Username to provide to PAM when prompted.
62         char const      *password;      //!< Password to provide to PAM when prompted.
63         bool            error;          //!< True if pam_conv failed.
64 } rlm_pam_data_t;
65
66 /** Dialogue between RADIUS and PAM modules
67  *
68  * Uses PAM's appdata_ptr so it's thread safe, and doesn't
69  * have any nasty static variables hanging around.
70  */
71 static int pam_conv(int num_msg, struct pam_message const **msg, struct pam_response **resp, void *appdata_ptr)
72 {
73         int count;
74         struct pam_response *reply;
75         REQUEST *request;
76         rlm_pam_data_t *pam_config = (rlm_pam_data_t *) appdata_ptr;
77
78         request = pam_config->request;
79
80 /* strdup(NULL) doesn't work on some platforms */
81 #define COPY_STRING(s) ((s) ? strdup(s) : NULL)
82
83         reply = rad_malloc(num_msg * sizeof(struct pam_response));
84         memset(reply, 0, num_msg * sizeof(struct pam_response));
85         for (count = 0; count < num_msg; count++) {
86                 switch (msg[count]->msg_style) {
87                 case PAM_PROMPT_ECHO_ON:
88                         reply[count].resp_retcode = PAM_SUCCESS;
89                         reply[count].resp = COPY_STRING(pam_config->username);
90                         break;
91
92                 case PAM_PROMPT_ECHO_OFF:
93                         reply[count].resp_retcode = PAM_SUCCESS;
94                         reply[count].resp = COPY_STRING(pam_config->password);
95                         break;
96
97                 case PAM_TEXT_INFO:
98                         RDEBUG2("%s", msg[count]->msg);
99                         break;
100
101                 case PAM_ERROR_MSG:
102                 default:
103                         RERROR("PAM conversation failed");
104                         /* Must be an error of some sort... */
105                         for (count = 0; count < num_msg; count++) {
106                                 if (msg[count]->msg_style == PAM_ERROR_MSG) RERROR("%s", msg[count]->msg);
107                                 if (reply[count].resp) {
108                                         /* could be a password, let's be sanitary */
109                                         memset(reply[count].resp, 0, strlen(reply[count].resp));
110                                         free(reply[count].resp);
111                                 }
112                         }
113                         free(reply);
114                         pam_config->error = true;
115                         return PAM_CONV_ERR;
116                 }
117         }
118         *resp = reply;
119         /* PAM frees reply (including reply[].resp) */
120
121         return PAM_SUCCESS;
122 }
123
124 /** Check the users password against the standard UNIX password table + PAM.
125  *
126  * @note For most flexibility, passing a pamauth type to this function
127  *       allows you to have multiple authentication types (i.e. multiple
128  *       files associated with radius in /etc/pam.d).
129  *
130  * @param request The current request.
131  * @param username User to authenticate.
132  * @param passwd Password to authenticate with,
133  * @param pamauth Type of PAM authentication.
134  * @return 0 on success -1 on failure.
135  */
136 static int do_pam(REQUEST *request, char const *username, char const *passwd, char const *pamauth)
137 {
138         pam_handle_t *handle = NULL;
139         int ret;
140         rlm_pam_data_t pam_config;
141         struct pam_conv conv;
142
143         /*
144          *  Initialize the structures
145          */
146         conv.conv = pam_conv;
147         conv.appdata_ptr = &pam_config;
148         pam_config.request = request;
149         pam_config.username = username;
150         pam_config.password = passwd;
151         pam_config.error = false;
152
153         RDEBUG2("Using pamauth string \"%s\" for pam.conf lookup", pamauth);
154
155         ret = pam_start(pamauth, username, &conv, &handle);
156         if (ret != PAM_SUCCESS) {
157                 RERROR("pam_start failed: %s", pam_strerror(handle, ret));
158                 return -1;
159         }
160
161         ret = pam_authenticate(handle, 0);
162         if (ret != PAM_SUCCESS) {
163                 RERROR("pam_authenticate failed: %s", pam_strerror(handle, ret));
164                 pam_end(handle, ret);
165                 return -1;
166         }
167
168         /*
169          *      FreeBSD 3.x doesn't have account and session management
170          *      functions in PAM, while 4.0 does.
171          */
172 #if !defined(__FreeBSD_version) || (__FreeBSD_version >= 400000)
173         ret = pam_acct_mgmt(handle, 0);
174         if (ret != PAM_SUCCESS) {
175                 RERROR("pam_acct_mgmt failed: %s", pam_strerror(handle, ret));
176                 pam_end(handle, ret);
177                 return -1;
178         }
179 #endif
180         RDEBUG2("Authentication succeeded");
181         pam_end(handle, ret);
182         return 0;
183 }
184
185 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
186 {
187         int ret;
188         VALUE_PAIR *pair;
189         rlm_pam_t *data = (rlm_pam_t *) instance;
190
191         char const *pam_auth_string = data->pam_auth_name;
192
193         /*
194          *      We can only authenticate user requests which HAVE
195          *      a User-Name attribute.
196          */
197         if (!request->username) {
198                 RAUTH("Attribute \"User-Name\" is required for authentication");
199                 return RLM_MODULE_INVALID;
200         }
201
202         /*
203          *      We can only authenticate user requests which HAVE
204          *      a User-Password attribute.
205          */
206         if (!request->password) {
207                 RAUTH("Attribute \"User-Password\" is required for authentication");
208                 return RLM_MODULE_INVALID;
209         }
210
211         /*
212          *  Ensure that we're being passed a plain-text password,
213          *  and not anything else.
214          */
215         if (request->password->da->attr != PW_USER_PASSWORD) {
216                 RAUTH("Attribute \"User-Password\" is required for authentication.  Cannot use \"%s\".", request->password->da->name);
217                 return RLM_MODULE_INVALID;
218         }
219
220         /*
221          *      Let the 'users' file over-ride the PAM auth name string,
222          *      for backwards compatibility.
223          */
224         pair = fr_pair_find_by_num(request->config, PW_PAM_AUTH, 0, TAG_ANY);
225         if (pair) pam_auth_string = pair->vp_strvalue;
226
227         ret = do_pam(request, request->username->vp_strvalue, request->password->vp_strvalue, pam_auth_string);
228         if (ret < 0) return RLM_MODULE_REJECT;
229
230         return RLM_MODULE_OK;
231 }
232
233 extern module_t rlm_pam;
234 module_t rlm_pam = {
235         .magic          = RLM_MODULE_INIT,
236         .name           = "pam",
237         .type           = RLM_TYPE_THREAD_UNSAFE,       /* The PAM libraries are not thread-safe */
238         .inst_size      = sizeof(rlm_pam_t),
239         .config         = module_config,
240         .methods = {
241                 [MOD_AUTHENTICATE]      = mod_authenticate
242         },
243 };
244