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