Patch from "Alan Curry" <pacman-radius@cqc.com>
[freeradius.git] / src / modules / rlm_pam / rlm_pam.c
1 /*
2  * pam.c        Functions to access the PAM library. This was taken
3  *              from the hacks that miguel a.l. paraz <map@iphil.net>
4  *              did on radiusd-cistron-1.5.3 and migrated to a
5  *              separate file.
6  *
7  *              That, in fact, was again based on the original stuff
8  *              from Jeph Blaize <jblaize@kiva.net> done in May 1997.
9  *
10  * Version:     $Id$
11  *
12  */
13
14 #include        "autoconf.h"
15
16 #include        <sys/types.h>
17 #include        <sys/socket.h>
18 #include        <sys/time.h>
19 #include        <netinet/in.h>
20
21 #include        <stdio.h>
22 #include        <string.h>
23 #include        <pwd.h>
24 #include        <time.h>
25 #include        <ctype.h>
26
27 #include        <security/pam_appl.h>
28
29 #if HAVE_MALLOC_H
30 #  include      <malloc.h>
31 #endif
32
33 #include        "radiusd.h"
34 #include        "modules.h"
35
36 /*************************************************************************
37  *
38  *      Function: PAM_conv
39  *
40  *      Purpose: Dialogue between RADIUS and PAM modules.
41  *
42  * jab - stolen from pop3d
43  *
44  * Alan DeKok: modified to use PAM's appdata_ptr, so that we're
45  *             multi-threaded safe, and don't have any nasty static
46  *             variables hanging around.
47  *
48  *************************************************************************/
49
50 typedef struct my_PAM {
51   const char *username;
52   const char *password;
53   int         error;
54 } my_PAM;
55
56 static int PAM_conv (int num_msg,
57                      const struct pam_message **msg,
58                      struct pam_response **resp,
59                      void *appdata_ptr) {
60   int count = 0, replies = 0;
61   struct pam_response *reply = NULL;
62   int size = sizeof(struct pam_response);
63   my_PAM *pam_config = (my_PAM *) appdata_ptr;
64   
65 #define GET_MEM if (reply) realloc(reply, size); else reply = malloc(size); \
66   if (!reply) return PAM_CONV_ERR; \
67   size += sizeof(struct pam_response)
68 #define COPY_STRING(s) ((s) ? strdup(s) : NULL)
69                                      
70   for (count = 0; count < num_msg; count++) {
71     switch (msg[count]->msg_style) {
72     case PAM_PROMPT_ECHO_ON:
73       GET_MEM;
74       reply[replies].resp_retcode = PAM_SUCCESS;
75       reply[replies++].resp = COPY_STRING(pam_config->username);
76       /* PAM frees resp */
77       break;
78     case PAM_PROMPT_ECHO_OFF:
79       GET_MEM;
80       reply[replies].resp_retcode = PAM_SUCCESS;
81       reply[replies++].resp = COPY_STRING(pam_config->password);
82       /* PAM frees resp */
83       break;
84     case PAM_TEXT_INFO:
85       /* ignore it... */
86       break;
87     case PAM_ERROR_MSG:
88     default:
89       /* Must be an error of some sort... */
90       free (reply);
91       pam_config->error = 1;
92       return PAM_CONV_ERR;
93     }
94   }
95   if (reply) *resp = reply;
96
97   return PAM_SUCCESS;
98 }
99
100 /*************************************************************************
101  *
102  *      Function: pam_pass
103  *
104  *      Purpose: Check the users password against the standard UNIX
105  *               password table + PAM.
106  *
107  * jab start 19970529
108  *************************************************************************/
109
110 /* cjd 19980706
111  * 
112  * for most flexibility, passing a pamauth type to this function
113  * allows you to have multiple authentication types (i.e. multiple
114  * files associated with radius in /etc/pam.d)
115  */
116 static int pam_pass(const char *name, const char *passwd, const char *pamauth)
117 {
118     pam_handle_t *pamh=NULL;
119     int retval;
120     my_PAM pam_config;
121     struct pam_conv conv;
122
123     /*
124      *  Initialize the structures.
125      */
126     conv.conv = PAM_conv;
127     conv.appdata_ptr = &pam_config;
128     pam_config.username = name;
129     pam_config.password = passwd;
130     pam_config.error = 0;
131
132     DEBUG("pam_pass: using pamauth string <%s> for pam.conf lookup", pamauth);
133     retval = pam_start(pamauth, name, &conv, &pamh);
134     if (retval != PAM_SUCCESS) {
135       DEBUG("pam_pass: function pam_start FAILED for <%s>. Reason: %s",
136             name, pam_strerror(pamh, retval));
137       return -1;
138     }
139
140     retval = pam_authenticate(pamh, 0);
141     if (retval != PAM_SUCCESS) {
142       DEBUG("pam_pass: function pam_authenticate FAILED for <%s>. Reason: %s",
143             name, pam_strerror(pamh, retval));
144       pam_end(pamh, 0);
145       return -1;
146     }
147
148     /*
149      * FreeBSD 3.x doesn't have account and session management
150      * functions in PAM, while 4.0 does.
151      */
152 #if !defined(__FreeBSD_version) || (__FreeBSD_version >= 400000)
153     retval = pam_acct_mgmt(pamh, 0);
154     if (retval != PAM_SUCCESS) {
155       DEBUG("pam_pass: function pam_acct_mgmt FAILED for <%s>. Reason: %s",
156             name, pam_strerror(pamh, retval));
157       pam_end(pamh, 0);
158       return -1;
159     }
160 #endif
161
162     DEBUG("pam_pass: authentication succeeded for <%s>", name);
163     pam_end(pamh, 0);
164     return 0;
165 }
166
167 /* translate between function declarations */
168 static int pam_auth(void *instance, REQUEST *request)
169 {
170         int     r;
171         VALUE_PAIR *pair;
172         const char *pam_auth_string = "radiusd";
173
174         instance = instance;
175
176         /*
177          *      We can only authenticate user requests which HAVE
178          *      a User-Name attribute.
179          */
180         if (!request->username) {
181                 radlog(L_AUTH, "rlm_pam: Attribute \"User-Name\" is required for authentication.");
182                 return RLM_MODULE_INVALID;
183         }
184
185         /*
186          *      We can only authenticate user requests which HAVE
187          *      a Password attribute.
188          */
189         if (!request->password) {
190                 radlog(L_AUTH, "rlm_pam: Attribute \"Password\" is required for authentication.");
191                 return RLM_MODULE_INVALID;
192         }
193
194         /*
195          *  Ensure that we're being passed a plain-text password,
196          *  and not anything else.
197          */
198         if (request->password->attribute != PW_PASSWORD) {
199                 radlog(L_AUTH, "rlm_pam: Attribute \"Password\" is required for authentication.  Cannot use \"%s\".", request->password->name);
200                 return RLM_MODULE_INVALID;
201         }
202
203         pair = pairfind(request->config_items, PAM_AUTH_ATTR);
204         if (pair) pam_auth_string = (char *)pair->strvalue;
205
206         r = pam_pass((char *)request->username->strvalue,
207                      (char *)request->password->strvalue,
208                      pam_auth_string);
209         if (r == 0) {
210                 return RLM_MODULE_OK;
211         }
212         return RLM_MODULE_REJECT;
213 }
214
215 module_t rlm_pam = {
216   "Pam",
217   0,                            /* type: reserved */
218   NULL,                         /* initialize */
219   NULL,                         /* instantiation */
220   NULL,                         /* authorize */
221   pam_auth,                     /* authenticate */
222   NULL,                         /* pre-accounting */
223   NULL,                         /* accounting */
224   NULL,                         /* detach */
225   NULL,                         /* destroy */
226 };
227