Commit | Line | Data |
---|---|---|
d3372da9 | 1 | /* |
2 | * Dolda Connect - Modular multiuser Direct Connect-style client | |
302a2600 | 3 | * Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com> |
d3372da9 | 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> | |
d3372da9 | 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" | |
ded209d3 FT |
46 | #include "module.h" |
47 | ||
48 | #ifdef HAVE_PAM | |
49 | #include <ucontext.h> | |
50 | #include <security/pam_appl.h> | |
d3372da9 | 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 | { | |
ff19b3d8 | 105 | for(i--; i >= 0; i--) |
106 | free((*resp)[i].resp); | |
107 | free(*resp); | |
108 | *resp = NULL; | |
d3372da9 | 109 | return(PAM_CONV_ERR); |
110 | } | |
ff19b3d8 | 111 | (*resp)[i].resp_retcode = PAM_SUCCESS; |
d3372da9 | 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)); | |
ff19b3d8 | 118 | break; |
119 | default: | |
120 | (*resp)[i].resp = NULL; | |
d3372da9 | 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; | |
142d5fc6 | 174 | if((buf = icwcstombs(confgetstr("auth-pam", "pamserv"), NULL)) == NULL) |
d3372da9 | 175 | { |
142d5fc6 | 176 | flog(LOG_ERR, "could not initialize pam since auth-pam.pamserv cannot be translated into the current locale: %s", strerror(errno)); |
d3372da9 | 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 | ||
3616b334 | 203 | static int pamauth(struct authhandle *auth, struct socket *sk, char *passdata) |
d3372da9 | 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 | ||
ded209d3 | 327 | static struct authmech authmech_pam = |
d3372da9 | 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 | }; | |
ded209d3 FT |
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 */ |