Transfer from CVS at SourceForge
[doldaconnect.git] / daemon / auth-pam.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
4  *  
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *  
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *  
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19 /*
20  * I have decided that I don't like PAM. Maybe I'm inexperienced, so
21  * please correct me if I'm wrong, but is it not so that
22  * pam_authenticate blocks until the user has fully authenticated
23  * herself? That isn't very good in a program that wants to do other
24  * things at the same time. In my mind, pam_authenticate should return
25  * with a conversation struct every time it wants data.
26  *
27  * My solution here, for now, is to use the ucontext context switching
28  * functions to get back and forth from the conversation
29  * function. Ugly? Yes indeed, it most certainly is, but what am I to
30  * do, then? If there actually is a good way to do this that is built
31  * into PAM, _please_, do mail me about it.
32  */
33
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <string.h>
37 #include <ucontext.h>
38 #include <security/pam_appl.h>
39 #include <errno.h>
40
41 #ifdef HAVE_CONFIG_H
42 #include <config.h>
43 #endif
44 #include "auth.h"
45 #include "utils.h"
46 #include "conf.h"
47 #include "log.h"
48
49 struct pamdata
50 {
51     pam_handle_t *pamh;
52     volatile int pamret;
53     ucontext_t mainctxt, pamctxt;
54     void *pamstack;
55     volatile int validctxt;
56     volatile int convdone, converr;
57     volatile char *passdata;
58 };
59
60 static int pamconv(int nmsg, const struct pam_message **msg, struct pam_response **resp, struct authhandle *auth)
61 {
62     int i;
63     struct pamdata *data;
64     
65     data = auth->mechdata;
66     *resp = smalloc(sizeof(**resp) * nmsg);
67     for(i = 0; i < nmsg; i++)
68     {
69         switch(msg[i]->msg_style)
70         {
71         case PAM_PROMPT_ECHO_OFF:
72             auth->prompt = AUTH_PR_NOECHO;
73             break;
74         case PAM_PROMPT_ECHO_ON:
75             auth->prompt = AUTH_PR_ECHO;
76             break;
77         case PAM_ERROR_MSG:
78             auth->prompt = AUTH_PR_ERROR;
79             break;
80         case PAM_TEXT_INFO:
81             auth->prompt = AUTH_PR_INFO;
82             break;
83         }
84         if(auth->text != NULL)
85             free(auth->text);
86         if((auth->text = icmbstowcs((char *)msg[i]->msg, NULL)) == NULL)
87         {
88             flog(LOG_ERR, "could not convert PAM error %s into wcs: %s", msg[i]->msg, strerror(errno));
89             free(*resp);
90             *resp = NULL;
91             return(PAM_CONV_ERR);
92         }
93         if(swapcontext(&data->pamctxt, &data->mainctxt))
94         {
95             flog(LOG_CRIT, "could not swap context in PAM conversation: %s", strerror(errno));
96             free(*resp);
97             *resp = NULL;
98             return(PAM_CONV_ERR);
99         }
100         if(data->converr)
101         {
102             for(; i < nmsg; i++)
103             {
104                 (*resp)[i].resp = sstrdup("");
105                 (*resp)[i].resp_retcode = PAM_SUCCESS;
106             }
107             return(PAM_CONV_ERR);
108         }
109         switch(msg[i]->msg_style)
110         {
111         case PAM_PROMPT_ECHO_OFF:
112         case PAM_PROMPT_ECHO_ON:
113             (*resp)[i].resp = sstrdup((char *)data->passdata);
114             memset((void *)data->passdata, 0, strlen((char *)data->passdata));
115             (*resp)[i].resp_retcode = PAM_SUCCESS;
116             break;
117         }
118     }
119     return(PAM_SUCCESS);
120 }
121
122 static void releasepam(struct pamdata *data)
123 {
124     if(data->pamh != NULL)
125     {
126         if(data->validctxt)
127         {
128             data->converr = 1;
129             if(swapcontext(&data->mainctxt, &data->pamctxt))
130             {
131                 flog(LOG_CRIT, "could not switch back to PAM context while releasing: %s", strerror(errno));
132                 return;
133             }
134         }
135         pam_end(data->pamh, data->pamret);
136     }
137     if(data->pamstack != NULL)
138         free(data->pamstack);
139     free(data);
140 }
141
142 static void release(struct authhandle *auth)
143 {
144     releasepam((struct pamdata *)auth->mechdata);
145 }
146
147 static struct pamdata *newpamdata(void)
148 {
149     struct pamdata *new;
150
151     new = smalloc(sizeof(*new));
152     new->pamh = NULL;
153     new->pamret = PAM_SUCCESS;
154     new->pamstack = NULL;
155     new->validctxt = 0;
156     new->converr = 0;
157     return(new);
158 }
159
160 static int inithandle(struct authhandle *auth, char *username)
161 {
162     char *buf;
163     struct pamdata *data;
164     struct pam_conv conv;
165     
166     data = newpamdata();
167     conv.conv = (int (*)(int, const struct pam_message **, struct pam_response **, void *))pamconv;
168     conv.appdata_ptr = auth;
169     if((buf = icwcstombs(confgetstr("auth", "pamserv"), NULL)) == NULL)
170     {
171         flog(LOG_ERR, "could not initialize pam since auth.pamserv cannot be translated into the current locale: %s", strerror(errno));
172         releasepam(data);
173         return(1);
174     }
175     if((data->pamret = pam_start(buf, username, &conv, &data->pamh)) != PAM_SUCCESS)
176     {
177         flog(LOG_CRIT, "could not pam_start: %s", pam_strerror(NULL, data->pamret));
178         releasepam(data);
179         free(buf);
180         errno = ENOTSUP; /* XXX */
181         return(1);
182     }
183     free(buf);
184     auth->mechdata = data;
185     return(0);
186 }
187
188 static void pamauththread(struct authhandle *auth)
189 {
190     struct pamdata *data;
191     
192     data = (struct pamdata *)auth->mechdata;
193     data->validctxt = 1;
194     data->pamret = pam_authenticate(data->pamh, 0);
195     data->validctxt = 0;
196 }
197
198 static int pamauth(struct authhandle *auth, char *passdata)
199 {
200     struct pamdata *data;
201     
202     data = auth->mechdata;
203     if(!data->validctxt)
204     {
205         if(getcontext(&data->pamctxt))
206         {
207             flog(LOG_CRIT, "could not get context: %s", strerror(errno));
208             return(AUTH_ERR);
209         }
210         data->pamctxt.uc_link = &data->mainctxt;
211         if(data->pamstack == NULL)
212             data->pamstack = smalloc(65536);
213         data->pamctxt.uc_stack.ss_sp = data->pamstack;
214         data->pamctxt.uc_stack.ss_size = 65536;
215         makecontext(&data->pamctxt, (void (*)(void))pamauththread, 1, auth);
216         if(swapcontext(&data->mainctxt, &data->pamctxt))
217         {
218             flog(LOG_CRIT, "Could not switch to PAM context: %s", strerror(errno));
219             return(AUTH_ERR);
220         }
221         if(!data->validctxt)
222         {
223             if(data->pamret == PAM_AUTHINFO_UNAVAIL)
224                 return(AUTH_ERR);
225             else if(data->pamret == PAM_SUCCESS)
226                 return(AUTH_SUCCESS);
227             else
228                 return(AUTH_DENIED);
229         }
230         return(AUTH_PASS);
231     } else {
232         data->passdata = passdata;
233         if(swapcontext(&data->mainctxt, &data->pamctxt))
234         {
235             flog(LOG_CRIT, "could not switch back to PAM context: %s", strerror(errno));
236             return(AUTH_ERR);
237         }
238         if(!data->validctxt)
239         {
240             if(data->pamret == PAM_AUTHINFO_UNAVAIL)
241                 return(AUTH_ERR);
242             else if(data->pamret == PAM_SUCCESS)
243                 return(AUTH_SUCCESS);
244             else
245                 return(AUTH_DENIED);
246         }
247         return(AUTH_PASS);
248     }
249 }
250
251 static int renewcred(struct authhandle *auth)
252 {
253     struct pamdata *data;
254     
255     data = auth->mechdata;
256     if(data->pamh == NULL)
257         return(AUTH_SUCCESS);
258     data->pamret = pam_setcred(data->pamh, PAM_REFRESH_CRED);
259     if(data->pamret != PAM_SUCCESS)
260     {
261         flog(LOG_INFO, "could not refresh credentials: %s", pam_strerror(data->pamh, data->pamret));
262         return(AUTH_ERR);
263     }
264     return(AUTH_SUCCESS);
265 }
266
267 static int opensess(struct authhandle *auth)
268 {
269     struct pamdata *data;
270     char **envp;
271     
272     data = auth->mechdata;
273     if(data->pamh == NULL)
274     {
275         flog(LOG_ERR, "bug: in auth-pam.c:opensess: called with NULL pamh");
276         return(AUTH_ERR);
277     }
278     data->pamret = pam_setcred(data->pamh, PAM_ESTABLISH_CRED);
279     if(data->pamret != PAM_SUCCESS)
280     {
281         flog(LOG_INFO, "could not establish credentials: %s", pam_strerror(data->pamh, data->pamret));
282         return(AUTH_ERR);
283     }
284     data->pamret = pam_open_session(data->pamh, 0);
285     if(data->pamret != PAM_SUCCESS)
286     {
287         flog(LOG_INFO, "could not open session: %s", pam_strerror(data->pamh, data->pamret));
288         return(AUTH_ERR);
289     }
290     for(envp = pam_getenvlist(data->pamh); *envp; envp++)
291         putenv(*envp);
292     return(AUTH_SUCCESS);
293 }
294
295 static int closesess(struct authhandle *auth)
296 {
297     int rc;
298     struct pamdata *data;
299     
300     data = auth->mechdata;
301     if(data->pamh == NULL)
302     {
303         flog(LOG_ERR, "bug: in auth-pam.c:closesess: called with NULL pamh");
304         return(AUTH_ERR);
305     }
306     rc = AUTH_SUCCESS;
307     data->pamret = pam_close_session(data->pamh, 0);
308     if(data->pamret != PAM_SUCCESS)
309     {
310         flog(LOG_INFO, "could not open session: %s", pam_strerror(data->pamh, data->pamret));
311         rc = AUTH_ERR;
312     }
313     data->pamret = pam_setcred(data->pamh, PAM_DELETE_CRED);
314     if(data->pamret != PAM_SUCCESS)
315     {
316         flog(LOG_INFO, "could not establish credentials: %s", pam_strerror(data->pamh, data->pamret));
317         rc = AUTH_ERR;
318     }
319     return(rc);
320 }
321
322 struct authmech authmech_pam =
323 {
324     .inithandle = inithandle,
325     .release = release,
326     .authenticate = pamauth,
327     .renewcred = renewcred,
328     .opensess = opensess,
329     .closesess = closesess,
330     .name = L"pam",
331     .enabled = 1
332 };