auth-pam.c revision 76394
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.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
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"
32#include "servconf.h"
33#include "readpass.h"
34#include "canohost.h"
35
36RCSID("$FreeBSD: head/crypto/openssh/auth-pam.c 76394 2001-05-09 03:40:37Z alfred $");
37
38#define NEW_AUTHTOK_MSG \
39	"Warning: Your password has expired, please change it now"
40
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,
46	  struct pam_response **resp, void *appdata_ptr);
47void do_pam_cleanup_proc(void *context);
48void pam_msg_cat(const char *msg);
49
50/* module-local variables */
51static struct pam_conv conv = {
52	do_pam_conversation,
53	NULL
54};
55static pam_handle_t *pamh = NULL;
56static const char *pampasswd = NULL;
57static char *pam_msg = NULL;
58extern ServerOptions options;
59
60/* states for do_pam_conversation() */
61typedef enum { INITIAL_LOGIN, OTHER } pamstates;
62static pamstates 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
72/*
73 * accessor which allows us to switch conversation structs according to
74 * the authentication method being used
75 */
76void do_pam_set_conv(struct pam_conv *conv)
77{
78	pam_set_item(pamh, PAM_CONV, conv);
79}
80
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
87 * messages with pam_msg_cat().  This is used during initial
88 * authentication to bypass the normal PAM password prompt.
89 *
90 * OTHER mode handles PAM_PROMPT_ECHO_OFF with read_passphrase(prompt, 1)
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)
104		return PAM_CONV_ERR;
105
106	for (count = 0; count < num_msg; count++) {
107		switch ((*msg)[count].msg_style) {
108			case PAM_PROMPT_ECHO_ON:
109				if (pamstate == INITIAL_LOGIN) {
110					free(reply);
111					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				}
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				}
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) {
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					}
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;
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{
159	int pam_retval;
160
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		}
167	}
168
169	if (pamh != NULL && creds_set) {
170		pam_retval = pam_setcred(pamh, PAM_DELETE_CRED);
171		if (pam_retval != PAM_SUCCESS) {
172			debug("Cannot delete credentials[%d]: %.200s",
173				pam_retval, PAM_STRERROR(pamh, pam_retval));
174		}
175	}
176
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		}
183	}
184}
185
186/* Attempt password authentation using PAM */
187int auth_pam_password(Authctxt *authctxt, const char *password)
188{
189	struct passwd *pw = authctxt->pw;
190	int pam_retval;
191
192	do_pam_set_conv(&conv);
193
194	/* deny if no user. */
195	if (pw == NULL)
196		return 0;
197	if (pw->pw_uid == 0 && options.permit_root_login == 2)
198		return 0;
199	if (*password == '\0' && options.permit_empty_passwd == 0)
200		return 0;
201
202	pampasswd = password;
203
204	pamstate = INITIAL_LOGIN;
205	pam_retval = pam_authenticate(pamh, 0);
206	was_authenticated = (pam_retval == PAM_SUCCESS);
207	if (pam_retval == PAM_SUCCESS) {
208		debug("PAM Password authentication accepted for user \"%.100s\"",
209			pw->pw_name);
210		return 1;
211	} else {
212		debug("PAM Password authentication for \"%.100s\" failed[%d]: %s",
213			pw->pw_name, pam_retval, 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);
224
225	debug("PAM setting rhost to \"%.200s\"",
226	    get_canonical_hostname(options.reverse_mapping_check));
227	pam_retval = pam_set_item(pamh, PAM_RHOST,
228		get_canonical_hostname(options.reverse_mapping_check));
229	if (pam_retval != PAM_SUCCESS) {
230		fatal("PAM set rhost failed[%d]: %.200s",
231			pam_retval, PAM_STRERROR(pamh, pam_retval));
232	}
233
234	if (remote_user != NULL) {
235		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		}
241	}
242
243	pam_retval = pam_acct_mgmt(pamh, 0);
244	switch (pam_retval) {
245		case PAM_SUCCESS:
246			/* This is what we want */
247			break;
248		case PAM_NEW_AUTHTOK_REQD:
249			pam_msg_cat(NEW_AUTHTOK_MSG);
250			/* flag that password change is necessary */
251			password_change_required = 1;
252			break;
253		default:
254			log("PAM rejected by account configuration[%d]: %.200s",
255				pam_retval, PAM_STRERROR(pamh, pam_retval));
256			return(0);
257	}
258
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		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		}
276	}
277
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	}
284
285	session_opened = 1;
286}
287
288/* Set PAM credentials */
289void do_pam_setcred(void)
290{
291	int pam_retval;
292
293	do_pam_set_conv(&conv);
294
295	debug("PAM establishing creds");
296	pam_retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
297	if (pam_retval != PAM_SUCCESS) {
298		if (was_authenticated)
299			fatal("PAM setcred failed[%d]: %.200s",
300			      pam_retval, PAM_STRERROR(pamh, pam_retval));
301		else
302			debug("PAM setcred failed[%d]: %.200s",
303			      pam_retval, PAM_STRERROR(pamh, pam_retval));
304	} else
305		creds_set = 1;
306}
307
308/* accessor function for file scope static variable */
309int pam_password_change_required(void)
310{
311	return password_change_required;
312}
313
314/*
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		/*
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);
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 */
349void start_pam(struct passwd *pw)
350{
351	int pam_retval;
352
353	debug("Starting up PAM with username \"%.200s\"", pw->pw_name);
354
355	pam_retval = pam_start(SSHD_PAM_SERVICE, pw->pw_name, &conv, &pamh);
356
357	if (pam_retval != PAM_SUCCESS) {
358		fatal("PAM initialisation failed[%d]: %.200s",
359			pam_retval, PAM_STRERROR(pamh, pam_retval));
360	}
361
362#ifdef PAM_TTY_KLUDGE
363	/*
364	 * 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.
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)
368	 * Kludge: Set a fake PAM_TTY
369	 */
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	}
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
384	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{
394	if (pam_msg != NULL)
395		fputs(pam_msg, stderr);
396}
397
398/* Append a message to the PAM message buffer */
399void pam_msg_cat(const char *msg)
400{
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	}
414
415	memcpy(p, msg, new_msg_len);
416	p[new_msg_len] = '\n';
417	p[new_msg_len + 1] = '\0';
418}
419
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];
428
429    /* Whether we have sent off our challenge */
430    int state;
431};
432
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;
516}
517
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
581	cookie = malloc(sizeof(*cookie));
582	if (cookie == NULL)
583		return NULL;
584	cookie->state = 0;
585	/* Set up the cookie so ipam_freecookie can be used on it */
586	cookie->num_msg = 0;
587	cookie->msg = NULL;
588	cookie->resp = NULL;
589	cookie->pid = -1;
590
591	ud = calloc(sizeof(*ud), 1);
592	if (ud == NULL) {
593		free(cookie);
594		return NULL;
595	}
596	cookie->userdata = ud;
597	ud->statefd[0] = ud->statefd[1] = -1;
598	ud->challengefd[0] = ud->challengefd[1] = -1;
599	ud->responsefd[0] = ud->responsefd[1] = -1;
600
601	if (pipe(ud->statefd) != 0) {
602		ud->statefd[0] = ud->statefd[1] = -1;
603		ipam_free_cookie(cookie);
604		return NULL;
605	}
606	if (pipe(ud->challengefd) != 0) {
607		ud->challengefd[0] = ud->challengefd[1] = -1;
608		ipam_free_cookie(cookie);
609		return NULL;
610	}
611	if (pipe(ud->responsefd) != 0) {
612		ud->responsefd[0] = ud->responsefd[1] = -1;
613		ipam_free_cookie(cookie);
614		return NULL;
615	}
616	cookie->pid = fork();
617	if (cookie->pid == -1) {
618		ipam_free_cookie(cookie);
619		return NULL;
620	} else if (cookie->pid != 0) {
621		int num_msgs;	/* Number of messages from PAM */
622		char *endptr;
623		char buf[1024];
624		FILE *reader;
625		size_t num_msg;
626		int i;
627		char state;	/* Which state did the connection just enter? */
628
629		/* We are the parent - wait for a call to the communications
630		   function to turn up, or the challenge to be finished */
631		if (read(ud->statefd[0], &state, 1) != 1) {
632			ipam_free_cookie(cookie);
633			return NULL;
634		}
635		cookie->state = state;
636		switch (state) {
637		case STATE_CONV:
638			/* We are running the conversation function */
639			/* The stdio functions are more convenient for read */
640			reader = fdopen(ud->challengefd[0], "r");
641			if (reader == NULL) {
642				ipam_free_cookie(cookie);
643				return NULL;
644			}
645			if (fgets(buf, 4, reader) == NULL) {
646				fclose(reader);
647				ipam_free_cookie(cookie);
648				return NULL;
649			}
650			num_msg = (size_t)strtoul(buf, &endptr, 10);
651			/* The length is supposed to stand on a line by itself */
652			if (endptr == NULL || *endptr != '\n') {
653				fclose(reader);
654				ipam_free_cookie(cookie);
655				return NULL;
656			}
657			cookie->msg =
658				malloc(sizeof(struct pam_message *) * num_msg);
659			cookie->resp =
660				malloc(sizeof(struct pam_response *) * num_msg);
661			if (cookie->msg == NULL || cookie->resp == NULL) {
662				fclose(reader);
663				ipam_free_cookie(cookie);
664				return NULL;
665			}
666			for (i = 0; i < num_msg; i++) {
667				cookie->msg[i] =
668					malloc(sizeof(struct pam_message));
669				cookie->resp[i] =
670					malloc(sizeof(struct pam_response));
671				if (cookie->msg[i] == NULL ||
672				    cookie->resp[i] == NULL) {
673					for (;;) {
674						free(cookie->msg[i]);
675						free(cookie->resp[i]);
676						if (i == 0)
677							break;
678						i--;
679					}
680					fclose(reader);
681					ipam_free_cookie(cookie);
682					return NULL;
683				}
684				cookie->msg[i]->msg = NULL;
685				cookie->resp[i]->resp = NULL;
686				cookie->resp[i]->resp_retcode = 0;
687			}
688			/* Set up so the above will be freed on failure */
689			cookie->num_msg = num_msg;
690			/*
691			 * We have a an allocated response and message for
692			 * each of the entries in the PAM structure - transfer
693			 * the data sent to the conversation function over.
694			 */
695			for (i = 0; i < num_msg; i++) {
696				size_t len;
697
698				if (fgets(buf, sizeof(buf), reader) == NULL) {
699					fclose(reader);
700					ipam_free_cookie(cookie);
701					return NULL;
702				}
703				cookie->msg[i]->msg_style =
704					(size_t)strtoul(buf, &endptr, 10);
705				if (endptr == NULL || *endptr != '\n') {
706					fclose(reader);
707					ipam_free_cookie(cookie);
708					return NULL;
709				}
710				if (fgets(buf, sizeof(buf), reader) == NULL) {
711					fclose(reader);
712					ipam_free_cookie(cookie);
713					return NULL;
714				}
715				len = (size_t)strtoul(buf, &endptr, 10);
716				if (endptr == NULL || *endptr != '\n') {
717					fclose(reader);
718					ipam_free_cookie(cookie);
719					return NULL;
720				}
721				cookie->msg[i]->msg = malloc(len + 1);
722				if (cookie->msg[i]->msg == NULL) {
723					fclose(reader);
724					ipam_free_cookie(cookie);
725					return NULL;
726				}
727				if (fread((char *)cookie->msg[i]->msg, len, 1, reader) !=
728				    1) {
729					fclose(reader);
730					ipam_free_cookie(cookie);
731					return NULL;
732				}
733				*(char *)&(cookie->msg[i]->msg[len]) = '\0';
734			}
735			break;
736		case STATE_AUTH_OK:
737		case STATE_AUTH_FAIL:
738			break;
739		default:
740			/* Internal failure, somehow */
741			fclose(reader);
742			ipam_free_cookie(cookie);
743			return NULL;
744		}
745		return cookie;
746	} else {
747		/* We are the child */
748		pam_handle_t *pamh=NULL;
749		int retval;
750		char state;
751
752		conv.appdata_ptr = ud;
753		retval = pam_start(service, username, &conv, &pamh);
754		/* Is user really user? */
755		if (retval == PAM_SUCCESS)
756			retval = pam_authenticate(pamh, 0);
757		/* permitted access? */
758		if (retval == PAM_SUCCESS)
759			retval = pam_acct_mgmt(pamh, 0);
760		/* This is where we have been authorized or not. */
761
762		/* Be conservative - flag as auth failure if we can't close */
763		/*
764		 * XXX This is based on example code from Linux-PAM -
765		 * but can it really be correct to pam_end if
766		 * pam_start failed?
767		 */
768		if (pam_end(pamh, retval) != PAM_SUCCESS)
769			retval = PAM_AUTH_ERR;
770
771		/* Message to parent */
772		state = retval == PAM_SUCCESS ? STATE_AUTH_OK : STATE_AUTH_FAIL;
773		if (write(ud->statefd[1], &state, 1) != 1) {
774			_exit(1);
775		}
776		/* FDs will be closed, so further communication will stop */
777		_exit(0);
778	}
779}
780
781/*
782 * Do second half of PAM authentication - cookie should now be filled
783 * in with the response to the challenge.
784 */
785
786int
787ipam_complete_auth(struct inverted_pam_cookie *cookie) {
788    int i;
789    char buf[1024];
790    struct inverted_pam_userdata *ud = cookie->userdata;
791    char state;
792
793    /* Send over our responses */
794    for (i = 0; i < cookie->num_msg; i++) {
795	if (cookie->msg[i]->msg_style != PAM_PROMPT_ECHO_ON &&
796	    cookie->msg[i]->msg_style != PAM_PROMPT_ECHO_OFF)
797	    continue;
798	if (write(ud->responsefd[1], buf,
799		  sprintf(buf, "%d\n", strlen(cookie->resp[i]->resp))) == -1) {
800	    ipam_free_cookie(cookie);
801	    return 0;
802	}
803	if (write(ud->responsefd[1], cookie->resp[i]->resp,
804		  strlen(cookie->resp[i]->resp)) == -1) {
805	    ipam_free_cookie(cookie);
806	    return 0;
807	}
808    }
809    /* Find out what state we are changing to */
810    if (read(ud->statefd[0], &state, 1) != 1) {
811	ipam_free_cookie(cookie);
812	return 0;
813    }
814
815    return state == STATE_AUTH_OK ? 1 : 0;
816}
817
818#endif /* USE_PAM */
819