Ported the Guile library for usage of 64-bit numbers.
[doldaconnect.git] / daemon / auth-pam.c
... / ...
CommitLineData
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
52struct 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
63static 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
127static 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
147static void release(struct authhandle *auth)
148{
149 releasepam((struct pamdata *)auth->mechdata);
150}
151
152static 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
165static 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
193static 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
203static 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
256static 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
272static 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
300static 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
327static 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
339static int init(int hup)
340{
341 if(!hup)
342 regmech(&authmech_pam);
343 return(0);
344}
345
346static 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
353static struct module me =
354{
355 .conf =
356 {
357 .vars = myvars
358 },
359 .init = init,
360 .name = "auth-pam"
361};
362
363MODULE(me);
364
365#endif /* HAVE_PAM */