xref: /freebsd/crypto/openssh/auth-pam.c (revision 099584266b3a508b982965708de56eaba461bee4)
109958426SBrian Feldman /*
209958426SBrian Feldman  * Copyright (c) 2000 Damien Miller.  All rights reserved.
309958426SBrian Feldman  *
409958426SBrian Feldman  * Redistribution and use in source and binary forms, with or without
509958426SBrian Feldman  * modification, are permitted provided that the following conditions
609958426SBrian Feldman  * are met:
709958426SBrian Feldman  * 1. Redistributions of source code must retain the above copyright
809958426SBrian Feldman  *    notice, this list of conditions and the following disclaimer.
909958426SBrian Feldman  * 2. Redistributions in binary form must reproduce the above copyright
1009958426SBrian Feldman  *    notice, this list of conditions and the following disclaimer in the
1109958426SBrian Feldman  *    documentation and/or other materials provided with the distribution.
1209958426SBrian Feldman  *
1309958426SBrian Feldman  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1409958426SBrian Feldman  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1509958426SBrian Feldman  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1609958426SBrian Feldman  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
1709958426SBrian Feldman  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
1809958426SBrian Feldman  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
1909958426SBrian Feldman  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2009958426SBrian Feldman  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2109958426SBrian Feldman  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2209958426SBrian Feldman  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2309958426SBrian Feldman  */
2409958426SBrian Feldman 
2509958426SBrian Feldman #include "includes.h"
2609958426SBrian Feldman 
2709958426SBrian Feldman #ifdef USE_PAM
2809958426SBrian Feldman #include <security/pam_appl.h>
2909958426SBrian Feldman #include "ssh.h"
3009958426SBrian Feldman #include "xmalloc.h"
3109958426SBrian Feldman #include "servconf.h"
3209958426SBrian Feldman 
3309958426SBrian Feldman RCSID("$FreeBSD$");
3409958426SBrian Feldman 
3509958426SBrian Feldman #define NEW_AUTHTOK_MSG \
3609958426SBrian Feldman 	"Warning: Your password has expired, please change it now"
3709958426SBrian Feldman 
3809958426SBrian Feldman #define SSHD_PAM_SERVICE "sshd"
3909958426SBrian Feldman #define PAM_STRERROR(a, b) pam_strerror((a), (b))
4009958426SBrian Feldman 
4109958426SBrian Feldman /* Callbacks */
4209958426SBrian Feldman static int pamconv(int num_msg, const struct pam_message **msg,
4309958426SBrian Feldman 	  struct pam_response **resp, void *appdata_ptr);
4409958426SBrian Feldman void pam_cleanup_proc(void *context);
4509958426SBrian Feldman void pam_msg_cat(const char *msg);
4609958426SBrian Feldman 
4709958426SBrian Feldman /* module-local variables */
4809958426SBrian Feldman static struct pam_conv conv = {
4909958426SBrian Feldman 	pamconv,
5009958426SBrian Feldman 	NULL
5109958426SBrian Feldman };
5209958426SBrian Feldman static pam_handle_t *pamh = NULL;
5309958426SBrian Feldman static const char *pampasswd = NULL;
5409958426SBrian Feldman static char *pam_msg = NULL;
5509958426SBrian Feldman 
5609958426SBrian Feldman /* states for pamconv() */
5709958426SBrian Feldman typedef enum { INITIAL_LOGIN, OTHER } pamstates;
5809958426SBrian Feldman static pamstates pamstate = INITIAL_LOGIN;
5909958426SBrian Feldman /* remember whether pam_acct_mgmt() returned PAM_NEWAUTHTOK_REQD */
6009958426SBrian Feldman static int password_change_required = 0;
6109958426SBrian Feldman 
6209958426SBrian Feldman /*
6309958426SBrian Feldman  * PAM conversation function.
6409958426SBrian Feldman  * There are two states this can run in.
6509958426SBrian Feldman  *
6609958426SBrian Feldman  * INITIAL_LOGIN mode simply feeds the password from the client into
6709958426SBrian Feldman  * PAM in response to PAM_PROMPT_ECHO_OFF, and collects output
6809958426SBrian Feldman  * messages with pam_msg_cat().  This is used during initial
6909958426SBrian Feldman  * authentication to bypass the normal PAM password prompt.
7009958426SBrian Feldman  *
7109958426SBrian Feldman  * OTHER mode handles PAM_PROMPT_ECHO_OFF with read_passphrase(prompt, 1)
7209958426SBrian Feldman  * and outputs messages to stderr. This mode is used if pam_chauthtok()
7309958426SBrian Feldman  * is called to update expired passwords.
7409958426SBrian Feldman  */
7509958426SBrian Feldman static int pamconv(int num_msg, const struct pam_message **msg,
7609958426SBrian Feldman 	struct pam_response **resp, void *appdata_ptr)
7709958426SBrian Feldman {
7809958426SBrian Feldman 	struct pam_response *reply;
7909958426SBrian Feldman 	int count;
8009958426SBrian Feldman 	char buf[1024];
8109958426SBrian Feldman 
8209958426SBrian Feldman 	/* PAM will free this later */
8309958426SBrian Feldman 	reply = malloc(num_msg * sizeof(*reply));
8409958426SBrian Feldman 	if (reply == NULL)
8509958426SBrian Feldman 		return PAM_CONV_ERR;
8609958426SBrian Feldman 
8709958426SBrian Feldman 	for (count = 0; count < num_msg; count++) {
8809958426SBrian Feldman 		switch ((*msg)[count].msg_style) {
8909958426SBrian Feldman 			case PAM_PROMPT_ECHO_ON:
9009958426SBrian Feldman 				if (pamstate == INITIAL_LOGIN) {
9109958426SBrian Feldman 					free(reply);
9209958426SBrian Feldman 					return PAM_CONV_ERR;
9309958426SBrian Feldman 				} else {
9409958426SBrian Feldman 					fputs((*msg)[count].msg, stderr);
9509958426SBrian Feldman 					fgets(buf, sizeof(buf), stdin);
9609958426SBrian Feldman 					reply[count].resp = xstrdup(buf);
9709958426SBrian Feldman 					reply[count].resp_retcode = PAM_SUCCESS;
9809958426SBrian Feldman 					break;
9909958426SBrian Feldman 				}
10009958426SBrian Feldman 			case PAM_PROMPT_ECHO_OFF:
10109958426SBrian Feldman 				if (pamstate == INITIAL_LOGIN) {
10209958426SBrian Feldman 					if (pampasswd == NULL) {
10309958426SBrian Feldman 						free(reply);
10409958426SBrian Feldman 						return PAM_CONV_ERR;
10509958426SBrian Feldman 					}
10609958426SBrian Feldman 					reply[count].resp = xstrdup(pampasswd);
10709958426SBrian Feldman 				} else {
10809958426SBrian Feldman 					reply[count].resp =
10909958426SBrian Feldman 						xstrdup(read_passphrase((*msg)[count].msg, 1));
11009958426SBrian Feldman 				}
11109958426SBrian Feldman 				reply[count].resp_retcode = PAM_SUCCESS;
11209958426SBrian Feldman 				break;
11309958426SBrian Feldman 			case PAM_ERROR_MSG:
11409958426SBrian Feldman 			case PAM_TEXT_INFO:
11509958426SBrian Feldman 				if ((*msg)[count].msg != NULL) {
11609958426SBrian Feldman 					if (pamstate == INITIAL_LOGIN)
11709958426SBrian Feldman 						pam_msg_cat((*msg)[count].msg);
11809958426SBrian Feldman 					else {
11909958426SBrian Feldman 						fputs((*msg)[count].msg, stderr);
12009958426SBrian Feldman 						fputs("\n", stderr);
12109958426SBrian Feldman 					}
12209958426SBrian Feldman 				}
12309958426SBrian Feldman 				reply[count].resp = xstrdup("");
12409958426SBrian Feldman 				reply[count].resp_retcode = PAM_SUCCESS;
12509958426SBrian Feldman 				break;
12609958426SBrian Feldman 			default:
12709958426SBrian Feldman 				free(reply);
12809958426SBrian Feldman 				return PAM_CONV_ERR;
12909958426SBrian Feldman 		}
13009958426SBrian Feldman 	}
13109958426SBrian Feldman 
13209958426SBrian Feldman 	*resp = reply;
13309958426SBrian Feldman 
13409958426SBrian Feldman 	return PAM_SUCCESS;
13509958426SBrian Feldman }
13609958426SBrian Feldman 
13709958426SBrian Feldman /* Called at exit to cleanly shutdown PAM */
13809958426SBrian Feldman void pam_cleanup_proc(void *context)
13909958426SBrian Feldman {
14009958426SBrian Feldman 	int pam_retval;
14109958426SBrian Feldman 
14209958426SBrian Feldman 	if (pamh != NULL)
14309958426SBrian Feldman 	{
14409958426SBrian Feldman 		pam_retval = pam_close_session(pamh, 0);
14509958426SBrian Feldman 		if (pam_retval != PAM_SUCCESS) {
14609958426SBrian Feldman 			log("Cannot close PAM session[%d]: %.200s",
14709958426SBrian Feldman 				pam_retval, PAM_STRERROR(pamh, pam_retval));
14809958426SBrian Feldman 		}
14909958426SBrian Feldman 
15009958426SBrian Feldman 		pam_retval = pam_setcred(pamh, PAM_DELETE_CRED);
15109958426SBrian Feldman 		if (pam_retval != PAM_SUCCESS) {
15209958426SBrian Feldman 			debug("Cannot delete credentials[%d]: %.200s",
15309958426SBrian Feldman 				pam_retval, PAM_STRERROR(pamh, pam_retval));
15409958426SBrian Feldman 		}
15509958426SBrian Feldman 
15609958426SBrian Feldman 		pam_retval = pam_end(pamh, pam_retval);
15709958426SBrian Feldman 		if (pam_retval != PAM_SUCCESS) {
15809958426SBrian Feldman 			log("Cannot release PAM authentication[%d]: %.200s",
15909958426SBrian Feldman 				pam_retval, PAM_STRERROR(pamh, pam_retval));
16009958426SBrian Feldman 		}
16109958426SBrian Feldman 	}
16209958426SBrian Feldman }
16309958426SBrian Feldman 
16409958426SBrian Feldman /* Attempt password authentation using PAM */
16509958426SBrian Feldman int auth_pam_password(struct passwd *pw, const char *password)
16609958426SBrian Feldman {
16709958426SBrian Feldman 	extern ServerOptions options;
16809958426SBrian Feldman 	int pam_retval;
16909958426SBrian Feldman 
17009958426SBrian Feldman 	/* deny if no user. */
17109958426SBrian Feldman 	if (pw == NULL)
17209958426SBrian Feldman 		return 0;
17309958426SBrian Feldman 	if (pw->pw_uid == 0 && options.permit_root_login == 2)
17409958426SBrian Feldman 		return 0;
17509958426SBrian Feldman 	if (*password == '\0' && options.permit_empty_passwd == 0)
17609958426SBrian Feldman 		return 0;
17709958426SBrian Feldman 
17809958426SBrian Feldman 	pampasswd = password;
17909958426SBrian Feldman 
18009958426SBrian Feldman 	pamstate = INITIAL_LOGIN;
18109958426SBrian Feldman 	pam_retval = pam_authenticate(pamh, 0);
18209958426SBrian Feldman 	if (pam_retval == PAM_SUCCESS) {
18309958426SBrian Feldman 		debug("PAM Password authentication accepted for user \"%.100s\"",
18409958426SBrian Feldman 			pw->pw_name);
18509958426SBrian Feldman 		return 1;
18609958426SBrian Feldman 	} else {
18709958426SBrian Feldman 		debug("PAM Password authentication for \"%.100s\" failed[%d]: %s",
18809958426SBrian Feldman 			pw->pw_name, pam_retval, PAM_STRERROR(pamh, pam_retval));
18909958426SBrian Feldman 		return 0;
19009958426SBrian Feldman 	}
19109958426SBrian Feldman }
19209958426SBrian Feldman 
19309958426SBrian Feldman /* Do account management using PAM */
19409958426SBrian Feldman int do_pam_account(char *username, char *remote_user)
19509958426SBrian Feldman {
19609958426SBrian Feldman 	int pam_retval;
19709958426SBrian Feldman 
19809958426SBrian Feldman 	debug("PAM setting rhost to \"%.200s\"", get_canonical_hostname());
19909958426SBrian Feldman 	pam_retval = pam_set_item(pamh, PAM_RHOST,
20009958426SBrian Feldman 		get_canonical_hostname());
20109958426SBrian Feldman 	if (pam_retval != PAM_SUCCESS) {
20209958426SBrian Feldman 		fatal("PAM set rhost failed[%d]: %.200s",
20309958426SBrian Feldman 			pam_retval, PAM_STRERROR(pamh, pam_retval));
20409958426SBrian Feldman 	}
20509958426SBrian Feldman 
20609958426SBrian Feldman 	if (remote_user != NULL) {
20709958426SBrian Feldman 		debug("PAM setting ruser to \"%.200s\"", remote_user);
20809958426SBrian Feldman 		pam_retval = pam_set_item(pamh, PAM_RUSER, remote_user);
20909958426SBrian Feldman 		if (pam_retval != PAM_SUCCESS) {
21009958426SBrian Feldman 			fatal("PAM set ruser failed[%d]: %.200s",
21109958426SBrian Feldman 				pam_retval, PAM_STRERROR(pamh, pam_retval));
21209958426SBrian Feldman 		}
21309958426SBrian Feldman 	}
21409958426SBrian Feldman 
21509958426SBrian Feldman 	pam_retval = pam_acct_mgmt(pamh, 0);
21609958426SBrian Feldman 	switch (pam_retval) {
21709958426SBrian Feldman 		case PAM_SUCCESS:
21809958426SBrian Feldman 			/* This is what we want */
21909958426SBrian Feldman 			break;
22009958426SBrian Feldman 		case PAM_NEW_AUTHTOK_REQD:
22109958426SBrian Feldman 			pam_msg_cat(NEW_AUTHTOK_MSG);
22209958426SBrian Feldman 			/* flag that password change is necessary */
22309958426SBrian Feldman 			password_change_required = 1;
22409958426SBrian Feldman 			break;
22509958426SBrian Feldman 		default:
22609958426SBrian Feldman 			log("PAM rejected by account configuration[%d]: %.200s",
22709958426SBrian Feldman 				pam_retval, PAM_STRERROR(pamh, pam_retval));
22809958426SBrian Feldman 			return(0);
22909958426SBrian Feldman 	}
23009958426SBrian Feldman 
23109958426SBrian Feldman 	return(1);
23209958426SBrian Feldman }
23309958426SBrian Feldman 
23409958426SBrian Feldman /* Do PAM-specific session initialisation */
23509958426SBrian Feldman void do_pam_session(char *username, const char *ttyname)
23609958426SBrian Feldman {
23709958426SBrian Feldman 	int pam_retval;
23809958426SBrian Feldman 
23909958426SBrian Feldman 	if (ttyname != NULL) {
24009958426SBrian Feldman 		debug("PAM setting tty to \"%.200s\"", ttyname);
24109958426SBrian Feldman 		pam_retval = pam_set_item(pamh, PAM_TTY, ttyname);
24209958426SBrian Feldman 		if (pam_retval != PAM_SUCCESS) {
24309958426SBrian Feldman 			fatal("PAM set tty failed[%d]: %.200s",
24409958426SBrian Feldman 				pam_retval, PAM_STRERROR(pamh, pam_retval));
24509958426SBrian Feldman 		}
24609958426SBrian Feldman 	}
24709958426SBrian Feldman 
24809958426SBrian Feldman 	debug("do_pam_session: euid %u, uid %u", geteuid(), getuid());
24909958426SBrian Feldman 	pam_retval = pam_open_session(pamh, 0);
25009958426SBrian Feldman 	if (pam_retval != PAM_SUCCESS) {
25109958426SBrian Feldman 		fatal("PAM session setup failed[%d]: %.200s",
25209958426SBrian Feldman 			pam_retval, PAM_STRERROR(pamh, pam_retval));
25309958426SBrian Feldman 	}
25409958426SBrian Feldman }
25509958426SBrian Feldman 
25609958426SBrian Feldman /* Set PAM credentials */
25709958426SBrian Feldman void do_pam_setcred(void)
25809958426SBrian Feldman {
25909958426SBrian Feldman 	int pam_retval;
26009958426SBrian Feldman 
26109958426SBrian Feldman 	debug("PAM establishing creds");
26209958426SBrian Feldman 	pam_retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
26309958426SBrian Feldman 	if (pam_retval != PAM_SUCCESS) {
26409958426SBrian Feldman 		fatal("PAM setcred failed[%d]: %.200s",
26509958426SBrian Feldman 			pam_retval, PAM_STRERROR(pamh, pam_retval));
26609958426SBrian Feldman 	}
26709958426SBrian Feldman }
26809958426SBrian Feldman 
26909958426SBrian Feldman /* accessor function for file scope static variable */
27009958426SBrian Feldman int pam_password_change_required(void)
27109958426SBrian Feldman {
27209958426SBrian Feldman 	return password_change_required;
27309958426SBrian Feldman }
27409958426SBrian Feldman 
27509958426SBrian Feldman /*
27609958426SBrian Feldman  * Have user change authentication token if pam_acct_mgmt() indicated
27709958426SBrian Feldman  * it was expired.  This needs to be called after an interactive
27809958426SBrian Feldman  * session is established and the user's pty is connected to
27909958426SBrian Feldman  * stdin/stout/stderr.
28009958426SBrian Feldman  */
28109958426SBrian Feldman void do_pam_chauthtok(void)
28209958426SBrian Feldman {
28309958426SBrian Feldman 	int pam_retval;
28409958426SBrian Feldman 
28509958426SBrian Feldman 	if (password_change_required) {
28609958426SBrian Feldman 		pamstate = OTHER;
28709958426SBrian Feldman 		/*
28809958426SBrian Feldman 		 * XXX: should we really loop forever?
28909958426SBrian Feldman 		 */
29009958426SBrian Feldman 		do {
29109958426SBrian Feldman 			pam_retval = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
29209958426SBrian Feldman 			if (pam_retval != PAM_SUCCESS) {
29309958426SBrian Feldman 				log("PAM pam_chauthtok failed[%d]: %.200s",
29409958426SBrian Feldman 					pam_retval, PAM_STRERROR(pamh, pam_retval));
29509958426SBrian Feldman 			}
29609958426SBrian Feldman 		} while (pam_retval != PAM_SUCCESS);
29709958426SBrian Feldman 	}
29809958426SBrian Feldman }
29909958426SBrian Feldman 
30009958426SBrian Feldman /* Cleanly shutdown PAM */
30109958426SBrian Feldman void finish_pam(void)
30209958426SBrian Feldman {
30309958426SBrian Feldman 	pam_cleanup_proc(NULL);
30409958426SBrian Feldman 	fatal_remove_cleanup(&pam_cleanup_proc, NULL);
30509958426SBrian Feldman }
30609958426SBrian Feldman 
30709958426SBrian Feldman /* Start PAM authentication for specified account */
30809958426SBrian Feldman void start_pam(struct passwd *pw)
30909958426SBrian Feldman {
31009958426SBrian Feldman 	int pam_retval;
31109958426SBrian Feldman 
31209958426SBrian Feldman 	debug("Starting up PAM with username \"%.200s\"", pw->pw_name);
31309958426SBrian Feldman 
31409958426SBrian Feldman 	pam_retval = pam_start(SSHD_PAM_SERVICE, pw->pw_name, &conv, &pamh);
31509958426SBrian Feldman 
31609958426SBrian Feldman 	if (pam_retval != PAM_SUCCESS) {
31709958426SBrian Feldman 		fatal("PAM initialisation failed[%d]: %.200s",
31809958426SBrian Feldman 			pam_retval, PAM_STRERROR(pamh, pam_retval));
31909958426SBrian Feldman 	}
32009958426SBrian Feldman 
32109958426SBrian Feldman #ifdef PAM_TTY_KLUDGE
32209958426SBrian Feldman 	/*
32309958426SBrian Feldman 	 * Some PAM modules (e.g. pam_time) require a TTY to operate,
32409958426SBrian Feldman 	 * and will fail in various stupid ways if they don't get one.
32509958426SBrian Feldman 	 * sshd doesn't set the tty until too late in the auth process and may
32609958426SBrian Feldman 	 * not even need one (for tty-less connections)
32709958426SBrian Feldman 	 * Kludge: Set a fake PAM_TTY
32809958426SBrian Feldman 	 */
32909958426SBrian Feldman 	pam_retval = pam_set_item(pamh, PAM_TTY, "ssh");
33009958426SBrian Feldman 	if (pam_retval != PAM_SUCCESS) {
33109958426SBrian Feldman 		fatal("PAM set tty failed[%d]: %.200s",
33209958426SBrian Feldman 			pam_retval, PAM_STRERROR(pamh, pam_retval));
33309958426SBrian Feldman 	}
33409958426SBrian Feldman #endif /* PAM_TTY_KLUDGE */
33509958426SBrian Feldman 
33609958426SBrian Feldman 	fatal_add_cleanup(&pam_cleanup_proc, NULL);
33709958426SBrian Feldman }
33809958426SBrian Feldman 
33909958426SBrian Feldman /* Return list of PAM enviornment strings */
34009958426SBrian Feldman char **fetch_pam_environment(void)
34109958426SBrian Feldman {
34209958426SBrian Feldman #ifdef HAVE_PAM_GETENVLIST
34309958426SBrian Feldman 	return(pam_getenvlist(pamh));
34409958426SBrian Feldman #else /* HAVE_PAM_GETENVLIST */
34509958426SBrian Feldman 	return(NULL);
34609958426SBrian Feldman #endif /* HAVE_PAM_GETENVLIST */
34709958426SBrian Feldman }
34809958426SBrian Feldman 
34909958426SBrian Feldman /* Print any messages that have been generated during authentication */
35009958426SBrian Feldman /* or account checking to stderr */
35109958426SBrian Feldman void print_pam_messages(void)
35209958426SBrian Feldman {
35309958426SBrian Feldman 	if (pam_msg != NULL)
35409958426SBrian Feldman 		fputs(pam_msg, stderr);
35509958426SBrian Feldman }
35609958426SBrian Feldman 
35709958426SBrian Feldman /* Append a message to the PAM message buffer */
35809958426SBrian Feldman void pam_msg_cat(const char *msg)
35909958426SBrian Feldman {
36009958426SBrian Feldman 	char *p;
36109958426SBrian Feldman 	size_t new_msg_len;
36209958426SBrian Feldman 	size_t pam_msg_len;
36309958426SBrian Feldman 
36409958426SBrian Feldman 	new_msg_len = strlen(msg);
36509958426SBrian Feldman 
36609958426SBrian Feldman 	if (pam_msg) {
36709958426SBrian Feldman 		pam_msg_len = strlen(pam_msg);
36809958426SBrian Feldman 		pam_msg = xrealloc(pam_msg, new_msg_len + pam_msg_len + 2);
36909958426SBrian Feldman 		p = pam_msg + pam_msg_len;
37009958426SBrian Feldman 	} else {
37109958426SBrian Feldman 		pam_msg = p = xmalloc(new_msg_len + 2);
37209958426SBrian Feldman 	}
37309958426SBrian Feldman 
37409958426SBrian Feldman 	memcpy(p, msg, new_msg_len);
37509958426SBrian Feldman 	p[new_msg_len] = '\n';
37609958426SBrian Feldman 	p[new_msg_len + 1] = '\0';
37709958426SBrian Feldman }
37809958426SBrian Feldman 
37909958426SBrian Feldman struct inverted_pam_userdata {
38009958426SBrian Feldman     /*
38109958426SBrian Feldman      * Pipe for telling whether we are doing conversation or sending
38209958426SBrian Feldman      * authentication results.
38309958426SBrian Feldman      */
38409958426SBrian Feldman     int statefd[2];
38509958426SBrian Feldman     int challengefd[2];
38609958426SBrian Feldman     int responsefd[2];
38709958426SBrian Feldman 
38809958426SBrian Feldman     /* Whether we have sent off our challenge */
38909958426SBrian Feldman     int state;
39009958426SBrian Feldman };
39109958426SBrian Feldman 
39209958426SBrian Feldman #define STATE_CONV	1
39309958426SBrian Feldman #define STATE_AUTH_OK	2
39409958426SBrian Feldman #define STATE_AUTH_FAIL	3
39509958426SBrian Feldman 
39609958426SBrian Feldman int
39709958426SBrian Feldman ssh_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp,
39809958426SBrian Feldman 	 void *userdata) {
39909958426SBrian Feldman 	int i;
40009958426SBrian Feldman 	FILE *reader;
40109958426SBrian Feldman 	char buf[1024];
40209958426SBrian Feldman 	struct pam_response *reply = NULL;
40309958426SBrian Feldman 	char state_to_write = STATE_CONV; /* One char to write */
40409958426SBrian Feldman 	struct inverted_pam_userdata *ud = userdata;
40509958426SBrian Feldman 	char *response = NULL;
40609958426SBrian Feldman 
40709958426SBrian Feldman 	/* The stdio functions are more convenient for the read half */
40809958426SBrian Feldman 	reader = fdopen(ud->responsefd[0], "rb");
40909958426SBrian Feldman 	if (reader == NULL)
41009958426SBrian Feldman 		goto protocol_failure;
41109958426SBrian Feldman 
41209958426SBrian Feldman 	reply = malloc(num_msg * sizeof(struct pam_response));
41309958426SBrian Feldman 	if (reply == NULL)
41409958426SBrian Feldman 		return PAM_CONV_ERR;
41509958426SBrian Feldman 
41609958426SBrian Feldman 	if (write(ud->statefd[1], &state_to_write, 1) != 1)
41709958426SBrian Feldman 		goto protocol_failure;
41809958426SBrian Feldman 
41909958426SBrian Feldman 	/*
42009958426SBrian Feldman 	 * Re-package our data and send it off to our better half (the actual SSH
42109958426SBrian Feldman 	 * process)
42209958426SBrian Feldman 	 */
42309958426SBrian Feldman 	if (write(ud->challengefd[1], buf,
42409958426SBrian Feldman 		  sprintf(buf, "%d\n", num_msg)) == -1)
42509958426SBrian Feldman 		goto protocol_failure;
42609958426SBrian Feldman 	for (i = 0; i < num_msg; i++) {
42709958426SBrian Feldman 		if (write(ud->challengefd[1], buf,
42809958426SBrian Feldman 			  sprintf(buf, "%d\n", msg[i]->msg_style)) == -1)
42909958426SBrian Feldman 			goto protocol_failure;
43009958426SBrian Feldman 		if (write(ud->challengefd[1], buf,
43109958426SBrian Feldman 			  sprintf(buf, "%d\n", strlen(msg[i]->msg))) == -1)
43209958426SBrian Feldman 			goto protocol_failure;
43309958426SBrian Feldman 		if (write(ud->challengefd[1], msg[i]->msg,
43409958426SBrian Feldman 			  strlen(msg[i]->msg)) == -1)
43509958426SBrian Feldman 			goto protocol_failure;
43609958426SBrian Feldman 	}
43709958426SBrian Feldman 	/*
43809958426SBrian Feldman 	 * Read back responses.  These may not be as nice as we want, as the SSH
43909958426SBrian Feldman 	 * protocol isn't exactly a perfect fit with PAM.
44009958426SBrian Feldman 	 */
44109958426SBrian Feldman 
44209958426SBrian Feldman 	for (i = 0; i < num_msg; i++) {
44309958426SBrian Feldman 		char buf[1024];
44409958426SBrian Feldman 		char *endptr;
44509958426SBrian Feldman 		size_t len;	/* Length of the response */
44609958426SBrian Feldman 
44709958426SBrian Feldman 		switch (msg[i]->msg_style) {
44809958426SBrian Feldman 		case PAM_PROMPT_ECHO_OFF:
44909958426SBrian Feldman 		case PAM_PROMPT_ECHO_ON:
45009958426SBrian Feldman 			if (fgets(buf, sizeof(buf), reader) == NULL)
45109958426SBrian Feldman 				goto protocol_failure;
45209958426SBrian Feldman 			len = (size_t)strtoul(buf, &endptr, 10);
45309958426SBrian Feldman 			/* The length is supposed to stand on a line by itself */
45409958426SBrian Feldman 			if (endptr == NULL || *endptr != '\n')
45509958426SBrian Feldman 				goto protocol_failure;
45609958426SBrian Feldman 			response = malloc(len+1);
45709958426SBrian Feldman 			if (response == NULL)
45809958426SBrian Feldman 				goto protocol_failure;
45909958426SBrian Feldman 			if (fread(response, len, 1, reader) != 1)
46009958426SBrian Feldman 				goto protocol_failure;
46109958426SBrian Feldman 			response[len] = '\0';
46209958426SBrian Feldman 			reply[i].resp = response;
46309958426SBrian Feldman 			response = NULL;
46409958426SBrian Feldman 			break;
46509958426SBrian Feldman 		default:
46609958426SBrian Feldman 			reply[i].resp = NULL;
46709958426SBrian Feldman 			break;
46809958426SBrian Feldman 		}
46909958426SBrian Feldman 	}
47009958426SBrian Feldman 	*resp = reply;
47109958426SBrian Feldman 	return PAM_SUCCESS;
47209958426SBrian Feldman  protocol_failure:
47309958426SBrian Feldman 	free(reply);
47409958426SBrian Feldman 	return PAM_CONV_ERR;
47509958426SBrian Feldman }
47609958426SBrian Feldman 
47709958426SBrian Feldman void
47809958426SBrian Feldman ipam_free_cookie(struct inverted_pam_cookie *cookie) {
47909958426SBrian Feldman 	struct inverted_pam_userdata *ud;
48009958426SBrian Feldman 	int i;
48109958426SBrian Feldman 
48209958426SBrian Feldman 	if (cookie == NULL)
48309958426SBrian Feldman 		return;
48409958426SBrian Feldman 	ud = cookie->userdata;
48509958426SBrian Feldman 	cookie->userdata = NULL;
48609958426SBrian Feldman 	/* Free userdata if allocated */
48709958426SBrian Feldman 	if (ud) {
48809958426SBrian Feldman 		/* Close any opened file descriptors */
48909958426SBrian Feldman 		if (ud->statefd[0] != -1)
49009958426SBrian Feldman 			close(ud->statefd[0]);
49109958426SBrian Feldman 		if (ud->statefd[1] != -1)
49209958426SBrian Feldman 			close(ud->statefd[1]);
49309958426SBrian Feldman 		if (ud->challengefd[0] != -1)
49409958426SBrian Feldman 			close(ud->challengefd[0]);
49509958426SBrian Feldman 		if (ud->challengefd[1] != -1)
49609958426SBrian Feldman 			close(ud->challengefd[1]);
49709958426SBrian Feldman 		if (ud->responsefd[0] != -1)
49809958426SBrian Feldman 			close(ud->responsefd[0]);
49909958426SBrian Feldman 		if (ud->responsefd[1] != -1)
50009958426SBrian Feldman 			close(ud->responsefd[1]);
50109958426SBrian Feldman 		free(ud);
50209958426SBrian Feldman 		ud = NULL;
50309958426SBrian Feldman 	}
50409958426SBrian Feldman 	/* Now free the normal cookie */
50509958426SBrian Feldman 	if (cookie->pid != 0 && cookie->pid != -1) {
50609958426SBrian Feldman 		int status;
50709958426SBrian Feldman 
50809958426SBrian Feldman 		/* XXX Use different signal? */
50909958426SBrian Feldman 		kill(cookie->pid, SIGKILL);
51009958426SBrian Feldman 		waitpid(cookie->pid, &status, 0);
51109958426SBrian Feldman 	}
51209958426SBrian Feldman 	for (i = 0; i < cookie->num_msg; i++) {
51309958426SBrian Feldman 		if (cookie->resp && cookie->resp[i]) {
51409958426SBrian Feldman 			free(cookie->resp[i]->resp);
51509958426SBrian Feldman 			free(cookie->resp[i]);
51609958426SBrian Feldman 		}
51709958426SBrian Feldman 		if (cookie->msg && cookie->msg[i]) {
51809958426SBrian Feldman 			free((void *)cookie->msg[i]->msg);
51909958426SBrian Feldman 			free(cookie->msg[i]);
52009958426SBrian Feldman 		}
52109958426SBrian Feldman 	}
52209958426SBrian Feldman 	free(cookie->msg);
52309958426SBrian Feldman 	free(cookie->resp);
52409958426SBrian Feldman 	free(cookie);
52509958426SBrian Feldman }
52609958426SBrian Feldman 
52709958426SBrian Feldman /*
52809958426SBrian Feldman  * Do first half of PAM authentication - this comes to the point where
52909958426SBrian Feldman  * you get a message to send to the user.
53009958426SBrian Feldman  */
53109958426SBrian Feldman struct inverted_pam_cookie *
53209958426SBrian Feldman ipam_start_auth(const char *service, const char *username) {
53309958426SBrian Feldman 	struct inverted_pam_cookie *cookie;
53409958426SBrian Feldman 	struct inverted_pam_userdata *ud;
53509958426SBrian Feldman 	static struct pam_conv conv = {
53609958426SBrian Feldman 		ssh_conv,
53709958426SBrian Feldman 		NULL
53809958426SBrian Feldman 	};
53909958426SBrian Feldman 
54009958426SBrian Feldman 	cookie = malloc(sizeof(*cookie));
54109958426SBrian Feldman 	if (cookie == NULL)
54209958426SBrian Feldman 		return NULL;
54309958426SBrian Feldman 	cookie->state = 0;
54409958426SBrian Feldman 	/* Set up the cookie so ipam_freecookie can be used on it */
54509958426SBrian Feldman 	cookie->num_msg = 0;
54609958426SBrian Feldman 	cookie->msg = NULL;
54709958426SBrian Feldman 	cookie->resp = NULL;
54809958426SBrian Feldman 	cookie->pid = -1;
54909958426SBrian Feldman 
55009958426SBrian Feldman 	ud = calloc(sizeof(*ud), 1);
55109958426SBrian Feldman 	if (ud == NULL) {
55209958426SBrian Feldman 		free(cookie);
55309958426SBrian Feldman 		return NULL;
55409958426SBrian Feldman 	}
55509958426SBrian Feldman 	cookie->userdata = ud;
55609958426SBrian Feldman 	ud->statefd[0] = ud->statefd[1] = -1;
55709958426SBrian Feldman 	ud->challengefd[0] = ud->challengefd[1] = -1;
55809958426SBrian Feldman 	ud->responsefd[0] = ud->responsefd[1] = -1;
55909958426SBrian Feldman 
56009958426SBrian Feldman 	if (pipe(ud->statefd) != 0) {
56109958426SBrian Feldman 		ud->statefd[0] = ud->statefd[1] = -1;
56209958426SBrian Feldman 		ipam_free_cookie(cookie);
56309958426SBrian Feldman 		return NULL;
56409958426SBrian Feldman 	}
56509958426SBrian Feldman 	if (pipe(ud->challengefd) != 0) {
56609958426SBrian Feldman 		ud->challengefd[0] = ud->challengefd[1] = -1;
56709958426SBrian Feldman 		ipam_free_cookie(cookie);
56809958426SBrian Feldman 		return NULL;
56909958426SBrian Feldman 	}
57009958426SBrian Feldman 	if (pipe(ud->responsefd) != 0) {
57109958426SBrian Feldman 		ud->responsefd[0] = ud->responsefd[1] = -1;
57209958426SBrian Feldman 		ipam_free_cookie(cookie);
57309958426SBrian Feldman 		return NULL;
57409958426SBrian Feldman 	}
57509958426SBrian Feldman 	cookie->pid = fork();
57609958426SBrian Feldman 	if (cookie->pid == -1) {
57709958426SBrian Feldman 		ipam_free_cookie(cookie);
57809958426SBrian Feldman 		return NULL;
57909958426SBrian Feldman 	} else if (cookie->pid != 0) {
58009958426SBrian Feldman 		int num_msgs;	/* Number of messages from PAM */
58109958426SBrian Feldman 		char *endptr;
58209958426SBrian Feldman 		char buf[1024];
58309958426SBrian Feldman 		FILE *reader;
58409958426SBrian Feldman 		size_t num_msg;
58509958426SBrian Feldman 		int i;
58609958426SBrian Feldman 		char state;	/* Which state did the connection just enter? */
58709958426SBrian Feldman 
58809958426SBrian Feldman 		/* We are the parent - wait for a call to the communications
58909958426SBrian Feldman 		   function to turn up, or the challenge to be finished */
59009958426SBrian Feldman 		if (read(ud->statefd[0], &state, 1) != 1) {
59109958426SBrian Feldman 			ipam_free_cookie(cookie);
59209958426SBrian Feldman 			return NULL;
59309958426SBrian Feldman 		}
59409958426SBrian Feldman 		cookie->state = state;
59509958426SBrian Feldman 		switch (state) {
59609958426SBrian Feldman 		case STATE_CONV:
59709958426SBrian Feldman 			/* We are running the conversation function */
59809958426SBrian Feldman 			/* The stdio functions are more convenient for read */
59909958426SBrian Feldman 			reader = fdopen(ud->challengefd[0], "r");
60009958426SBrian Feldman 			if (reader == NULL) {
60109958426SBrian Feldman 				ipam_free_cookie(cookie);
60209958426SBrian Feldman 				return NULL;
60309958426SBrian Feldman 			}
60409958426SBrian Feldman 			if (fgets(buf, 4, reader) == NULL) {
60509958426SBrian Feldman 				fclose(reader);
60609958426SBrian Feldman 				ipam_free_cookie(cookie);
60709958426SBrian Feldman 				return NULL;
60809958426SBrian Feldman 			}
60909958426SBrian Feldman 			num_msg = (size_t)strtoul(buf, &endptr, 10);
61009958426SBrian Feldman 			/* The length is supposed to stand on a line by itself */
61109958426SBrian Feldman 			if (endptr == NULL || *endptr != '\n') {
61209958426SBrian Feldman 				fclose(reader);
61309958426SBrian Feldman 				ipam_free_cookie(cookie);
61409958426SBrian Feldman 				return NULL;
61509958426SBrian Feldman 			}
61609958426SBrian Feldman 			cookie->msg =
61709958426SBrian Feldman 				malloc(sizeof(struct pam_message *) * num_msg);
61809958426SBrian Feldman 			cookie->resp =
61909958426SBrian Feldman 				malloc(sizeof(struct pam_response *) * num_msg);
62009958426SBrian Feldman 			if (cookie->msg == NULL || cookie->resp == NULL) {
62109958426SBrian Feldman 				fclose(reader);
62209958426SBrian Feldman 				ipam_free_cookie(cookie);
62309958426SBrian Feldman 				return NULL;
62409958426SBrian Feldman 			}
62509958426SBrian Feldman 			for (i = 0; i < num_msg; i++) {
62609958426SBrian Feldman 				cookie->msg[i] =
62709958426SBrian Feldman 					malloc(sizeof(struct pam_message));
62809958426SBrian Feldman 				cookie->resp[i] =
62909958426SBrian Feldman 					malloc(sizeof(struct pam_response));
63009958426SBrian Feldman 				if (cookie->msg[i] == NULL ||
63109958426SBrian Feldman 				    cookie->resp[i] == NULL) {
63209958426SBrian Feldman 					for (;;) {
63309958426SBrian Feldman 						free(cookie->msg[i]);
63409958426SBrian Feldman 						free(cookie->resp[i]);
63509958426SBrian Feldman 						if (i == 0)
63609958426SBrian Feldman 							break;
63709958426SBrian Feldman 						i--;
63809958426SBrian Feldman 					}
63909958426SBrian Feldman 					fclose(reader);
64009958426SBrian Feldman 					ipam_free_cookie(cookie);
64109958426SBrian Feldman 					return NULL;
64209958426SBrian Feldman 				}
64309958426SBrian Feldman 				cookie->msg[i]->msg = NULL;
64409958426SBrian Feldman 				cookie->resp[i]->resp = NULL;
64509958426SBrian Feldman 				cookie->resp[i]->resp_retcode = 0;
64609958426SBrian Feldman 			}
64709958426SBrian Feldman 			/* Set up so the above will be freed on failure */
64809958426SBrian Feldman 			cookie->num_msg = num_msg;
64909958426SBrian Feldman 			/*
65009958426SBrian Feldman 			 * We have a an allocated response and message for
65109958426SBrian Feldman 			 * each of the entries in the PAM structure - transfer
65209958426SBrian Feldman 			 * the data sent to the conversation function over.
65309958426SBrian Feldman 			 */
65409958426SBrian Feldman 			for (i = 0; i < num_msg; i++) {
65509958426SBrian Feldman 				size_t len;
65609958426SBrian Feldman 
65709958426SBrian Feldman 				if (fgets(buf, sizeof(buf), reader) == NULL) {
65809958426SBrian Feldman 					fclose(reader);
65909958426SBrian Feldman 					ipam_free_cookie(cookie);
66009958426SBrian Feldman 					return NULL;
66109958426SBrian Feldman 				}
66209958426SBrian Feldman 				cookie->msg[i]->msg_style =
66309958426SBrian Feldman 					(size_t)strtoul(buf, &endptr, 10);
66409958426SBrian Feldman 				if (endptr == NULL || *endptr != '\n') {
66509958426SBrian Feldman 					fclose(reader);
66609958426SBrian Feldman 					ipam_free_cookie(cookie);
66709958426SBrian Feldman 					return NULL;
66809958426SBrian Feldman 				}
66909958426SBrian Feldman 				if (fgets(buf, sizeof(buf), reader) == NULL) {
67009958426SBrian Feldman 					fclose(reader);
67109958426SBrian Feldman 					ipam_free_cookie(cookie);
67209958426SBrian Feldman 					return NULL;
67309958426SBrian Feldman 				}
67409958426SBrian Feldman 				len = (size_t)strtoul(buf, &endptr, 10);
67509958426SBrian Feldman 				if (endptr == NULL || *endptr != '\n') {
67609958426SBrian Feldman 					fclose(reader);
67709958426SBrian Feldman 					ipam_free_cookie(cookie);
67809958426SBrian Feldman 					return NULL;
67909958426SBrian Feldman 				}
68009958426SBrian Feldman 				cookie->msg[i]->msg = malloc(len + 1);
68109958426SBrian Feldman 				if (cookie->msg[i]->msg == NULL) {
68209958426SBrian Feldman 					fclose(reader);
68309958426SBrian Feldman 					ipam_free_cookie(cookie);
68409958426SBrian Feldman 					return NULL;
68509958426SBrian Feldman 				}
68609958426SBrian Feldman 				if (fread((char *)cookie->msg[i]->msg, len, 1, reader) !=
68709958426SBrian Feldman 				    1) {
68809958426SBrian Feldman 					fclose(reader);
68909958426SBrian Feldman 					ipam_free_cookie(cookie);
69009958426SBrian Feldman 					return NULL;
69109958426SBrian Feldman 				}
69209958426SBrian Feldman 				*(char *)&(cookie->msg[i]->msg[len]) = '\0';
69309958426SBrian Feldman 			}
69409958426SBrian Feldman 			break;
69509958426SBrian Feldman 		case STATE_AUTH_OK:
69609958426SBrian Feldman 		case STATE_AUTH_FAIL:
69709958426SBrian Feldman 			break;
69809958426SBrian Feldman 		default:
69909958426SBrian Feldman 			/* Internal failure, somehow */
70009958426SBrian Feldman 			fclose(reader);
70109958426SBrian Feldman 			ipam_free_cookie(cookie);
70209958426SBrian Feldman 			return NULL;
70309958426SBrian Feldman 		}
70409958426SBrian Feldman 		return cookie;
70509958426SBrian Feldman 	} else {
70609958426SBrian Feldman 		/* We are the child */
70709958426SBrian Feldman 		pam_handle_t *pamh=NULL;
70809958426SBrian Feldman 		int retval;
70909958426SBrian Feldman 		char state;
71009958426SBrian Feldman 
71109958426SBrian Feldman 		conv.appdata_ptr = ud;
71209958426SBrian Feldman 		retval = pam_start(service, username, &conv, &pamh);
71309958426SBrian Feldman 		/* Is user really user? */
71409958426SBrian Feldman 		if (retval == PAM_SUCCESS)
71509958426SBrian Feldman 			retval = pam_authenticate(pamh, 0);
71609958426SBrian Feldman 		/* permitted access? */
71709958426SBrian Feldman 		if (retval == PAM_SUCCESS)
71809958426SBrian Feldman 			retval = pam_acct_mgmt(pamh, 0);
71909958426SBrian Feldman 		/* This is where we have been authorized or not. */
72009958426SBrian Feldman 
72109958426SBrian Feldman 		/* Be conservative - flag as auth failure if we can't close */
72209958426SBrian Feldman 		/*
72309958426SBrian Feldman 		 * XXX This is based on example code from Linux-PAM -
72409958426SBrian Feldman 		 * but can it really be correct to pam_end if
72509958426SBrian Feldman 		 * pam_start failed?
72609958426SBrian Feldman 		 */
72709958426SBrian Feldman 		if (pam_end(pamh, retval) != PAM_SUCCESS)
72809958426SBrian Feldman 			retval = PAM_AUTH_ERR;
72909958426SBrian Feldman 
73009958426SBrian Feldman 		/* Message to parent */
73109958426SBrian Feldman 		state = retval == PAM_SUCCESS ? STATE_AUTH_OK : STATE_AUTH_FAIL;
73209958426SBrian Feldman 		if (write(ud->statefd[1], &state, 1) != 1) {
73309958426SBrian Feldman 			_exit(1);
73409958426SBrian Feldman 		}
73509958426SBrian Feldman 		/* FDs will be closed, so further communication will stop */
73609958426SBrian Feldman 		_exit(0);
73709958426SBrian Feldman 	}
73809958426SBrian Feldman }
73909958426SBrian Feldman 
74009958426SBrian Feldman /*
74109958426SBrian Feldman  * Do second half of PAM authentication - cookie should now be filled
74209958426SBrian Feldman  * in with the response to the challenge.
74309958426SBrian Feldman  */
74409958426SBrian Feldman 
74509958426SBrian Feldman int
74609958426SBrian Feldman ipam_complete_auth(struct inverted_pam_cookie *cookie) {
74709958426SBrian Feldman     int i;
74809958426SBrian Feldman     char buf[1024];
74909958426SBrian Feldman     struct inverted_pam_userdata *ud = cookie->userdata;
75009958426SBrian Feldman     char state;
75109958426SBrian Feldman 
75209958426SBrian Feldman     /* Send over our responses */
75309958426SBrian Feldman     for (i = 0; i < cookie->num_msg; i++) {
75409958426SBrian Feldman 	if (cookie->msg[i]->msg_style != PAM_PROMPT_ECHO_ON &&
75509958426SBrian Feldman 	    cookie->msg[i]->msg_style != PAM_PROMPT_ECHO_OFF)
75609958426SBrian Feldman 	    continue;
75709958426SBrian Feldman 	if (write(ud->responsefd[1], buf,
75809958426SBrian Feldman 		  sprintf(buf, "%d\n", strlen(cookie->resp[i]->resp))) == -1) {
75909958426SBrian Feldman 	    ipam_free_cookie(cookie);
76009958426SBrian Feldman 	    return 0;
76109958426SBrian Feldman 	}
76209958426SBrian Feldman 	if (write(ud->responsefd[1], cookie->resp[i]->resp,
76309958426SBrian Feldman 		  strlen(cookie->resp[i]->resp)) == -1) {
76409958426SBrian Feldman 	    ipam_free_cookie(cookie);
76509958426SBrian Feldman 	    return 0;
76609958426SBrian Feldman 	}
76709958426SBrian Feldman     }
76809958426SBrian Feldman     /* Find out what state we are changing to */
76909958426SBrian Feldman     if (read(ud->statefd[0], &state, 1) != 1) {
77009958426SBrian Feldman 	ipam_free_cookie(cookie);
77109958426SBrian Feldman 	return 0;
77209958426SBrian Feldman     }
77309958426SBrian Feldman 
77409958426SBrian Feldman     return state == STATE_AUTH_OK ? 1 : 0;
77509958426SBrian Feldman }
77609958426SBrian Feldman 
77709958426SBrian Feldman #endif /* USE_PAM */
778