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 | { |
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 | |
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 | |
3616b334 |
200 | static int pamauth(struct authhandle *auth, struct socket *sk, char *passdata) |
d3372da9 |
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 | }; |