New dc_connect implementation
[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--; i >= 0; i--)
103                 free((*resp)[i].resp);
104             free(*resp);
105             *resp = NULL;
106             return(PAM_CONV_ERR);
107         }
108         (*resp)[i].resp_retcode = PAM_SUCCESS;
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             break;
116         default:
117             (*resp)[i].resp = NULL;
118             break;
119         }
120     }
121     return(PAM_SUCCESS);
122 }
123
124 static void releasepam(struct pamdata *data)
125 {
126     if(data->pamh != NULL)
127     {
128         if(data->validctxt)
129         {
130             data->converr = 1;
131             if(swapcontext(&data->mainctxt, &data->pamctxt))
132             {
133                 flog(LOG_CRIT, "could not switch back to PAM context while releasing: %s", strerror(errno));
134                 return;
135             }
136         }
137         pam_end(data->pamh, data->pamret);
138     }
139     if(data->pamstack != NULL)
140         free(data->pamstack);
141     free(data);
142 }
143
144 static void release(struct authhandle *auth)
145 {
146     releasepam((struct pamdata *)auth->mechdata);
147 }
148
149 static struct pamdata *newpamdata(void)
150 {
151     struct pamdata *new;
152
153     new = smalloc(sizeof(*new));
154     new->pamh = NULL;
155     new->pamret = PAM_SUCCESS;
156     new->pamstack = NULL;
157     new->validctxt = 0;
158     new->converr = 0;
159     return(new);
160 }
161
162 static int inithandle(struct authhandle *auth, char *username)
163 {
164     char *buf;
165     struct pamdata *data;
166     struct pam_conv conv;
167     
168     data = newpamdata();
169     conv.conv = (int (*)(int, const struct pam_message **, struct pam_response **, void *))pamconv;
170     conv.appdata_ptr = auth;
171     if((buf = icwcstombs(confgetstr("auth", "pamserv"), NULL)) == NULL)
172     {
173         flog(LOG_ERR, "could not initialize pam since auth.pamserv cannot be translated into the current locale: %s", strerror(errno));
174         releasepam(data);
175         return(1);
176     }
177     if((data->pamret = pam_start(buf, username, &conv, &data->pamh)) != PAM_SUCCESS)
178     {
179         flog(LOG_CRIT, "could not pam_start: %s", pam_strerror(NULL, data->pamret));
180         releasepam(data);
181         free(buf);
182         errno = ENOTSUP; /* XXX */
183         return(1);
184     }
185     free(buf);
186     auth->mechdata = data;
187     return(0);
188 }
189
190 static void pamauththread(struct authhandle *auth)
191 {
192     struct pamdata *data;
193     
194     data = (struct pamdata *)auth->mechdata;
195     data->validctxt = 1;
196     data->pamret = pam_authenticate(data->pamh, 0);
197     data->validctxt = 0;
198 }
199
200 static int pamauth(struct authhandle *auth, char *passdata)
201 {
202     struct pamdata *data;
203     
204     data = auth->mechdata;
205     if(!data->validctxt)
206     {
207         if(getcontext(&data->pamctxt))
208         {
209             flog(LOG_CRIT, "could not get context: %s", strerror(errno));
210             return(AUTH_ERR);
211         }
212         data->pamctxt.uc_link = &data->mainctxt;
213         if(data->pamstack == NULL)
214             data->pamstack = smalloc(65536);
215         data->pamctxt.uc_stack.ss_sp = data->pamstack;
216         data->pamctxt.uc_stack.ss_size = 65536;
217         makecontext(&data->pamctxt, (void (*)(void))pamauththread, 1, auth);
218         if(swapcontext(&data->mainctxt, &data->pamctxt))
219         {
220             flog(LOG_CRIT, "Could not switch to PAM context: %s", strerror(errno));
221             return(AUTH_ERR);
222         }
223         if(!data->validctxt)
224         {
225             if(data->pamret == PAM_AUTHINFO_UNAVAIL)
226                 return(AUTH_ERR);
227             else if(data->pamret == PAM_SUCCESS)
228                 return(AUTH_SUCCESS);
229             else
230                 return(AUTH_DENIED);
231         }
232         return(AUTH_PASS);
233     } else {
234         data->passdata = passdata;
235         if(swapcontext(&data->mainctxt, &data->pamctxt))
236         {
237             flog(LOG_CRIT, "could not switch back to PAM context: %s", strerror(errno));
238             return(AUTH_ERR);
239         }
240         if(!data->validctxt)
241         {
242             if(data->pamret == PAM_AUTHINFO_UNAVAIL)
243                 return(AUTH_ERR);
244             else if(data->pamret == PAM_SUCCESS)
245                 return(AUTH_SUCCESS);
246             else
247                 return(AUTH_DENIED);
248         }
249         return(AUTH_PASS);
250     }
251 }
252
253 static int renewcred(struct authhandle *auth)
254 {
255     struct pamdata *data;
256     
257     data = auth->mechdata;
258     if(data->pamh == NULL)
259         return(AUTH_SUCCESS);
260     data->pamret = pam_setcred(data->pamh, PAM_REFRESH_CRED);
261     if(data->pamret != PAM_SUCCESS)
262     {
263         flog(LOG_INFO, "could not refresh credentials: %s", pam_strerror(data->pamh, data->pamret));
264         return(AUTH_ERR);
265     }
266     return(AUTH_SUCCESS);
267 }
268
269 static int opensess(struct authhandle *auth)
270 {
271     struct pamdata *data;
272     char **envp;
273     
274     data = auth->mechdata;
275     if(data->pamh == NULL)
276     {
277         flog(LOG_ERR, "bug: in auth-pam.c:opensess: called with NULL pamh");
278         return(AUTH_ERR);
279     }
280     data->pamret = pam_setcred(data->pamh, PAM_ESTABLISH_CRED);
281     if(data->pamret != PAM_SUCCESS)
282     {
283         flog(LOG_INFO, "could not establish credentials: %s", pam_strerror(data->pamh, data->pamret));
284         return(AUTH_ERR);
285     }
286     data->pamret = pam_open_session(data->pamh, 0);
287     if(data->pamret != PAM_SUCCESS)
288     {
289         flog(LOG_INFO, "could not open session: %s", pam_strerror(data->pamh, data->pamret));
290         return(AUTH_ERR);
291     }
292     for(envp = pam_getenvlist(data->pamh); *envp; envp++)
293         putenv(*envp);
294     return(AUTH_SUCCESS);
295 }
296
297 static int closesess(struct authhandle *auth)
298 {
299     int rc;
300     struct pamdata *data;
301     
302     data = auth->mechdata;
303     if(data->pamh == NULL)
304     {
305         flog(LOG_ERR, "bug: in auth-pam.c:closesess: called with NULL pamh");
306         return(AUTH_ERR);
307     }
308     rc = AUTH_SUCCESS;
309     data->pamret = pam_close_session(data->pamh, 0);
310     if(data->pamret != PAM_SUCCESS)
311     {
312         flog(LOG_INFO, "could not open session: %s", pam_strerror(data->pamh, data->pamret));
313         rc = AUTH_ERR;
314     }
315     data->pamret = pam_setcred(data->pamh, PAM_DELETE_CRED);
316     if(data->pamret != PAM_SUCCESS)
317     {
318         flog(LOG_INFO, "could not establish credentials: %s", pam_strerror(data->pamh, data->pamret));
319         rc = AUTH_ERR;
320     }
321     return(rc);
322 }
323
324 struct authmech authmech_pam =
325 {
326     .inithandle = inithandle,
327     .release = release,
328     .authenticate = pamauth,
329     .renewcred = renewcred,
330     .opensess = opensess,
331     .closesess = closesess,
332     .name = L"pam",
333     .enabled = 1
334 };