ChangeLog update.
[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 <errno.h>
38
39 #ifdef HAVE_CONFIG_H
40 #include <config.h>
41 #endif
42 #include "auth.h"
43 #include "utils.h"
44 #include "conf.h"
45 #include "log.h"
46 #include "module.h"
47
48 #ifdef HAVE_PAM
49 #include <ucontext.h>
50 #include <security/pam_appl.h>
51
52 struct pamdata
53 {
54     pam_handle_t *pamh;
55     volatile int pamret;
56     ucontext_t mainctxt, pamctxt;
57     void *pamstack;
58     volatile int validctxt;
59     volatile int convdone, converr;
60     volatile char *passdata;
61 };
62
63 static int pamconv(int nmsg, const struct pam_message **msg, struct pam_response **resp, struct authhandle *auth)
64 {
65     int i;
66     struct pamdata *data;
67     
68     data = auth->mechdata;
69     *resp = smalloc(sizeof(**resp) * nmsg);
70     for(i = 0; i < nmsg; i++)
71     {
72         switch(msg[i]->msg_style)
73         {
74         case PAM_PROMPT_ECHO_OFF:
75             auth->prompt = AUTH_PR_NOECHO;
76             break;
77         case PAM_PROMPT_ECHO_ON:
78             auth->prompt = AUTH_PR_ECHO;
79             break;
80         case PAM_ERROR_MSG:
81             auth->prompt = AUTH_PR_ERROR;
82             break;
83         case PAM_TEXT_INFO:
84             auth->prompt = AUTH_PR_INFO;
85             break;
86         }
87         if(auth->text != NULL)
88             free(auth->text);
89         if((auth->text = icmbstowcs((char *)msg[i]->msg, NULL)) == NULL)
90         {
91             flog(LOG_ERR, "could not convert PAM error %s into wcs: %s", msg[i]->msg, strerror(errno));
92             free(*resp);
93             *resp = NULL;
94             return(PAM_CONV_ERR);
95         }
96         if(swapcontext(&data->pamctxt, &data->mainctxt))
97         {
98             flog(LOG_CRIT, "could not swap context in PAM conversation: %s", strerror(errno));
99             free(*resp);
100             *resp = NULL;
101             return(PAM_CONV_ERR);
102         }
103         if(data->converr)
104         {
105             for(i--; i >= 0; i--)
106                 free((*resp)[i].resp);
107             free(*resp);
108             *resp = NULL;
109             return(PAM_CONV_ERR);
110         }
111         (*resp)[i].resp_retcode = PAM_SUCCESS;
112         switch(msg[i]->msg_style)
113         {
114         case PAM_PROMPT_ECHO_OFF:
115         case PAM_PROMPT_ECHO_ON:
116             (*resp)[i].resp = sstrdup((char *)data->passdata);
117             memset((void *)data->passdata, 0, strlen((char *)data->passdata));
118             break;
119         default:
120             (*resp)[i].resp = NULL;
121             break;
122         }
123     }
124     return(PAM_SUCCESS);
125 }
126
127 static void releasepam(struct pamdata *data)
128 {
129     if(data->pamh != NULL)
130     {
131         if(data->validctxt)
132         {
133             data->converr = 1;
134             if(swapcontext(&data->mainctxt, &data->pamctxt))
135             {
136                 flog(LOG_CRIT, "could not switch back to PAM context while releasing: %s", strerror(errno));
137                 return;
138             }
139         }
140         pam_end(data->pamh, data->pamret);
141     }
142     if(data->pamstack != NULL)
143         free(data->pamstack);
144     free(data);
145 }
146
147 static void release(struct authhandle *auth)
148 {
149     releasepam((struct pamdata *)auth->mechdata);
150 }
151
152 static struct pamdata *newpamdata(void)
153 {
154     struct pamdata *new;
155
156     new = smalloc(sizeof(*new));
157     new->pamh = NULL;
158     new->pamret = PAM_SUCCESS;
159     new->pamstack = NULL;
160     new->validctxt = 0;
161     new->converr = 0;
162     return(new);
163 }
164
165 static int inithandle(struct authhandle *auth, char *username)
166 {
167     char *buf;
168     struct pamdata *data;
169     struct pam_conv conv;
170     
171     data = newpamdata();
172     conv.conv = (int (*)(int, const struct pam_message **, struct pam_response **, void *))pamconv;
173     conv.appdata_ptr = auth;
174     if((buf = icwcstombs(confgetstr("auth-pam", "pamserv"), NULL)) == NULL)
175     {
176         flog(LOG_ERR, "could not initialize pam since auth-pam.pamserv cannot be translated into the current locale: %s", strerror(errno));
177         releasepam(data);
178         return(1);
179     }
180     if((data->pamret = pam_start(buf, username, &conv, &data->pamh)) != PAM_SUCCESS)
181     {
182         flog(LOG_CRIT, "could not pam_start: %s", pam_strerror(NULL, data->pamret));
183         releasepam(data);
184         free(buf);
185         errno = ENOTSUP; /* XXX */
186         return(1);
187     }
188     free(buf);
189     auth->mechdata = data;
190     return(0);
191 }
192
193 static void pamauththread(struct authhandle *auth)
194 {
195     struct pamdata *data;
196     
197     data = (struct pamdata *)auth->mechdata;
198     data->validctxt = 1;
199     data->pamret = pam_authenticate(data->pamh, 0);
200     data->validctxt = 0;
201 }
202
203 static int pamauth(struct authhandle *auth, struct socket *sk, char *passdata)
204 {
205     struct pamdata *data;
206     
207     data = auth->mechdata;
208     if(!data->validctxt)
209     {
210         if(getcontext(&data->pamctxt))
211         {
212             flog(LOG_CRIT, "could not get context: %s", strerror(errno));
213             return(AUTH_ERR);
214         }
215         data->pamctxt.uc_link = &data->mainctxt;
216         if(data->pamstack == NULL)
217             data->pamstack = smalloc(65536);
218         data->pamctxt.uc_stack.ss_sp = data->pamstack;
219         data->pamctxt.uc_stack.ss_size = 65536;
220         makecontext(&data->pamctxt, (void (*)(void))pamauththread, 1, auth);
221         if(swapcontext(&data->mainctxt, &data->pamctxt))
222         {
223             flog(LOG_CRIT, "Could not switch to PAM context: %s", strerror(errno));
224             return(AUTH_ERR);
225         }
226         if(!data->validctxt)
227         {
228             if(data->pamret == PAM_AUTHINFO_UNAVAIL)
229                 return(AUTH_ERR);
230             else if(data->pamret == PAM_SUCCESS)
231                 return(AUTH_SUCCESS);
232             else
233                 return(AUTH_DENIED);
234         }
235         return(AUTH_PASS);
236     } else {
237         data->passdata = passdata;
238         if(swapcontext(&data->mainctxt, &data->pamctxt))
239         {
240             flog(LOG_CRIT, "could not switch back to PAM context: %s", strerror(errno));
241             return(AUTH_ERR);
242         }
243         if(!data->validctxt)
244         {
245             if(data->pamret == PAM_AUTHINFO_UNAVAIL)
246                 return(AUTH_ERR);
247             else if(data->pamret == PAM_SUCCESS)
248                 return(AUTH_SUCCESS);
249             else
250                 return(AUTH_DENIED);
251         }
252         return(AUTH_PASS);
253     }
254 }
255
256 static int renewcred(struct authhandle *auth)
257 {
258     struct pamdata *data;
259     
260     data = auth->mechdata;
261     if(data->pamh == NULL)
262         return(AUTH_SUCCESS);
263     data->pamret = pam_setcred(data->pamh, PAM_REFRESH_CRED);
264     if(data->pamret != PAM_SUCCESS)
265     {
266         flog(LOG_INFO, "could not refresh credentials: %s", pam_strerror(data->pamh, data->pamret));
267         return(AUTH_ERR);
268     }
269     return(AUTH_SUCCESS);
270 }
271
272 static int opensess(struct authhandle *auth)
273 {
274     struct pamdata *data;
275     char **envp;
276     
277     data = auth->mechdata;
278     if(data->pamh == NULL)
279     {
280         flog(LOG_ERR, "bug: in auth-pam.c:opensess: called with NULL pamh");
281         return(AUTH_ERR);
282     }
283     data->pamret = pam_setcred(data->pamh, PAM_ESTABLISH_CRED);
284     if(data->pamret != PAM_SUCCESS)
285     {
286         flog(LOG_INFO, "could not establish credentials: %s", pam_strerror(data->pamh, data->pamret));
287         return(AUTH_ERR);
288     }
289     data->pamret = pam_open_session(data->pamh, 0);
290     if(data->pamret != PAM_SUCCESS)
291     {
292         flog(LOG_INFO, "could not open session: %s", pam_strerror(data->pamh, data->pamret));
293         return(AUTH_ERR);
294     }
295     for(envp = pam_getenvlist(data->pamh); *envp; envp++)
296         putenv(*envp);
297     return(AUTH_SUCCESS);
298 }
299
300 static int closesess(struct authhandle *auth)
301 {
302     int rc;
303     struct pamdata *data;
304     
305     data = auth->mechdata;
306     if(data->pamh == NULL)
307     {
308         flog(LOG_ERR, "bug: in auth-pam.c:closesess: called with NULL pamh");
309         return(AUTH_ERR);
310     }
311     rc = AUTH_SUCCESS;
312     data->pamret = pam_close_session(data->pamh, 0);
313     if(data->pamret != PAM_SUCCESS)
314     {
315         flog(LOG_INFO, "could not open session: %s", pam_strerror(data->pamh, data->pamret));
316         rc = AUTH_ERR;
317     }
318     data->pamret = pam_setcred(data->pamh, PAM_DELETE_CRED);
319     if(data->pamret != PAM_SUCCESS)
320     {
321         flog(LOG_INFO, "could not establish credentials: %s", pam_strerror(data->pamh, data->pamret));
322         rc = AUTH_ERR;
323     }
324     return(rc);
325 }
326
327 static struct authmech authmech_pam =
328 {
329     .inithandle = inithandle,
330     .release = release,
331     .authenticate = pamauth,
332     .renewcred = renewcred,
333     .opensess = opensess,
334     .closesess = closesess,
335     .name = L"pam",
336     .enabled = 1
337 };
338
339 static int init(int hup)
340 {
341     if(!hup)
342         regmech(&authmech_pam);
343     return(0);
344 }
345
346 static struct configvar myvars[] =
347 {
348     /** The name of the PAM service file to use. */
349     {CONF_VAR_STRING, "pamserv", {.str = L"doldacond"}},
350     {CONF_VAR_END}
351 };
352
353 static struct module me =
354 {
355     .conf =
356     {
357         .vars = myvars
358     },
359     .init = init,
360     .name = "auth-pam"
361 };
362
363 MODULE(me);
364
365 #endif /* HAVE_PAM */