Deleted Added
full compact
auth-pam.c (92879) auth-pam.c (98941)
1/*
2 * Copyright (c) 2000 Damien Miller. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.

--- 11 unchanged lines hidden (view full) ---

20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "includes.h"
26
27#ifdef USE_PAM
1/*
2 * Copyright (c) 2000 Damien Miller. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.

--- 11 unchanged lines hidden (view full) ---

20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "includes.h"
26
27#ifdef USE_PAM
28#include <security/pam_appl.h>
29#include "ssh.h"
30#include "xmalloc.h"
31#include "log.h"
28#include "ssh.h"
29#include "xmalloc.h"
30#include "log.h"
31#include "auth.h"
32#include "auth-pam.h"
32#include "servconf.h"
33#include "servconf.h"
33#include "readpass.h"
34#include "canohost.h"
34#include "canohost.h"
35#include "readpass.h"
35
36
36RCSID("$FreeBSD: head/crypto/openssh/auth-pam.c 92879 2002-03-21 12:55:21Z des $");
37extern char *__progname;
37
38
39RCSID("$Id: auth-pam.c,v 1.46 2002/05/08 02:27:56 djm Exp $");
40
38#define NEW_AUTHTOK_MSG \
39 "Warning: Your password has expired, please change it now"
40
41#define NEW_AUTHTOK_MSG \
42 "Warning: Your password has expired, please change it now"
43
41#define SSHD_PAM_SERVICE "sshd"
42#define PAM_STRERROR(a, b) pam_strerror((a), (b))
43
44/* Callbacks */
45static int do_pam_conversation(int num_msg, const struct pam_message **msg,
44static int do_pam_conversation(int num_msg, const struct pam_message **msg,
46 struct pam_response **resp, void *appdata_ptr);
47void do_pam_cleanup_proc(void *context);
48void pam_msg_cat(const char *msg);
45 struct pam_response **resp, void *appdata_ptr);
49
50/* module-local variables */
51static struct pam_conv conv = {
52 do_pam_conversation,
53 NULL
54};
46
47/* module-local variables */
48static struct pam_conv conv = {
49 do_pam_conversation,
50 NULL
51};
55static pam_handle_t *pamh = NULL;
56static const char *pampasswd = NULL;
57static char *pam_msg = NULL;
58extern ServerOptions options;
52static char *__pam_msg = NULL;
53static pam_handle_t *__pamh = NULL;
54static const char *__pampasswd = NULL;
59
60/* states for do_pam_conversation() */
55
56/* states for do_pam_conversation() */
61typedef enum { INITIAL_LOGIN, OTHER } pamstates;
62static pamstates pamstate = INITIAL_LOGIN;
57enum { INITIAL_LOGIN, OTHER } pamstate = INITIAL_LOGIN;
63/* remember whether pam_acct_mgmt() returned PAM_NEWAUTHTOK_REQD */
64static int password_change_required = 0;
65/* remember whether the last pam_authenticate() succeeded or not */
66static int was_authenticated = 0;
67
68/* Remember what has been initialised */
69static int session_opened = 0;
70static int creds_set = 0;
71
58/* remember whether pam_acct_mgmt() returned PAM_NEWAUTHTOK_REQD */
59static int password_change_required = 0;
60/* remember whether the last pam_authenticate() succeeded or not */
61static int was_authenticated = 0;
62
63/* Remember what has been initialised */
64static int session_opened = 0;
65static int creds_set = 0;
66
72/*
73 * accessor which allows us to switch conversation structs according to
74 * the authentication method being used
75 */
67/* accessor which allows us to switch conversation structs according to
68 * the authentication method being used */
76void do_pam_set_conv(struct pam_conv *conv)
77{
69void do_pam_set_conv(struct pam_conv *conv)
70{
78 pam_set_item(pamh, PAM_CONV, conv);
71 pam_set_item(__pamh, PAM_CONV, conv);
79}
80
72}
73
74/* start an authentication run */
75int do_pam_authenticate(int flags)
76{
77 int retval = pam_authenticate(__pamh, flags);
78 was_authenticated = (retval == PAM_SUCCESS);
79 return retval;
80}
81
81/*
82 * PAM conversation function.
83 * There are two states this can run in.
84 *
85 * INITIAL_LOGIN mode simply feeds the password from the client into
86 * PAM in response to PAM_PROMPT_ECHO_OFF, and collects output
82/*
83 * PAM conversation function.
84 * There are two states this can run in.
85 *
86 * INITIAL_LOGIN mode simply feeds the password from the client into
87 * PAM in response to PAM_PROMPT_ECHO_OFF, and collects output
87 * messages with pam_msg_cat(). This is used during initial
88 * messages with into __pam_msg. This is used during initial
88 * authentication to bypass the normal PAM password prompt.
89 *
89 * authentication to bypass the normal PAM password prompt.
90 *
90 * OTHER mode handles PAM_PROMPT_ECHO_OFF with read_passphrase(prompt, 1)
91 * OTHER mode handles PAM_PROMPT_ECHO_OFF with read_passphrase()
91 * and outputs messages to stderr. This mode is used if pam_chauthtok()
92 * is called to update expired passwords.
93 */
94static int do_pam_conversation(int num_msg, const struct pam_message **msg,
95 struct pam_response **resp, void *appdata_ptr)
96{
97 struct pam_response *reply;
98 int count;
99 char buf[1024];
100
101 /* PAM will free this later */
102 reply = malloc(num_msg * sizeof(*reply));
103 if (reply == NULL)
92 * and outputs messages to stderr. This mode is used if pam_chauthtok()
93 * is called to update expired passwords.
94 */
95static int do_pam_conversation(int num_msg, const struct pam_message **msg,
96 struct pam_response **resp, void *appdata_ptr)
97{
98 struct pam_response *reply;
99 int count;
100 char buf[1024];
101
102 /* PAM will free this later */
103 reply = malloc(num_msg * sizeof(*reply));
104 if (reply == NULL)
104 return PAM_CONV_ERR;
105 return PAM_CONV_ERR;
105
106 for (count = 0; count < num_msg; count++) {
106
107 for (count = 0; count < num_msg; count++) {
107 switch ((*msg)[count].msg_style) {
108 if (pamstate == INITIAL_LOGIN) {
109 /*
110 * We can't use stdio yet, queue messages for
111 * printing later
112 */
113 switch(PAM_MSG_MEMBER(msg, count, msg_style)) {
108 case PAM_PROMPT_ECHO_ON:
114 case PAM_PROMPT_ECHO_ON:
109 if (pamstate == INITIAL_LOGIN) {
115 free(reply);
116 return PAM_CONV_ERR;
117 case PAM_PROMPT_ECHO_OFF:
118 if (__pampasswd == NULL) {
110 free(reply);
111 return PAM_CONV_ERR;
119 free(reply);
120 return PAM_CONV_ERR;
112 } else {
113 fputs((*msg)[count].msg, stderr);
114 fgets(buf, sizeof(buf), stdin);
115 reply[count].resp = xstrdup(buf);
116 reply[count].resp_retcode = PAM_SUCCESS;
117 break;
118 }
121 }
119 case PAM_PROMPT_ECHO_OFF:
120 if (pamstate == INITIAL_LOGIN) {
121 if (pampasswd == NULL) {
122 free(reply);
123 return PAM_CONV_ERR;
124 }
125 reply[count].resp = xstrdup(pampasswd);
126 } else {
127 reply[count].resp =
128 xstrdup(read_passphrase((*msg)[count].msg, 1));
129 }
122 reply[count].resp = xstrdup(__pampasswd);
130 reply[count].resp_retcode = PAM_SUCCESS;
131 break;
132 case PAM_ERROR_MSG:
133 case PAM_TEXT_INFO:
134 if ((*msg)[count].msg != NULL) {
123 reply[count].resp_retcode = PAM_SUCCESS;
124 break;
125 case PAM_ERROR_MSG:
126 case PAM_TEXT_INFO:
127 if ((*msg)[count].msg != NULL) {
135 if (pamstate == INITIAL_LOGIN)
136 pam_msg_cat((*msg)[count].msg);
137 else {
138 fputs((*msg)[count].msg, stderr);
139 fputs("\n", stderr);
140 }
128 message_cat(&__pam_msg,
129 PAM_MSG_MEMBER(msg, count, msg));
141 }
142 reply[count].resp = xstrdup("");
143 reply[count].resp_retcode = PAM_SUCCESS;
144 break;
145 default:
146 free(reply);
147 return PAM_CONV_ERR;
130 }
131 reply[count].resp = xstrdup("");
132 reply[count].resp_retcode = PAM_SUCCESS;
133 break;
134 default:
135 free(reply);
136 return PAM_CONV_ERR;
137 }
138 } else {
139 /*
140 * stdio is connected, so interact directly
141 */
142 switch(PAM_MSG_MEMBER(msg, count, msg_style)) {
143 case PAM_PROMPT_ECHO_ON:
144 fputs(PAM_MSG_MEMBER(msg, count, msg), stderr);
145 fgets(buf, sizeof(buf), stdin);
146 reply[count].resp = xstrdup(buf);
147 reply[count].resp_retcode = PAM_SUCCESS;
148 break;
149 case PAM_PROMPT_ECHO_OFF:
150 reply[count].resp =
151 read_passphrase(PAM_MSG_MEMBER(msg, count,
152 msg), RP_ALLOW_STDIN);
153 reply[count].resp_retcode = PAM_SUCCESS;
154 break;
155 case PAM_ERROR_MSG:
156 case PAM_TEXT_INFO:
157 if ((*msg)[count].msg != NULL)
158 fprintf(stderr, "%s\n",
159 PAM_MSG_MEMBER(msg, count, msg));
160 reply[count].resp = xstrdup("");
161 reply[count].resp_retcode = PAM_SUCCESS;
162 break;
163 default:
164 free(reply);
165 return PAM_CONV_ERR;
166 }
148 }
149 }
150
151 *resp = reply;
152
153 return PAM_SUCCESS;
154}
155
156/* Called at exit to cleanly shutdown PAM */
157void do_pam_cleanup_proc(void *context)
158{
167 }
168 }
169
170 *resp = reply;
171
172 return PAM_SUCCESS;
173}
174
175/* Called at exit to cleanly shutdown PAM */
176void do_pam_cleanup_proc(void *context)
177{
159 int pam_retval;
178 int pam_retval = PAM_SUCCESS;
160
179
161 if (pamh != NULL && session_opened) {
162 pam_retval = pam_close_session(pamh, 0);
163 if (pam_retval != PAM_SUCCESS) {
164 log("Cannot close PAM session[%d]: %.200s",
165 pam_retval, PAM_STRERROR(pamh, pam_retval));
166 }
180 if (__pamh && session_opened) {
181 pam_retval = pam_close_session(__pamh, 0);
182 if (pam_retval != PAM_SUCCESS)
183 log("Cannot close PAM session[%d]: %.200s",
184 pam_retval, PAM_STRERROR(__pamh, pam_retval));
167 }
168
185 }
186
169 if (pamh != NULL && creds_set) {
170 pam_retval = pam_setcred(pamh, PAM_DELETE_CRED);
171 if (pam_retval != PAM_SUCCESS) {
187 if (__pamh && creds_set) {
188 pam_retval = pam_setcred(__pamh, PAM_DELETE_CRED);
189 if (pam_retval != PAM_SUCCESS)
172 debug("Cannot delete credentials[%d]: %.200s",
190 debug("Cannot delete credentials[%d]: %.200s",
173 pam_retval, PAM_STRERROR(pamh, pam_retval));
174 }
191 pam_retval, PAM_STRERROR(__pamh, pam_retval));
175 }
176
192 }
193
177 if (pamh != NULL) {
178 pam_retval = pam_end(pamh, pam_retval);
179 if (pam_retval != PAM_SUCCESS) {
180 log("Cannot release PAM authentication[%d]: %.200s",
181 pam_retval, PAM_STRERROR(pamh, pam_retval));
182 }
194 if (__pamh) {
195 pam_retval = pam_end(__pamh, pam_retval);
196 if (pam_retval != PAM_SUCCESS)
197 log("Cannot release PAM authentication[%d]: %.200s",
198 pam_retval, PAM_STRERROR(__pamh, pam_retval));
183 }
184}
185
186/* Attempt password authentation using PAM */
187int auth_pam_password(Authctxt *authctxt, const char *password)
188{
199 }
200}
201
202/* Attempt password authentation using PAM */
203int auth_pam_password(Authctxt *authctxt, const char *password)
204{
189 struct passwd *pw = authctxt->pw;
205 extern ServerOptions options;
190 int pam_retval;
206 int pam_retval;
207 struct passwd *pw = authctxt->pw;
191
192 do_pam_set_conv(&conv);
193
194 /* deny if no user. */
195 if (pw == NULL)
196 return 0;
208
209 do_pam_set_conv(&conv);
210
211 /* deny if no user. */
212 if (pw == NULL)
213 return 0;
197 if (pw->pw_uid == 0 && options.permit_root_login == 2)
214 if (pw->pw_uid == 0 && options.permit_root_login == PERMIT_NO_PASSWD)
198 return 0;
199 if (*password == '\0' && options.permit_empty_passwd == 0)
200 return 0;
201
215 return 0;
216 if (*password == '\0' && options.permit_empty_passwd == 0)
217 return 0;
218
202 pampasswd = password;
203
219 __pampasswd = password;
220
204 pamstate = INITIAL_LOGIN;
221 pamstate = INITIAL_LOGIN;
205 pam_retval = pam_authenticate(pamh, 0);
206 was_authenticated = (pam_retval == PAM_SUCCESS);
222 pam_retval = do_pam_authenticate(
223 options.permit_empty_passwd == 0 ? PAM_DISALLOW_NULL_AUTHTOK : 0);
207 if (pam_retval == PAM_SUCCESS) {
224 if (pam_retval == PAM_SUCCESS) {
208 debug("PAM Password authentication accepted for user \"%.100s\"",
209 pw->pw_name);
225 debug("PAM Password authentication accepted for "
226 "user \"%.100s\"", pw->pw_name);
210 return 1;
211 } else {
227 return 1;
228 } else {
212 debug("PAM Password authentication for \"%.100s\" failed[%d]: %s",
213 pw->pw_name, pam_retval, PAM_STRERROR(pamh, pam_retval));
229 debug("PAM Password authentication for \"%.100s\" "
230 "failed[%d]: %s", pw->pw_name, pam_retval,
231 PAM_STRERROR(__pamh, pam_retval));
214 return 0;
215 }
216}
217
218/* Do account management using PAM */
219int do_pam_account(char *username, char *remote_user)
220{
221 int pam_retval;
222
223 do_pam_set_conv(&conv);
232 return 0;
233 }
234}
235
236/* Do account management using PAM */
237int do_pam_account(char *username, char *remote_user)
238{
239 int pam_retval;
240
241 do_pam_set_conv(&conv);
224
225 debug("PAM setting rhost to \"%.200s\"",
226 get_canonical_hostname(options.verify_reverse_mapping));
227 pam_retval = pam_set_item(pamh, PAM_RHOST,
228 get_canonical_hostname(options.verify_reverse_mapping));
229 if (pam_retval != PAM_SUCCESS) {
230 fatal("PAM set rhost failed[%d]: %.200s",
231 pam_retval, PAM_STRERROR(pamh, pam_retval));
232 }
233
242
234 if (remote_user != NULL) {
243 if (remote_user) {
235 debug("PAM setting ruser to \"%.200s\"", remote_user);
244 debug("PAM setting ruser to \"%.200s\"", remote_user);
236 pam_retval = pam_set_item(pamh, PAM_RUSER, remote_user);
237 if (pam_retval != PAM_SUCCESS) {
238 fatal("PAM set ruser failed[%d]: %.200s",
239 pam_retval, PAM_STRERROR(pamh, pam_retval));
240 }
245 pam_retval = pam_set_item(__pamh, PAM_RUSER, remote_user);
246 if (pam_retval != PAM_SUCCESS)
247 fatal("PAM set ruser failed[%d]: %.200s", pam_retval,
248 PAM_STRERROR(__pamh, pam_retval));
241 }
242
249 }
250
243 pam_retval = pam_acct_mgmt(pamh, 0);
251 pam_retval = pam_acct_mgmt(__pamh, 0);
252 debug2("pam_acct_mgmt() = %d", pam_retval);
244 switch (pam_retval) {
245 case PAM_SUCCESS:
246 /* This is what we want */
247 break;
253 switch (pam_retval) {
254 case PAM_SUCCESS:
255 /* This is what we want */
256 break;
257#if 0
248 case PAM_NEW_AUTHTOK_REQD:
258 case PAM_NEW_AUTHTOK_REQD:
249 pam_msg_cat(NEW_AUTHTOK_MSG);
259 message_cat(&__pam_msg, NEW_AUTHTOK_MSG);
250 /* flag that password change is necessary */
251 password_change_required = 1;
252 break;
260 /* flag that password change is necessary */
261 password_change_required = 1;
262 break;
263#endif
253 default:
264 default:
254 log("PAM rejected by account configuration[%d]: %.200s",
255 pam_retval, PAM_STRERROR(pamh, pam_retval));
265 log("PAM rejected by account configuration[%d]: "
266 "%.200s", pam_retval, PAM_STRERROR(__pamh,
267 pam_retval));
256 return(0);
257 }
268 return(0);
269 }
258
270
259 return(1);
260}
261
262/* Do PAM-specific session initialisation */
263void do_pam_session(char *username, const char *ttyname)
264{
265 int pam_retval;
266
267 do_pam_set_conv(&conv);
268
269 if (ttyname != NULL) {
270 debug("PAM setting tty to \"%.200s\"", ttyname);
271 return(1);
272}
273
274/* Do PAM-specific session initialisation */
275void do_pam_session(char *username, const char *ttyname)
276{
277 int pam_retval;
278
279 do_pam_set_conv(&conv);
280
281 if (ttyname != NULL) {
282 debug("PAM setting tty to \"%.200s\"", ttyname);
271 pam_retval = pam_set_item(pamh, PAM_TTY, ttyname);
272 if (pam_retval != PAM_SUCCESS) {
273 fatal("PAM set tty failed[%d]: %.200s",
274 pam_retval, PAM_STRERROR(pamh, pam_retval));
275 }
283 pam_retval = pam_set_item(__pamh, PAM_TTY, ttyname);
284 if (pam_retval != PAM_SUCCESS)
285 fatal("PAM set tty failed[%d]: %.200s",
286 pam_retval, PAM_STRERROR(__pamh, pam_retval));
276 }
277
287 }
288
278 debug("do_pam_session: euid %u, uid %u", geteuid(), getuid());
279 pam_retval = pam_open_session(pamh, 0);
280 if (pam_retval != PAM_SUCCESS) {
281 fatal("PAM session setup failed[%d]: %.200s",
282 pam_retval, PAM_STRERROR(pamh, pam_retval));
283 }
289 pam_retval = pam_open_session(__pamh, 0);
290 if (pam_retval != PAM_SUCCESS)
291 fatal("PAM session setup failed[%d]: %.200s",
292 pam_retval, PAM_STRERROR(__pamh, pam_retval));
284
285 session_opened = 1;
286}
287
293
294 session_opened = 1;
295}
296
288/* Set PAM credentials */
289void do_pam_setcred(void)
297/* Set PAM credentials */
298void do_pam_setcred(int init)
290{
291 int pam_retval;
292
299{
300 int pam_retval;
301
302 if (__pamh == NULL)
303 return;
304
293 do_pam_set_conv(&conv);
305 do_pam_set_conv(&conv);
294
306
295 debug("PAM establishing creds");
307 debug("PAM establishing creds");
296 pam_retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
308 pam_retval = pam_setcred(__pamh,
309 init ? PAM_ESTABLISH_CRED : PAM_REINITIALIZE_CRED);
297 if (pam_retval != PAM_SUCCESS) {
298 if (was_authenticated)
299 fatal("PAM setcred failed[%d]: %.200s",
310 if (pam_retval != PAM_SUCCESS) {
311 if (was_authenticated)
312 fatal("PAM setcred failed[%d]: %.200s",
300 pam_retval, PAM_STRERROR(pamh, pam_retval));
313 pam_retval, PAM_STRERROR(__pamh, pam_retval));
301 else
302 debug("PAM setcred failed[%d]: %.200s",
314 else
315 debug("PAM setcred failed[%d]: %.200s",
303 pam_retval, PAM_STRERROR(pamh, pam_retval));
316 pam_retval, PAM_STRERROR(__pamh, pam_retval));
304 } else
305 creds_set = 1;
306}
307
308/* accessor function for file scope static variable */
317 } else
318 creds_set = 1;
319}
320
321/* accessor function for file scope static variable */
309int pam_password_change_required(void)
322int is_pam_password_change_required(void)
310{
311 return password_change_required;
312}
313
323{
324 return password_change_required;
325}
326
314/*
327/*
315 * Have user change authentication token if pam_acct_mgmt() indicated
316 * it was expired. This needs to be called after an interactive
317 * session is established and the user's pty is connected to
318 * stdin/stout/stderr.
319 */
320void do_pam_chauthtok(void)
321{
322 int pam_retval;
323
324 do_pam_set_conv(&conv);
325
326 if (password_change_required) {
327 pamstate = OTHER;
328 * Have user change authentication token if pam_acct_mgmt() indicated
329 * it was expired. This needs to be called after an interactive
330 * session is established and the user's pty is connected to
331 * stdin/stout/stderr.
332 */
333void do_pam_chauthtok(void)
334{
335 int pam_retval;
336
337 do_pam_set_conv(&conv);
338
339 if (password_change_required) {
340 pamstate = OTHER;
328 /*
329 * XXX: should we really loop forever?
330 */
331 do {
332 pam_retval = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
333 if (pam_retval != PAM_SUCCESS) {
334 log("PAM pam_chauthtok failed[%d]: %.200s",
335 pam_retval, PAM_STRERROR(pamh, pam_retval));
336 }
337 } while (pam_retval != PAM_SUCCESS);
341 pam_retval = pam_chauthtok(__pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
342 if (pam_retval != PAM_SUCCESS)
343 fatal("PAM pam_chauthtok failed[%d]: %.200s",
344 pam_retval, PAM_STRERROR(__pamh, pam_retval));
338 }
339}
340
341/* Cleanly shutdown PAM */
342void finish_pam(void)
343{
344 do_pam_cleanup_proc(NULL);
345 fatal_remove_cleanup(&do_pam_cleanup_proc, NULL);
346}
347
348/* Start PAM authentication for specified account */
345 }
346}
347
348/* Cleanly shutdown PAM */
349void finish_pam(void)
350{
351 do_pam_cleanup_proc(NULL);
352 fatal_remove_cleanup(&do_pam_cleanup_proc, NULL);
353}
354
355/* Start PAM authentication for specified account */
349void start_pam(struct passwd *pw)
356void start_pam(const char *user)
350{
351 int pam_retval;
357{
358 int pam_retval;
359 extern ServerOptions options;
360 extern u_int utmp_len;
361 const char *rhost;
352
362
353 debug("Starting up PAM with username \"%.200s\"", pw->pw_name);
363 debug("Starting up PAM with username \"%.200s\"", user);
354
364
355 pam_retval = pam_start(SSHD_PAM_SERVICE, pw->pw_name, &conv, &pamh);
365 pam_retval = pam_start(SSHD_PAM_SERVICE, user, &conv, &__pamh);
356
366
357 if (pam_retval != PAM_SUCCESS) {
358 fatal("PAM initialisation failed[%d]: %.200s",
359 pam_retval, PAM_STRERROR(pamh, pam_retval));
360 }
367 if (pam_retval != PAM_SUCCESS)
368 fatal("PAM initialisation failed[%d]: %.200s",
369 pam_retval, PAM_STRERROR(__pamh, pam_retval));
361
370
371 rhost = get_remote_name_or_ip(utmp_len, options.verify_reverse_mapping);
372 debug("PAM setting rhost to \"%.200s\"", rhost);
373
374 pam_retval = pam_set_item(__pamh, PAM_RHOST, rhost);
375 if (pam_retval != PAM_SUCCESS)
376 fatal("PAM set rhost failed[%d]: %.200s", pam_retval,
377 PAM_STRERROR(__pamh, pam_retval));
362#ifdef PAM_TTY_KLUDGE
363 /*
364 * Some PAM modules (e.g. pam_time) require a TTY to operate,
378#ifdef PAM_TTY_KLUDGE
379 /*
380 * Some PAM modules (e.g. pam_time) require a TTY to operate,
365 * and will fail in various stupid ways if they don't get one.
381 * and will fail in various stupid ways if they don't get one.
366 * sshd doesn't set the tty until too late in the auth process and may
367 * not even need one (for tty-less connections)
382 * sshd doesn't set the tty until too late in the auth process and may
383 * not even need one (for tty-less connections)
368 * Kludge: Set a fake PAM_TTY
384 * Kludge: Set a fake PAM_TTY
369 */
385 */
370 pam_retval = pam_set_item(pamh, PAM_TTY, "ssh");
371 if (pam_retval != PAM_SUCCESS) {
372 fatal("PAM set tty failed[%d]: %.200s",
373 pam_retval, PAM_STRERROR(pamh, pam_retval));
374 }
386 pam_retval = pam_set_item(__pamh, PAM_TTY, "NODEVssh");
387 if (pam_retval != PAM_SUCCESS)
388 fatal("PAM set tty failed[%d]: %.200s",
389 pam_retval, PAM_STRERROR(__pamh, pam_retval));
375#endif /* PAM_TTY_KLUDGE */
376
377 fatal_add_cleanup(&do_pam_cleanup_proc, NULL);
378}
379
380/* Return list of PAM enviornment strings */
381char **fetch_pam_environment(void)
382{
383#ifdef HAVE_PAM_GETENVLIST
390#endif /* PAM_TTY_KLUDGE */
391
392 fatal_add_cleanup(&do_pam_cleanup_proc, NULL);
393}
394
395/* Return list of PAM enviornment strings */
396char **fetch_pam_environment(void)
397{
398#ifdef HAVE_PAM_GETENVLIST
384 return(pam_getenvlist(pamh));
399 return(pam_getenvlist(__pamh));
385#else /* HAVE_PAM_GETENVLIST */
386 return(NULL);
387#endif /* HAVE_PAM_GETENVLIST */
388}
389
390/* Print any messages that have been generated during authentication */
391/* or account checking to stderr */
392void print_pam_messages(void)
393{
400#else /* HAVE_PAM_GETENVLIST */
401 return(NULL);
402#endif /* HAVE_PAM_GETENVLIST */
403}
404
405/* Print any messages that have been generated during authentication */
406/* or account checking to stderr */
407void print_pam_messages(void)
408{
394 if (pam_msg != NULL)
395 fputs(pam_msg, stderr);
409 if (__pam_msg != NULL)
410 fputs(__pam_msg, stderr);
396}
397
411}
412
398/* Append a message to the PAM message buffer */
399void pam_msg_cat(const char *msg)
413/* Append a message to buffer */
414void message_cat(char **p, const char *a)
400{
415{
401 char *p;
402 size_t new_msg_len;
403 size_t pam_msg_len;
404
405 new_msg_len = strlen(msg);
406
407 if (pam_msg) {
408 pam_msg_len = strlen(pam_msg);
409 pam_msg = xrealloc(pam_msg, new_msg_len + pam_msg_len + 2);
410 p = pam_msg + pam_msg_len;
411 } else {
412 pam_msg = p = xmalloc(new_msg_len + 2);
413 }
416 char *cp;
417 size_t new_len;
414
418
415 memcpy(p, msg, new_msg_len);
416 p[new_msg_len] = '\n';
417 p[new_msg_len + 1] = '\0';
418}
419 new_len = strlen(a);
419
420
420struct inverted_pam_userdata {
421 /*
422 * Pipe for telling whether we are doing conversation or sending
423 * authentication results.
424 */
425 int statefd[2];
426 int challengefd[2];
427 int responsefd[2];
421 if (*p) {
422 size_t len = strlen(*p);
428
423
429 /* Whether we have sent off our challenge */
430 int state;
431};
424 *p = xrealloc(*p, new_len + len + 2);
425 cp = *p + len;
426 } else
427 *p = cp = xmalloc(new_len + 2);
432
428
433#define STATE_CONV 1
434#define STATE_AUTH_OK 2
435#define STATE_AUTH_FAIL 3
436
437int
438ssh_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp,
439 void *userdata) {
440 int i;
441 FILE *reader;
442 char buf[1024];
443 struct pam_response *reply = NULL;
444 char state_to_write = STATE_CONV; /* One char to write */
445 struct inverted_pam_userdata *ud = userdata;
446 char *response = NULL;
447
448 /* The stdio functions are more convenient for the read half */
449 reader = fdopen(ud->responsefd[0], "rb");
450 if (reader == NULL)
451 goto protocol_failure;
452
453 reply = malloc(num_msg * sizeof(struct pam_response));
454 if (reply == NULL)
455 return PAM_CONV_ERR;
456
457 if (write(ud->statefd[1], &state_to_write, 1) != 1)
458 goto protocol_failure;
459
460 /*
461 * Re-package our data and send it off to our better half (the actual SSH
462 * process)
463 */
464 if (write(ud->challengefd[1], buf,
465 sprintf(buf, "%d\n", num_msg)) == -1)
466 goto protocol_failure;
467 for (i = 0; i < num_msg; i++) {
468 if (write(ud->challengefd[1], buf,
469 sprintf(buf, "%d\n", msg[i]->msg_style)) == -1)
470 goto protocol_failure;
471 if (write(ud->challengefd[1], buf,
472 sprintf(buf, "%d\n", strlen(msg[i]->msg))) == -1)
473 goto protocol_failure;
474 if (write(ud->challengefd[1], msg[i]->msg,
475 strlen(msg[i]->msg)) == -1)
476 goto protocol_failure;
477 }
478 /*
479 * Read back responses. These may not be as nice as we want, as the SSH
480 * protocol isn't exactly a perfect fit with PAM.
481 */
482
483 for (i = 0; i < num_msg; i++) {
484 char buf[1024];
485 char *endptr;
486 size_t len; /* Length of the response */
487
488 switch (msg[i]->msg_style) {
489 case PAM_PROMPT_ECHO_OFF:
490 case PAM_PROMPT_ECHO_ON:
491 if (fgets(buf, sizeof(buf), reader) == NULL)
492 goto protocol_failure;
493 len = (size_t)strtoul(buf, &endptr, 10);
494 /* The length is supposed to stand on a line by itself */
495 if (endptr == NULL || *endptr != '\n')
496 goto protocol_failure;
497 response = malloc(len+1);
498 if (response == NULL)
499 goto protocol_failure;
500 if (fread(response, len, 1, reader) != 1)
501 goto protocol_failure;
502 response[len] = '\0';
503 reply[i].resp = response;
504 response = NULL;
505 break;
506 default:
507 reply[i].resp = NULL;
508 break;
509 }
510 }
511 *resp = reply;
512 return PAM_SUCCESS;
513 protocol_failure:
514 free(reply);
515 return PAM_CONV_ERR;
429 memcpy(cp, a, new_len);
430 cp[new_len] = '\n';
431 cp[new_len + 1] = '\0';
516}
517
432}
433
518void
519ipam_free_cookie(struct inverted_pam_cookie *cookie) {
520 struct inverted_pam_userdata *ud;
521 int i;
522
523 if (cookie == NULL)
524 return;
525 ud = cookie->userdata;
526 cookie->userdata = NULL;
527 /* Free userdata if allocated */
528 if (ud) {
529 /* Close any opened file descriptors */
530 if (ud->statefd[0] != -1)
531 close(ud->statefd[0]);
532 if (ud->statefd[1] != -1)
533 close(ud->statefd[1]);
534 if (ud->challengefd[0] != -1)
535 close(ud->challengefd[0]);
536 if (ud->challengefd[1] != -1)
537 close(ud->challengefd[1]);
538 if (ud->responsefd[0] != -1)
539 close(ud->responsefd[0]);
540 if (ud->responsefd[1] != -1)
541 close(ud->responsefd[1]);
542 free(ud);
543 ud = NULL;
544 }
545 /* Now free the normal cookie */
546 if (cookie->pid != 0 && cookie->pid != -1) {
547 int status;
548
549 /* XXX Use different signal? */
550 kill(cookie->pid, SIGKILL);
551 waitpid(cookie->pid, &status, 0);
552 }
553 for (i = 0; i < cookie->num_msg; i++) {
554 if (cookie->resp && cookie->resp[i]) {
555 free(cookie->resp[i]->resp);
556 free(cookie->resp[i]);
557 }
558 if (cookie->msg && cookie->msg[i]) {
559 free((void *)cookie->msg[i]->msg);
560 free(cookie->msg[i]);
561 }
562 }
563 free(cookie->msg);
564 free(cookie->resp);
565 free(cookie);
566}
567
568/*
569 * Do first half of PAM authentication - this comes to the point where
570 * you get a message to send to the user.
571 */
572struct inverted_pam_cookie *
573ipam_start_auth(const char *service, const char *username) {
574 struct inverted_pam_cookie *cookie;
575 struct inverted_pam_userdata *ud;
576 static struct pam_conv conv = {
577 ssh_conv,
578 NULL
579 };
580 const char *rhost;
581
582 cookie = malloc(sizeof(*cookie));
583 if (cookie == NULL)
584 return NULL;
585 cookie->state = 0;
586 /* Set up the cookie so ipam_freecookie can be used on it */
587 cookie->num_msg = 0;
588 cookie->msg = NULL;
589 cookie->resp = NULL;
590 cookie->pid = -1;
591
592 ud = calloc(sizeof(*ud), 1);
593 if (ud == NULL) {
594 free(cookie);
595 return NULL;
596 }
597 cookie->userdata = ud;
598 ud->statefd[0] = ud->statefd[1] = -1;
599 ud->challengefd[0] = ud->challengefd[1] = -1;
600 ud->responsefd[0] = ud->responsefd[1] = -1;
601
602 if (pipe(ud->statefd) != 0) {
603 ud->statefd[0] = ud->statefd[1] = -1;
604 ipam_free_cookie(cookie);
605 return NULL;
606 }
607 if (pipe(ud->challengefd) != 0) {
608 ud->challengefd[0] = ud->challengefd[1] = -1;
609 ipam_free_cookie(cookie);
610 return NULL;
611 }
612 if (pipe(ud->responsefd) != 0) {
613 ud->responsefd[0] = ud->responsefd[1] = -1;
614 ipam_free_cookie(cookie);
615 return NULL;
616 }
617 rhost = get_canonical_hostname(options.verify_reverse_mapping);
618 cookie->pid = fork();
619 if (cookie->pid == -1) {
620 ipam_free_cookie(cookie);
621 return NULL;
622 } else if (cookie->pid != 0) {
623 int num_msgs; /* Number of messages from PAM */
624 char *endptr;
625 char buf[1024];
626 FILE *reader;
627 size_t num_msg;
628 int i;
629 char state; /* Which state did the connection just enter? */
630
631 /* We are the parent - wait for a call to the communications
632 function to turn up, or the challenge to be finished */
633 if (read(ud->statefd[0], &state, 1) != 1) {
634 ipam_free_cookie(cookie);
635 return NULL;
636 }
637 cookie->state = state;
638 switch (state) {
639 case STATE_CONV:
640 /* We are running the conversation function */
641 /* The stdio functions are more convenient for read */
642 reader = fdopen(ud->challengefd[0], "r");
643 if (reader == NULL) {
644 ipam_free_cookie(cookie);
645 return NULL;
646 }
647 if (fgets(buf, 4, reader) == NULL) {
648 fclose(reader);
649 ipam_free_cookie(cookie);
650 return NULL;
651 }
652 num_msg = (size_t)strtoul(buf, &endptr, 10);
653 /* The length is supposed to stand on a line by itself */
654 if (endptr == NULL || *endptr != '\n') {
655 fclose(reader);
656 ipam_free_cookie(cookie);
657 return NULL;
658 }
659 cookie->msg =
660 malloc(sizeof(struct pam_message *) * num_msg);
661 cookie->resp =
662 malloc(sizeof(struct pam_response *) * num_msg);
663 if (cookie->msg == NULL || cookie->resp == NULL) {
664 fclose(reader);
665 ipam_free_cookie(cookie);
666 return NULL;
667 }
668 for (i = 0; i < num_msg; i++) {
669 cookie->msg[i] =
670 malloc(sizeof(struct pam_message));
671 cookie->resp[i] =
672 malloc(sizeof(struct pam_response));
673 if (cookie->msg[i] == NULL ||
674 cookie->resp[i] == NULL) {
675 for (;;) {
676 free(cookie->msg[i]);
677 free(cookie->resp[i]);
678 if (i == 0)
679 break;
680 i--;
681 }
682 fclose(reader);
683 ipam_free_cookie(cookie);
684 return NULL;
685 }
686 cookie->msg[i]->msg = NULL;
687 cookie->resp[i]->resp = NULL;
688 cookie->resp[i]->resp_retcode = 0;
689 }
690 /* Set up so the above will be freed on failure */
691 cookie->num_msg = num_msg;
692 /*
693 * We have a an allocated response and message for
694 * each of the entries in the PAM structure - transfer
695 * the data sent to the conversation function over.
696 */
697 for (i = 0; i < num_msg; i++) {
698 size_t len;
699
700 if (fgets(buf, sizeof(buf), reader) == NULL) {
701 fclose(reader);
702 ipam_free_cookie(cookie);
703 return NULL;
704 }
705 cookie->msg[i]->msg_style =
706 (size_t)strtoul(buf, &endptr, 10);
707 if (endptr == NULL || *endptr != '\n') {
708 fclose(reader);
709 ipam_free_cookie(cookie);
710 return NULL;
711 }
712 if (fgets(buf, sizeof(buf), reader) == NULL) {
713 fclose(reader);
714 ipam_free_cookie(cookie);
715 return NULL;
716 }
717 len = (size_t)strtoul(buf, &endptr, 10);
718 if (endptr == NULL || *endptr != '\n') {
719 fclose(reader);
720 ipam_free_cookie(cookie);
721 return NULL;
722 }
723 cookie->msg[i]->msg = malloc(len + 1);
724 if (cookie->msg[i]->msg == NULL) {
725 fclose(reader);
726 ipam_free_cookie(cookie);
727 return NULL;
728 }
729 if (fread((char *)cookie->msg[i]->msg, len, 1, reader) !=
730 1) {
731 fclose(reader);
732 ipam_free_cookie(cookie);
733 return NULL;
734 }
735 *(char *)&(cookie->msg[i]->msg[len]) = '\0';
736 }
737 break;
738 case STATE_AUTH_OK:
739 case STATE_AUTH_FAIL:
740 break;
741 default:
742 /* Internal failure, somehow */
743 fclose(reader);
744 ipam_free_cookie(cookie);
745 return NULL;
746 }
747 return cookie;
748 } else {
749 /* We are the child */
750 pam_handle_t *pamh=NULL;
751 int retval;
752 char state;
753
754 conv.appdata_ptr = ud;
755 retval = pam_start(service, username, &conv, &pamh);
756 fprintf(stderr, "pam_start returned %d\n", retval);
757 if (retval == PAM_SUCCESS)
758 retval = pam_set_item(pamh, PAM_RHOST, rhost);
759 /* Is user really user? */
760 if (retval == PAM_SUCCESS)
761 retval = pam_authenticate(pamh, 0);
762 /* permitted access? */
763 if (retval == PAM_SUCCESS)
764 retval = pam_acct_mgmt(pamh, 0);
765 /* This is where we have been authorized or not. */
766
767 /* Be conservative - flag as auth failure if we can't close */
768 /*
769 * XXX This is based on example code from Linux-PAM -
770 * but can it really be correct to pam_end if
771 * pam_start failed?
772 */
773 if (pam_end(pamh, retval) != PAM_SUCCESS)
774 retval = PAM_AUTH_ERR;
775
776 /* Message to parent */
777 state = retval == PAM_SUCCESS ? STATE_AUTH_OK : STATE_AUTH_FAIL;
778 if (write(ud->statefd[1], &state, 1) != 1) {
779 _exit(1);
780 }
781 /* FDs will be closed, so further communication will stop */
782 _exit(0);
783 }
784}
785
786/*
787 * Do second half of PAM authentication - cookie should now be filled
788 * in with the response to the challenge.
789 */
790
791int
792ipam_complete_auth(struct inverted_pam_cookie *cookie) {
793 int i;
794 char buf[1024];
795 struct inverted_pam_userdata *ud = cookie->userdata;
796 char state;
797
798 /* Send over our responses */
799 for (i = 0; i < cookie->num_msg; i++) {
800 if (cookie->msg[i]->msg_style != PAM_PROMPT_ECHO_ON &&
801 cookie->msg[i]->msg_style != PAM_PROMPT_ECHO_OFF)
802 continue;
803 if (write(ud->responsefd[1], buf,
804 sprintf(buf, "%d\n", strlen(cookie->resp[i]->resp))) == -1) {
805 ipam_free_cookie(cookie);
806 return 0;
807 }
808 if (write(ud->responsefd[1], cookie->resp[i]->resp,
809 strlen(cookie->resp[i]->resp)) == -1) {
810 ipam_free_cookie(cookie);
811 return 0;
812 }
813 }
814 /* Find out what state we are changing to */
815 if (read(ud->statefd[0], &state, 1) != 1) {
816 ipam_free_cookie(cookie);
817 return 0;
818 }
819
820 return state == STATE_AUTH_OK ? 1 : 0;
821}
822
823#endif /* USE_PAM */
434#endif /* USE_PAM */