New dc_connect implementation
[doldaconnect.git] / daemon / auth-pam.c
CommitLineData
d3372da9 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
49struct 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
60static 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 {
ff19b3d8 102 for(i--; i >= 0; i--)
103 free((*resp)[i].resp);
104 free(*resp);
105 *resp = NULL;
d3372da9 106 return(PAM_CONV_ERR);
107 }
ff19b3d8 108 (*resp)[i].resp_retcode = PAM_SUCCESS;
d3372da9 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));
ff19b3d8 115 break;
116 default:
117 (*resp)[i].resp = NULL;
d3372da9 118 break;
119 }
120 }
121 return(PAM_SUCCESS);
122}
123
124static 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
144static void release(struct authhandle *auth)
145{
146 releasepam((struct pamdata *)auth->mechdata);
147}
148
149static 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
162static 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
190static 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
200static 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
253static 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
269static 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
297static 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
324struct 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};