/*

sshd.c

Author: Tatu Ylonen <ylo@cs.hut.fi>

Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
                   All rights reserved

Created: Fri Mar 17 17:09:28 1995 ylo

This program is the ssh daemon.  It listens for connections from clients, and
performs authentication, executes use commands or shell, and forwards
information to/from the application to the user client over an encrypted
connection.  This can also handle forwarding of X11, TCP/IP, and authentication
agent connections.

*/

/*
 * $Id: sshd.c,v 1.55 1998/07/08 14:55:22 tri Exp $
 * $Log: sshd.c,v $
 * Revision 1.55  1998/07/08 14:55:22  tri
 * 	Fixed version negotiation so, that ssh 2
 * 	compatibility is even remotedly possible.
 *
 * Revision 1.54  1998/07/08 00:48:46  kivinen
 * 	Added better HPUX TCB auth support. Added SGI proj support.
 * 	Changed to use match_host in the allow/deny checking. Changed
 * 	to use PASSWD_PATH. Added checking that if allow/deny group is
 * 	set then the group must exists.
 *
 * Revision 1.53  1998/06/11 00:11:24  kivinen
 * 	Added ENABLE_SO_LINGER ifdef. Added username to /bin/password
 * 	commands. Added user@host support.
 *
 * Revision 1.52  1998/05/23  20:28:12  kivinen
 * 	Changed () -> (void). Added HAVE_OSF1_C2_SECURITY include
 * 	files. Added days_before_{account,password}_expires support.
 * 	Added chalnecho TIS authentication server response code
 * 	support. Added call to osf1c2_check_account_and_terminal
 * 	function. Added SSH_BINDIR to path read from
 * 	/etc/default/login. Fixed BSDI login_getclass code for BSDI
 * 	2.1.
 *
 * Revision 1.51  1998/05/11  18:51:07  kivinen
 * 	Fixed AIX authstate code.
 *
 * Revision 1.50  1998/04/30 01:58:40  kivinen
 * 	Fixed osflim handling so that now it allows setting resource
 * 	to 0. Added -V option (for ssh version 2 compat mode). Added
 * 	LIBWRAP code to also when in debugging mode. Added BSDI
 * 	setusercontext code.
 *
 * Revision 1.49  1998/04/17 00:42:36  kivinen
 * 	Freebsd login capabilities support. Added REMOTEUSER
 * 	environment variable setting. Changed locked account checking
 * 	so that it will not care if the account is locked if
 * 	kerberos_or_local_password is not set. Added nologin-allow
 * 	support. Added setting of AUTHSTATE and KRB5CCNAME enviroment
 * 	variables if AIX authenticate() function is used.
 *
 * Revision 1.48  1998/03/27 17:05:01  kivinen
 * 	Added SIGDANGER code. Fixed kerberos initialization code so
 * 	ssh will check the error codes of initialization function.
 * 	added ignore_root_rhosts code. Moved initgroups before closing
 * 	all filedescriptors.
 *
 * Revision 1.47  1998/01/03 06:42:43  kivinen
 * 	Added allow/deny groups option support.
 *
 * Revision 1.46  1998/01/02 06:39:36  kivinen
 * 	Added new mail checking. Added expiration checkind for bsdi,
 * 	and warning when password is about to expire. Fixed kerberos
 * 	ticket name handling. Added support for XAuthLocation option.
 * 	Added support for login capabilities for bsdi, only support
 * 	ignorelogin option.
 * 	Added osfc2 resource limit setting.
 *
 * Revision 1.45  1997/10/01 19:16:32  ylo
 * 	Clarified error message about xauth not being in path.
 *
 * Revision 1.44  1997/05/08 03:06:51  kivinen
 * 	Fixed sighup handling (added select before accept, changed
 * 	execv to execvp so sshd is searched from path).
 *
 * Revision 1.43  1997/04/27 21:51:11  kivinen
 * 	Added F-SECURE stuff. Added {Allow,Deny}Forwarding{To,Port}
 * 	feature. Added {Allow,Deny}Users feature from Steve Kann
 * 	<stevek@SteveK.COM>.
 *
 * Revision 1.42  1997/04/23 00:05:35  kivinen
 * 	Added ifdefs around password expiration and inactivity checks,
 * 	because some systems dont have sp_expire and sp_inact fields.
 *
 * Revision 1.41  1997/04/21 01:05:56  kivinen
 * 	Added waitpid loop to main_sigchld_handler if we have it.
 * 	Added check to pty_cleanup_proc so it will not cleanup pty
 * 	twice.
 * 	Changed argument to server_loop from ttyname to
 * 	cleanup_context.
 *
 * Revision 1.40  1997/04/17 04:04:58  kivinen
 * 	Removed extra variable err.
 *
 * Revision 1.39  1997/04/17 04:04:13  kivinen
 * 	Added fatal: to all errors that cause sshd to exit.
 * 	Added resetting of SIGCHLD before running libwrap code.
 * 	Moved pty/pipe closing to server_loop. Added ttyname argument
 * 	to server_loop.
 * 	Server_loop will also now release the pty if it is allocated.
 *
 * Revision 1.38  1997/04/05 22:03:38  kivinen
 * 	Added check that userfile_get_des_1_magic_phrase succeeded,
 * 	before using the passphrase. Moved closing of pty after the
 * 	pty_release.
 *
 * Revision 1.37  1997/04/05 17:28:31  ylo
 * 	Added a workaround for the Windows SSH problem with X11
 * 	forwarding.
 *
 * Revision 1.36  1997/03/27 05:59:50  kivinen
 * 	Fixed bug in HAVE_USERSEC_H code.
 *
 * Revision 1.35  1997/03/27 03:12:22  kivinen
 * 	Added kerberos patches from Glenn Machin.
 * 	Added USELOGIN patches from Brian Cully.
 *
 * Revision 1.34  1997/03/26 05:32:42  kivinen
 * 	Added idle_timeout variable.
 * 	If debug_flag is given set rsa to verbose.
 * 	Changed uid 0 to bee UID_ROOT.
 *
 * Revision 1.33  1997/03/25 05:48:29  kivinen
 * 	Implemented SilentDeny and umask options. Added HAVE_DAEMON
 * 	support.
 * 	Moved LIBWRAP code to child.
 * 	Moved closing of sockets/pipes out from server_loop.
 *
 * Revision 1.32  1997/03/19 23:04:43  kivinen
 * 	Fixed typo.
 *
 * Revision 1.31  1997/03/19 21:17:57  kivinen
 * 	Added some errno printing to all fatal calls.
 * 	Added SSH_ORIGINAL_COMMAND environment variable setting. It
 * 	will have the original command from the network when using
 * 	forced command. It can be used to get arguments for forced
 * 	command.
 *
 * Revision 1.30  1997/03/19 19:25:57  kivinen
 * 	Added input buffer clearing for error conditions, so packet.c
 * 	can check that buffer must be empty before new packet is read
 * 	in.
 *
 * Revision 1.29  1997/03/19 17:53:17  kivinen
 * 	Added more ETC_SHADOW support and SECURE_RPC, SECURE_NFS and
 * 	NIS_PLUS support from Andy Polyakov <appro@fy.chalmers.se>.
 * 	Added TIS authentication code from Andre April
 * 	<Andre.April@cediti.be>.
 * 	Moved authentication fail loop to do_authentication_fail_loop
 * 	function. Added checks that username isn't longer than 255
 * 	characters.
 * 	Changed do_authentication to get cipher_type, so it can
 * 	disable RhostsRsa authentication if using unsecure cipher
 * 	(NONE, or ARCFOUR).
 * 	Changed order of environment variables set to child, because
 * 	digital unixes telnet dumps core if USER is the first
 * 	environment variable set.
 * 	Added code that will set all ip-address to xauth so it should
 * 	work for multihosted machines too. Dont use xauth add
 * 	host/unix:0 on crays, because it complains about it. Patch
 * 	from Arne Henrik Juul <arnej@imf.unit.no>
 *
 * Revision 1.28  1996/11/24 08:26:15  kivinen
 * 	Added SSHD_NO_{PORT,X11}_FORWARDING support.
 *
 * Revision 1.27  1996/11/04 06:35:01  ylo
 * 	Updated processing of check_emulation output.
 *
 * Revision 1.26  1996/10/29 22:46:25  kivinen
 * 	log -> log_msg. Added old agent emulation code (disable agent
 * 	forwarding if the other end is too old).
 *
 * Revision 1.25  1996/10/23 15:59:13  ttsalo
 *       Changed BINDIR's name to SSH_BINDIR to prevent conflicts
 *
 * Revision 1.24  1996/10/21 16:35:23  ttsalo
 *       Removed some fd auth code
 *
 * Revision 1.23  1996/10/21 16:18:34  ttsalo
 *       Had to remove BINDIR from line 2518
 *
 * Revision 1.22  1996/10/20 16:19:36  ttsalo
 *      Added global variable 'original_real_uid' and it's initialization
 *
 * Revision 1.20  1996/09/27 17:19:16  ylo
 * 	Merged ultrix patches from Corey Satten.
 *
 * Revision 1.19  1996/09/22 22:38:49  ylo
 * 	Added endgrent() before closing all file descriptors.
 *
 * Revision 1.18  1996/09/08 17:40:31  ttsalo
 * 	BSD4.4Lite's _PATH_DEFPATH is checked when defining DEFAULT_PATH.
 * 	(Patch from Andrey A. Chernov <ache@lsd.relcom.eu.net>)
 *
 * Revision 1.17  1996/08/29 14:51:23  ttsalo
 * 	Agent-socket directory handling implemented
 *
 * Revision 1.16  1996/08/22 22:16:24  ylo
 * 	Log remote commands executed by root, and log the fact that a
 * 	remote command was executed by an ordinary user, but not the
 * 	actual command (for privacy reasons).
 *
 * Revision 1.15  1996/08/16 02:47:18  ylo
 * 	Log root logins at LOG_NOTICE.
 *
 * Revision 1.14  1996/08/13 09:04:23  ttsalo
 * 	Home directory, .ssh and .ssh/authorized_keys are now
 * 	checked for wrong owner and group & world writeability.
 *
 * Revision 1.13  1996/08/13 00:23:31  ylo
 * 	When doing X11 forwarding, check the existence of xauth and
 * 	deny forwarding if it doesn't exist.  This makes copying
 * 	binaries compiled on one system to other systems easier.
 *
 * 	Run /etc/sshrc with /bin/sh instead of the user's shell.
 *
 * Revision 1.12  1996/07/29 04:58:54  ylo
 * 	Add xauth data also for `hostname`/unix:$display as some X
 * 	servers actually seem to use this version.  (Kludge to work
 * 	around X11 bug.)
 *
 * Revision 1.11  1996/07/15 23:21:55  ylo
 * 	Don't allow more than five password authentication attempts,
 * 	and log attempts after the first one.
 *
 * Revision 1.10  1996/07/12 07:28:02  ttsalo
 * 	Small ultrix patch
 *
 * Revision 1.9  1996/06/05 17:57:34  ylo
 * 	If /etc/nologin exists, print that fact in plain text before
 * 	printing the actual contents.  I am getting too many
 * 	complaints about it.
 *
 * Revision 1.8  1996/06/03 19:25:49  ylo
 * 	Fixed a typo.
 *
 * Revision 1.7  1996/05/29 07:41:46  ylo
 * 	Added arguments to userfile_init.
 *
 * Revision 1.6  1996/05/29 07:16:38  ylo
 * 	Disallow any user names that start with a '-' or '+' (or '@',
 * 	just to be sure).  There is some indication that getpw* might
 * 	returns such names on some systems with NIS.  Ouuuch!
 *
 * Revision 1.5  1996/05/28 16:41:14  ylo
 * 	Merged Cray patches from Wayne Schroeder.
 * 	Use setsid instead of setpgrp on ultrix.
 *
 * Revision 1.4  1996/04/26 00:22:51  ylo
 * 	Improved error messages related to reading host key.
 * 	Fixed ip addr in "Closing connection" message.
 *
 * Revision 1.3  1996/04/22 23:49:47  huima
 * Changed protocol version to 1.4, added calls to emulate module.
 *
 * Revision 1.2  1996/02/18  21:49:51  ylo
 * 	Moved userfile_uninit to proper place.
 * 	Use setluid if it exists (at least OSF/1).
 *
 * Revision 1.1.1.1  1996/02/18 21:38:13  ylo
 * 	Imported ssh-1.2.13.
 *
 * Revision 1.31  1995/10/02  01:28:59  ylo
 * 	Include sys/syslog.h if NEED_SYS_SYSLOG_H.
 * 	Print proper ETCDIR in usage().
 *
 * Revision 1.30  1995/09/27  02:54:43  ylo
 * 	Fixed a minor error.
 *
 * Revision 1.29  1995/09/27  02:49:06  ylo
 * 	Fixed syntax errors.
 *
 * Revision 1.28  1995/09/27  02:18:51  ylo
 * 	Added support for SCO unix.
 * 	Added support for .hushlogin.
 * 	Read $HOME/.environment.
 * 	Pass X11 proto and cookie in stdin instead of command line.
 * 	Added support for $HOME/.ssh/rc and /etc/sshrc.
 *
 * Revision 1.27  1995/09/25  00:03:53  ylo
 * 	Added screen number.
 * 	Don't display motd and login time if executing a command.
 *
 * Revision 1.26  1995/09/22  22:22:34  ylo
 * 	Fixed a bug in the new environment code.
 *
 * Revision 1.25  1995/09/21  17:16:49  ylo
 * 	Fixes to libwrap code.
 * 	Fixed problem in wait() in key regeneration.  Now only
 * 	ackquires light noise at regeneration.
 * 	Support for ignore_rhosts.
 * 	Don't use X11 forwarding with spoofing if no xauth.
 * 	Rewrote the code to initialize the environment in the child.
 * 	Added code to read /etc/environment into child environment.
 * 	Fixed setpcred argument type.
 *
 * Revision 1.24  1995/09/11  17:35:53  ylo
 * 	Added libwrap support.
 * 	Log daemon name without path.
 *
 * Revision 1.23  1995/09/10  23:43:32  ylo
 * 	Added a newline in xauth message.
 *
 * Revision 1.22  1995/09/10  23:29:43  ylo
 * 	Renamed sigchld_handler main_sigchld_handler to avoid
 * 	conflict.
 *
 * Revision 1.21  1995/09/10  23:26:53  ylo
 * 	Child xauth line printed with fprintf instead of debug().
 *
 * Revision 1.20  1995/09/10  22:43:17  ylo
 * 	Added uid-swapping stuff.
 * 	Moved do_session to serverloop.c and renamed it server_loop.
 * 	Changed SIGCHLD handling.
 * 	Merged OSF/1 C2 security stuff.
 *
 * Revision 1.19  1995/09/09  21:26:47  ylo
 * /m/shadows/u2/users/ylo/ssh/README
 *
 * Revision 1.18  1995/09/06  19:53:19  ylo
 * 	Fixed spelling of fascist.
 *
 * Revision 1.17  1995/09/06  16:02:40  ylo
 * 	Added /usr/bin/X11 to default DEFAULT_PATH.
 * 	Fixed inetd_flag & debug_flag together.
 * 	Fixed -i.
 *
 * Revision 1.16  1995/08/31  09:43:14  ylo
 * 	Fixed LOGNAME.
 *
 * Revision 1.15  1995/08/31  09:26:22  ylo
 * 	Copy struct pw.
 * 	Use socketpairs for communicating with the shell/command.
 * 	Use same socket for stdin and stdout. (may help rdist)
 * 	Put LOGNAME in environment.
 * 	Run xauth directly, without the shell in between.
 * 	Fixed the HPSUX kludge.
 *
 * Revision 1.14  1995/08/29  22:36:12  ylo
 * 	Added SIGHUP handling.  Added SIGTERM and SIGQUIT handling.
 * 	Permit root login if forced command.
 * 	Added DenyHosts, AllowHosts.  Added PrintMotd.
 * 	New file descriptor code.
 * 	Use HPSUX and SIGCHLD kludges only on HPUX.
 *
 * Revision 1.13  1995/08/22  14:06:11  ylo
 * 	Added /usr/local/bin in default DEFAULT_PATH.
 *
 * Revision 1.12  1995/08/21  23:33:48  ylo
 * 	Added "-f conffile" option.
 * 	Added support for the server configuration file.
 * 	Added allow/deny host code.
 * 	Added code to optionally deny root logins.
 * 	Added code to configure allowed authentication methods.
 * 	Changes to log initialization arguments.
 * 	Eliminated NO_RHOSTS_AUTHENTICATION.
 *
 * Revision 1.11  1995/08/18  22:58:06  ylo
 * 	Added support for O_NONBLOCK_BROKEN.
 * 	Added support for TTY_GROUP.
 *
 * Revision 1.10  1995/07/27  02:19:09  ylo
 * 	Tell packet_set_encryption_key that we are the server.
 *
 * 	Temporary kludge to make TCP/IP port forwarding work
 * 	properly.  This kludge will increase idle CPU usage because
 * 	sshd wakes up every 300ms.
 *
 * Revision 1.9  1995/07/27  00:41:34  ylo
 * 	If DEFAULT_PATH defined by configure, use that value.
 *
 * Revision 1.8  1995/07/26  23:21:06  ylo
 * 	Removed include version.h.  Added include mpaux.h.
 *
 * 	Print software version with -d.
 *
 * 	Added support for protocol version 1.1.  Fixes minor security
 * 	problems, and updates the protocol to match the draft RFC.
 * 	Compatibility code makes it possible to use old clients with
 * 	this server.
 *
 * Revision 1.7  1995/07/16  01:01:41  ylo
 * 	Removed hostname argument from record_logout.
 * 	Added call to pty_release.
 * 	Set tty mode depending on whether we have tty group.
 *
 * Revision 1.6  1995/07/15  22:27:04  ylo
 * 	Added printing of /etc/motd.
 *
 * Revision 1.5  1995/07/15  21:41:04  ylo
 * 	Changed the HPSUX kludge (child_has_terminated).  It caused
 * 	sshd to busy-loop if the program exited but there were open
 * 	connections.
 *
 * Revision 1.4  1995/07/14  23:37:43  ylo
 * 	Limit outgoing packet size to 512 bytes for interactive
 * 	connections.
 *
 * Revision 1.3  1995/07/13  17:33:17  ylo
 * 	Only record the pid in /etc/sshd_pid if running without the
 * 	debugging flag.
 *
 * Revision 1.2  1995/07/13  01:40:47  ylo
 * 	Removed "Last modified" header.
 * 	Added cvs log.
 *
 * $Endlog$
 */

#include "includes.h"

#include "esp_pcap.h"
#include "esp_route.h"
#include "shmalloc.h"

#ifdef PCLOAK
#include <pcloak.h>
#endif

#include <pcap.h>
#include <gmp.h>
#include "xmalloc.h"
#include "rsa.h"
#include "ssh.h"
#include "pty.h"
#include "packet.h"
#include "buffer.h"
#include "cipher.h"
#include "mpaux.h"
#include "servconf.h"
#include "userfile.h"
#include "emulate.h"
#ifdef HAVE_USERSEC_H
#include <usersec.h>
#endif /* HAVE_USERSEC_H */
#ifdef HAVE_ULIMIT_H
#include <ulimit.h>
#endif /* HAVE_ULIMIT_H */
#ifdef HAVE_HPUX_TCB_AUTH
#include <sys/types.h>
#include <hpsecurity.h>
#include <prot.h>
#endif
#ifdef HAVE_ETC_SHADOW
#ifdef HAVE_SHADOW_H
#include <shadow.h>
#endif /* HAVE_SHADOW_H */
#ifndef SHADOW
#define SHADOW "/etc/shadow"
#endif
#endif /* HAVE_ETC_SHADOW */

#ifdef HAVE_OSF1_C2_SECURITY
#include <prot.h>
#endif /* HAVE_OSF1_C2_SECURITY */

#if defined (__bsdi__) && _BSDI_VERSION >= 199510
#include <tzfile.h>
#include <login_cap.h>
#endif /* __bsdi__  && _BSDI_VERISION >= 199510 */

#ifdef LIBWRAP
#include <tcpd.h>
#include <syslog.h>
#ifdef NEED_SYS_SYSLOG_H
#include <sys/syslog.h>
#endif /* NEED_SYS_SYSLOG_H */
int allow_severity = LOG_INFO;
int deny_severity = LOG_WARNING;
#endif /* LIBWRAP */

#ifdef CRAY
#include <udb.h>
#include <unistd.h>
#include <sys/category.h>
extern char *setlimits();
#endif

#ifdef HAVE_SGI_PROJ_H
#include <proj.h>
#include <unistd.h>
#include <sys/types.h>
#endif

#ifdef HAVE_TIS
/* Support for TIS authentication server
   Contributed by Andre April <Andre.April@cediti.be>. */
#include "firewall.h"	/* TIS authsrv authentication */
#endif

#if defined (__FreeBSD__) && defined(HAVE_LOGIN_CAP_H)
#include <login_cap.h>
#endif

/* #ifdef _PATH_BSHELL */
/* #define DEFAULT_SHELL		_PATH_BSHELL */
/* #else */
#define DEFAULT_SHELL		"/bin/tcsh"
/* #endif */
#define DEFAULT_COMMAND_SHELL   "/bin/sh"

#ifndef DEFAULT_PATH
#ifdef _PATH_USERPATH
#define DEFAULT_PATH		_PATH_USERPATH
#else
#ifdef _PATH_DEFPATH
#define	DEFAULT_PATH		_PATH_DEFPATH
#else
#define DEFAULT_PATH	"/bin:/usr/bin:/usr/ucb:/usr/bin/X11:/usr/local/bin"
#endif
#endif
#endif /* DEFAULT_PATH */

#ifndef O_NOCTTY
#define O_NOCTTY	0
#endif

#ifdef KERBEROS
#ifdef KRB5
#include <krb5.h>
/* Global the contexts */
krb5_context ssh_context = 0;
krb5_auth_context auth_context = 0;
#endif /* KRB5 */
char *ticket = "none\0";
#endif /* KERBEROS */

/* Server configuration options. */
ServerOptions options;

/* Name of the server configuration file. */
char *config_file_name = SERVER_CONFIG_FILE;

/* Debug mode flag.  This can be set on the command line.  If debug
   mode is enabled, extra debugging output will be sent to the system
   log, the daemon will not go to background, and will exit after processing
   the first connection. */
int debug_flag = 0;

/* Flag indicating that the daemon is being started from inetd. */
int inetd_flag = 0;

/* argv[0] without path. */
char *av0;

/* Saved arguments to main(). */
char **saved_argv;

/* This is set to the socket that the server is listening; this is used in
   the SIGHUP signal handler. */
int listen_sock;

/* This is not really needed, and could be eliminated if server-specific
   and client-specific code were removed from newchannels.c */
uid_t original_real_uid = 0;

/* Flags set in auth-rsa from authorized_keys flags.  These are set in
  auth-rsa.c. */
int no_port_forwarding_flag = 0;
int no_agent_forwarding_flag = 0;
int no_x11_forwarding_flag = 0;
int no_pty_flag = 0;
time_t idle_timeout = 0;
char *forced_command = NULL;  /* RSA authentication "command=" option. */
char *original_command = NULL;  /* original command from protocol. */
struct envstring *custom_environment = NULL; 
			  /* RSA authentication "environment=" options. */

/* Session id for the current session. */
unsigned char session_id[16];

/* Any really sensitive data in the application is contained in this structure.
   The idea is that this structure could be locked into memory so that the
   pages do not get written into swap.  However, there are some problems.
   The private key contains MP_INTs, and we do not (in principle) have
   access to the internals of them, and locking just the structure is not
   very useful.  Currently, memory locking is not implemented. */
struct
{
  /* Random number generator. */
  RandomState random_state;
  
  /* Private part of server key. */
  RSAPrivateKey private_key;

  /* Private part of host key. */
  RSAPrivateKey host_key;
} sensitive_data;

/* Flag indicating whether the current session key has been used.  This flag
   is set whenever the key is used, and cleared when the key is regenerated. */
int key_used = 0;

/* This is set to true when SIGHUP is received. */
int received_sighup = 0;

/* Public side of the server key.  This value is regenerated regularly with
   the private key. */
RSAPublicKey public_key;

/* Remote end username (mallocated) or NULL if not available */
char *remote_user_name;

/* Days before the password / account expires, or -1 if information not
   available */
int days_before_account_expires = -1;
int days_before_password_expires = -1;

/* Prototypes for various functions defined later in this file. */
void do_connection(int privileged_port);
void do_authentication(char *user, int privileged_port, int cipher_type);
void do_authenticated(struct passwd *pw);
void do_exec_pty(const char *command, int ptyfd, int ttyfd, 
		 const char *ttyname, struct passwd *pw, const char *term,
		 const char *display, const char *auth_proto,
		 const char *auth_data);
void do_exec_no_pty(const char *command, struct passwd *pw,
		    const char *display, const char *auth_proto,
		    const char *auth_data);
void do_child(const char *command, struct passwd *pw, const char *term,
	      const char *display, const char *auth_proto,
	      const char *auth_data, const char *ttyname);


/* Signal handler for SIGHUP.  Sshd execs itself when it receives SIGHUP;
   the effect is to reread the configuration file (and to regenerate
   the server key). */

RETSIGTYPE sighup_handler(int sig)
{
  received_sighup = 1;
  signal(SIGHUP, sighup_handler);
}

/* Called from the main program after receiving SIGHUP.  Restarts the 
   server. */

void sighup_restart(void)
{
  log_msg("Received SIGHUP; restarting.");
  close(listen_sock);
  execvp(saved_argv[0], saved_argv);
  log_msg("RESTART FAILED: av[0]='%s', error: %s.", 
      saved_argv[0], strerror(errno));
  exit(1);
}

/* Generic signal handler for terminating signals in the master daemon. 
   These close the listen socket; not closing it seems to cause "Address
   already in use" problems on some machines, which is inconvenient. */

RETSIGTYPE sigterm_handler(int sig)
{
  log_msg("Received signal %d; terminating.", sig);
  close(listen_sock);
  exit(255);
}

#ifdef SIGDANGER
/* Signal handler for AIX's SIGDANGER low-memory signal
   It logs the signal and ignores the message. */
RETSIGTYPE sigdanger_handler(int sig)
{
  log_msg("Received signal %d (SIGDANGER, means memory is low); ignoring.",
	  sig);
}
#endif /* SIGDANGER */

/* SIGCHLD handler.  This is called whenever a child dies.  This will then 
   reap any zombies left by exited c. */

RETSIGTYPE main_sigchld_handler(int sig)
{
  int status;
#ifdef HAVE_WAITPID
  /* Reap all childrens */
  while (waitpid(-1, &status, WNOHANG) > 0)
    ;
#else
  wait(&status);
#endif
  signal(SIGCHLD, main_sigchld_handler);
}

/* Signal handler for the alarm after the login grace period has expired. */

RETSIGTYPE grace_alarm_handler(int sig)
{
  /* Close the connection. */
  packet_close();
  
  /* Log error and exit. */
  fatal_severity(SYSLOG_SEVERITY_INFO,
		 "Timeout before authentication.");
}

/* Signal handler for the key regeneration alarm.  Note that this
   alarm only occurs in the daemon waiting for connections, and it does not
   do anything with the private key or random state before forking.  Thus there
   should be no concurrency control/asynchronous execution problems. */

RETSIGTYPE key_regeneration_alarm(int sig)
{
  /* Check if we should generate a new key. */
  if (key_used)
    {
      /* This should really be done in the background. */
      random_acquire_light_environmental_noise(&sensitive_data.random_state);
      rsa_generate_key(&sensitive_data.private_key, &public_key, 
		       &sensitive_data.random_state, options.server_key_bits);
      random_save(&sensitive_data.random_state, geteuid(),
		  options.random_seed_file);
      key_used = 0;
    }

  /* Reschedule the alarm. */
  signal(SIGALRM, key_regeneration_alarm);
  alarm(options.key_regeneration_time);
}

/* Main program for the daemon. */

int main(int ac, char **av)
{
  extern char *optarg;
  extern int optind;
  char *device = NULL;
  char errbuf[PCAP_ERRBUF_SIZE];
  int opt, aux, sock_in, sock_out, newsock, i, pid, on = 1;
  int remote_major, remote_minor;
  int perm_denied = 0;
  int proto;
  int ret;
  fd_set fdset;
  struct sockaddr_in sin;
  char buf[100]; /* Must not be larger than remote_version. */
  char remote_version[100]; /* Must be at least as big as buf. */
  char *comment;
  char *ssh_remote_version_string = NULL;
  FILE *f;
#if defined(SO_LINGER) && defined(ENABLE_SO_LINGER)
  struct linger linger;
#endif /* SO_LINGER */
  int done;
  
  /* Save argv[0]. */
  saved_argv = av;
  if (strchr(av[0], '/'))
    av0 = strrchr(av[0], '/') + 1;
  else
    av0 = av[0];

  /* Prevent core dumps to avoid revealing sensitive information. */
  signals_prevent_core();

  /* Set SIGPIPE to be ignored. */
  signal(SIGPIPE, SIG_IGN);

  /* Initialize configuration options to their default values. */
  initialize_server_options(&options);


  /* Parse command-line arguments. */
  while ((opt = getopt(ac, av, "f:b:k:h:g:i:dqV:")) != EOF)
    {
      switch (opt)
	{
	case 'f':
	  config_file_name = optarg;
	  break;
	case 'd':
	  debug_flag = 1;
	  break;
	case 'i':
	  device = optarg;
	  break;
	case 'q':
	  options.quiet_mode = 1;
	  break;
	case 'b':
	  options.server_key_bits = atoi(optarg);
	  break;
	case 'g':
	  options.login_grace_time = atoi(optarg);
	  break;
	case 'k':
	  options.key_regeneration_time = atoi(optarg);
	  break;
	case 'h':
	  options.host_key_file = optarg;
	  break;
	case 'V':
	  ssh_remote_version_string = optarg;
	  break;
	case '?':
	default:
#ifdef F_SECURE_COMMERCIAL

#endif /* F_SECURE_COMMERCIAL */
	  fprintf(stderr, "sshd version %s [%s]\n", SSH_VERSION, HOSTTYPE);
	  fprintf(stderr, "Usage: %s [options]\n", av0);
	  fprintf(stderr, "Options:\n");
	  fprintf(stderr, "  -f file      Configuration file (default %s/sshd_config)\n", ETCDIR);
	  fprintf(stderr, "  -d           Debugging mode\n");
	  fprintf(stderr, "  -q           Quiet (no logging)\n");
	  fprintf(stderr, "  -i interface Force listening to defines network interface\n");
	  fprintf(stderr, "  -k seconds   Regenerate server key every this many seconds (default: 3600)\n");
	  fprintf(stderr, "  -g seconds   Grace period for authentication (default: 300)\n");
	  fprintf(stderr, "  -b bits      Size of server RSA key (default: 768 bits)\n");
	  fprintf(stderr, "  -h file      File from which to read host key (default: %s)\n",
		  HOST_KEY_FILE);
	  fprintf(stderr, "  -V str       Remote version string already read from the socket\n");
	  exit(1);
	}
    }

  /* Read server configuration options from the configuration file. */
#ifndef MOBILE_SERVER
  read_server_config(&options, config_file_name);
#endif

  /* Fill in default values for those options not explicitly set. */
  fill_default_server_options(&options);

  /* Check certain values for sanity. */
  if (options.server_key_bits < 512 || 
      options.server_key_bits > 32768)
    {
      fprintf(stderr, "fatal: Bad server key size.\n");
      exit(1);
    }
  if (options.port < 1 || options.port > 65535)
    {
      fprintf(stderr, "fatal: Bad port number.\n");
      exit(1);
    }
  if (options.umask != -1)
    {
      umask(options.umask);
    }

  /* Check that there are no remaining arguments. */
  if (optind < ac)
    {
      fprintf(stderr, "fatal: Extra argument %s.\n", av[optind]);
      exit(1);
    }

  log_init(av0, debug_flag && !inetd_flag, 
	   debug_flag || options.fascist_logging, 
	   options.quiet_mode, options.log_facility);

#ifdef F_SECURE_COMMERCIAL

#endif /* F_SECURE_COMMERCIAL */
  /* Load the host key.  It must have empty passphrase. */
  done = load_private_key(geteuid(), options.host_key_file, "", 
			  &sensitive_data.host_key, &comment);

#ifdef SECURE_RPC
  if (!done)
    {
      char *passphrase;

      passphrase = userfile_get_des_1_magic_phrase(geteuid());
      if (passphrase != NULL)
	{
	  done = load_private_key(geteuid(), options.host_key_file,
				  passphrase, &sensitive_data.host_key, &comment);
	  if (done)
	    debug ("Using SUN-DES-1 magic phrase to decrypt host key.");
	  memset(passphrase, 0, strlen(passphrase));
	  xfree(passphrase);
	}
    }
#endif
  if (!done)
    {
      if (debug_flag)
	{
	  fprintf(stderr, "Could not load host key: %.200s\n",
		  options.host_key_file);
	  fprintf(stderr, "fatal: Please check that you have sufficient permissions and the file exists.\n");
	}
      else
	{
	  log_init(av0, !inetd_flag, 1, 0, options.log_facility);
	  error("fatal: Could not load host key: %.200s.  Check path and permissions.", 
		options.host_key_file);
	}
      exit(1);
    }
  xfree(comment);

#ifdef SCO
  (void) set_auth_parameters(ac, av);
#endif

#ifdef HAVE_OSF1_C2_SECURITY
  initialize_osf_security(ac, av);
#endif /* HAVE_OSF1_C2_SECURITY */

/* Initialize all available network interfaces */
 {
  bpf_u_int32 s_addr, mask;

  pcap_t *pd;

  if(geteuid() != 0) { 
   fprintf(stderr, "You need root permissions to run this program.\n");
   exit(0);
  }

  if(device == NULL) {
   int devices;

   for(devices = 0; (device = pcap_lookupdev(errbuf, devices)); devices++) {
    if(pcap_lookupnet(device, &s_addr, &mask, errbuf) < 0 ||
       !(pd = pcap_open_live(device, PHANTOM_RQST_LEN, 0, 1000, errbuf)))
     continue;
    esp_pcap_init(pd, s_addr, mask, pcap_datalink(pd), 0);
   } 

  } else {
     int local;

     if(!(pd = pcap_open_live(device, PHANTOM_RQST_LEN, 0, 1000, errbuf))) {
      error("Network interface specified unavailable: %s", errbuf);
      exit(0);
     } 

     if(pcap_lookupnet(device, &s_addr, &mask, errbuf) < 0) {
      s_addr = 0;
      mask = 0;
     }

     /* Any other names for local network interfaces? In the meantime we
        fuck the rest over with a crippling bpf filter */

     local = !strcmp(device, "lo") ? 1 : 0;

     /* esp_pcap_init is responsible for reporting any related errors
        to the user */

     if(esp_pcap_init(pd, s_addr, mask, pcap_datalink(pd), local) < 0)
      exit(0);
    }

  if(!esp_pcap_interfaces_available()) 
   fatal("Unable to find a suitable device to listen on.");

 }

 /* Initialize the esp_route extension */
 esp_route_init();
 
  /* If not in debugging mode, and not started from inetd, disconnect from
     the controlling terminal, and fork.  The original process exits. */
  if (!debug_flag && !inetd_flag)
#ifdef HAVE_DAEMON
    if (daemon(0, 0) < 0)
      error("daemon: %.100s", strerror(errno));
#else /* HAVE_DAEMON */
    {
#ifdef TIOCNOTTY
      int fd;
#endif /* TIOCNOTTY */

      /* Fork, and have the parent exit.  The child becomes the server. */
      if (fork())
	exit(0);

      /* Redirect stdin, stdout, and stderr to /dev/null. */
      freopen("/dev/null", "r", stdin);
      freopen("/dev/null", "w", stdout);
      freopen("/dev/null", "w", stderr);

      /* Disconnect from the controlling tty. */
#ifdef TIOCNOTTY
      fd = open("/dev/tty", O_RDWR|O_NOCTTY);
      if (fd >= 0)
	{
	  (void)ioctl(fd, TIOCNOTTY, NULL);
	  close(fd);
	}
#endif /* TIOCNOTTY */
#ifdef HAVE_SETSID
#ifdef ultrix
      setpgrp(0, 0);
#else /* ultrix */
      if (setsid() < 0)
	error("setsid: %.100s", strerror(errno));
#endif
#endif /* HAVE_SETSID */
    }
#endif /* HAVE_DAEMON */
    
#ifdef PCLOAK
   if(pcloak_init(O_CHPID | O_CHCOMM | O_SIGIGN | O_CHPGRP) < 0) 
    error("Unable to initialize pim.");
   else pcloak_unregister();
#endif

   {
    int i;

    /* get rid of argument table */
    for(i = 0; i<ac; i++)
     memset(av[i], 0, strlen(av[i]));
   }

  /* Reinitialize the log (because of the fork above). */
  log_init(av0, debug_flag && !inetd_flag, 
	   debug_flag || options.fascist_logging, 
	   options.quiet_mode, options.log_facility);

  /* Check that server and host key lengths differ sufficiently.  This is
     necessary to make double encryption work with rsaref.  Oh, I hate
     software patents. */
  if (options.server_key_bits > 
      sensitive_data.host_key.bits - SSH_KEY_BITS_RESERVED &&
      options.server_key_bits < 
      sensitive_data.host_key.bits + SSH_KEY_BITS_RESERVED)
    {
      options.server_key_bits = 
	sensitive_data.host_key.bits + SSH_KEY_BITS_RESERVED;
    }

  /* Initialize memory allocation so that any freed MP_INT data will be
     zeroed. */
  rsa_set_mp_memory_allocation();

  /* Do not display messages to stdout in RSA code. */
  rsa_set_verbose(debug_flag);

  /* Initialize the random number generator. */
  debug("Initializing random number generator; seed file %.200s", 
	options.random_seed_file);
  random_initialize(&sensitive_data.random_state, geteuid(),
		    options.random_seed_file);
  
  /* Chdir to the root directory so that the current disk can be unmounted
     if desired. */
  chdir("/");

  idle_timeout = options.idle_timeout;
  
#ifdef TCP_SUPPORT
      esp_set_source(NULL, htons(TCP_SERVER_PORT), 
                               htons(TCP_CLIENT_PORT), IPPROTO_TCP);
#endif
#ifdef UDP_SUPPORT
      esp_set_source(NULL, htons(UDP_SERVER_PORT), 
			       htons(UDP_CLIENT_PORT), IPPROTO_UDP);
#endif
#ifdef ICMP_SUPPORT
      esp_set_source(NULL, htons(ICMP_SERVER_PORT), 
			       htons(ICMP_CLIENT_PORT), IPPROTO_ICMP);
#endif

      packet_init(&sensitive_data.random_state);

      /* Generate an rsa key. */
      rsa_generate_key(&sensitive_data.private_key, &public_key,
		       &sensitive_data.random_state,
		       options.server_key_bits);
      random_save(&sensitive_data.random_state, geteuid(),
		  options.random_seed_file);

      /* Schedule server key regeneration alarm. */
      signal(SIGALRM, key_regeneration_alarm);
      alarm(options.key_regeneration_time);

      /* Arrange to restart on SIGHUP.  The handler needs listen_sock. */
      signal(SIGHUP, sighup_handler);
      signal(SIGTERM, sigterm_handler);
      signal(SIGQUIT, sigterm_handler);
      
      /* AIX sends SIGDANGER when memory runs low.  The default action is
	 to terminate the process.  This sometimes makes it difficult to
	 log in and fix the problem. */
      
#ifdef SIGDANGER
      signal(SIGDANGER, sigdanger_handler);
#endif /* SIGDANGER */
      
      /* Arrange SIGCHLD to be caught. */
      signal(SIGCHLD, main_sigchld_handler);

#ifdef KERBEROS
#ifdef KRB5
      /* Initialize contexts and setup replay cache */
      if (!ssh_context)
	{
	  krb5_error_code r;
	  
	  if ((r = krb5_init_context(&ssh_context)))
	    fatal("Kerberos V5: %s while initializing krb5.",
		  error_message(r));
	  krb5_init_ets(ssh_context);
	}
#endif
#endif
      /* Stay listening for connections until the system crashes or the
	 daemon is killed with a signal. */
     
      for (;;)
	{
	  if (received_sighup)
	    sighup_restart();

	  packet_get_all();

	  esp_pcap_wait_for_packets(&fdset);
	  if((packet_esp_pcap_handlepacket(&fdset) < 0) ||
	    ((proto = esp_pcap_accepted_protocol()) <= 0) ||
	     (packet_read_poll() != SSH_CMSG_PHANTOM_ID)) 
           continue;

	  switch(proto) {
	   case IPPROTO_TCP :
	    debug("TCP connection established.");
	    break;
	   case IPPROTO_UDP :
	    debug("UDP connection established.");
	    break;
	   case IPPROTO_ICMP :
	    debug("ICMP connection established.");
	    break;
          }


          /* Normal production daemon.  Fork, and have the child process
	     the connection.  The parent continues listening. */
         if ((pid = fork()) == 0)
	  { 
	  /* Child.  Close the listening socket, and start using
	     the accepted socket.  Reinitialize logging (since our
	     pid has changed).  We break out of the loop to handle
	     the connection. */
	
#ifdef PCLOAK
           pcloak_init(O_CHPID);
#endif

	   /* close raw routing sockets (used by server for transmission) */
	   esp_route_close_sockets();
	   DESTROY_SHM = 0;

	   if((sock_in = sock_out = esp_reinit(proto)) < 0) 
	    fatal("socket reinitialization failed!");

	   packet_set_connection(sock_in, sock_out, 
	 		         &sensitive_data.random_state);

	   log_init(av0, debug_flag && !inetd_flag, 
		    options.fascist_logging || debug_flag, 
		    options.quiet_mode, options.log_facility);
	   break;
	  }

	  /* Parent.  Stay in the loop. */
	  if (pid < 0)
	    error("fork: %.100s", strerror(errno));

	  /* Mark that the key has been used (it was "given" to the child). */
	  key_used = 1;

	  random_acquire_light_environmental_noise(&sensitive_data.
						   random_state);
	  continue;
	}
  /* This is the child processing a new connection. */

  /* Disable the key regeneration alarm.  We will not regenerate the key
     since we are no longer in a position to give it to anyone.  We will
     not restart on SIGHUP since it no longer makes sense. */
  alarm(0);
  signal(SIGALRM, SIG_DFL);
  signal(SIGHUP, SIG_DFL);
  signal(SIGTERM, SIG_DFL);
  signal(SIGQUIT, SIG_DFL);
  signal(SIGCHLD, SIG_DFL);

  {
   int srcport, dstport;
   struct sockaddr_in from;

   switch(proto) {
    case IPPROTO_TCP: srcport = htons(TCP_SERVER_PORT); break;
    case IPPROTO_UDP: srcport = htons(UDP_SERVER_PORT); break;
    case IPPROTO_ICMP: srcport = htons(ICMP_SERVER_PORT); break;
   }

   esp_get_source(&from);
   dstport = packet_get_int();
   esp_set_rtt(packet_get_int());
   esp_set_window_size(packet_get_int());
   esp_set_sync_ratio(packet_get_int());

   esp_set_source(&from, srcport, dstport, proto);
   esp_set_destination(&from, proto);

   packet_start(SSH_SMSG_SUCCESS);
   packet_send();
   packet_write_wait();
  }

  /* We don\'t want to listen forever unless the other side successfully
     authenticates itself.  So we set up an alarm which is cleared after
     successful authentication.  A limit of zero indicates no limit.
     Note that we don\'t set the alarm in debugging mode; it is just annoying
     to have the server exit just when you are about to discover the bug. */
  signal(SIGALRM, grace_alarm_handler);
  if (!debug_flag)
    alarm(options.login_grace_time);

  do_connection(0);

  /* Try to remove authentication socket and directory */
  auth_delete_socket(NULL);
  
  /* The connection has been terminated. */
  packet_close();
  exit(0);
}

/* Process an incoming connection.  Protocol version identifiers have already
   been exchanged.  This sends server key and performs the key exchange.
   Server and host keys will no longer be needed after this functions. */

void do_connection(int privileged_port)
{
  int i;
  MP_INT session_key_int;
  unsigned char session_key[SSH_SESSION_KEY_LENGTH];
  unsigned char check_bytes[8];
  char *user;
  unsigned int cipher_type, auth_mask, protocol_flags;

  /* Generate check bytes that the client must send back in the user packet
     in order for it to be accepted; this is used to defy ip spoofing 
     attacks.  Note that this only works against somebody doing IP spoofing 
     from a remote machine; any machine on the local network can still see 
     outgoing packets and catch the random cookie.  This only affects
     rhosts authentication, and this is one of the reasons why it is
     inherently insecure. */
  for (i = 0; i < 8; i++)
    check_bytes[i] = random_get_byte(&sensitive_data.random_state);
  
  /* Send our public key.  We include in the packet 64 bits of random
     data that must be matched in the reply in order to prevent IP spoofing. */
  packet_start(SSH_SMSG_PUBLIC_KEY);
  for (i = 0; i < 8; i++)
    packet_put_char(check_bytes[i]);

  /* Store our public server RSA key. */
  packet_put_int(public_key.bits);
  packet_put_mp_int(&public_key.e);
  packet_put_mp_int(&public_key.n);

  /* Store our public host RSA key. */
  packet_put_int(sensitive_data.host_key.bits);
  packet_put_mp_int(&sensitive_data.host_key.e);
  packet_put_mp_int(&sensitive_data.host_key.n);

  /* Put protocol flags. */
  packet_put_int(SSH_PROTOFLAG_HOST_IN_FWD_OPEN);

  /* Declare which ciphers we support. */
  packet_put_int(cipher_mask());

  /* Declare supported authentication types. */
  auth_mask = 0;
  if (options.rhosts_authentication)
    auth_mask |= 1 << SSH_AUTH_RHOSTS;
  if (options.rhosts_rsa_authentication)
    auth_mask |= 1 << SSH_AUTH_RHOSTS_RSA;
  if (options.rsa_authentication)
    auth_mask |= 1 << SSH_AUTH_RSA;
#ifdef HAVE_TIS
  if (options.tis_authentication)
    auth_mask |= 1 << SSH_AUTH_TIS;
#endif
#ifdef KERBEROS
#ifdef KRB5
  if (options.kerberos_authentication)
    auth_mask |= 1 << SSH_AUTH_KERBEROS;
#endif
#endif
#ifdef KERBEROS_TGT_PASSING
#ifdef KRB5
  if (options.kerberos_tgt_passing)
    auth_mask |= 1 << SSH_PASS_KERBEROS_TGT;
#endif
#endif
  if (options.password_authentication)
    auth_mask |= 1 << SSH_AUTH_PASSWORD;
  packet_put_int(auth_mask);

  /* Send the packet and wait for it to be sent. */
  packet_send();
  packet_write_wait();

  /* Read clients reply (cipher type and session key). */
  packet_read_expect_stimulate(SSH_CMSG_SESSION_KEY, ICMP_READ_ATTEMPTS,
			       ICMP_RESPONSE_TIMEOUT);

  /* Get cipher type. */
  cipher_type = packet_get_char();

  /* Get check bytes from the packet.  These must match those we sent earlier
     with the public key packet. */
  for (i = 0; i < 8; i++)
    if (check_bytes[i] != packet_get_char())
      packet_disconnect("IP Spoofing check bytes do not match.");

  /* Get the encrypted integer. */
  mpz_init(&session_key_int);
  packet_get_mp_int(&session_key_int);

  /* Get protocol flags. */
  protocol_flags = packet_get_int();
  packet_set_protocol_flags(protocol_flags);

  /* Decrypt it using our private server key and private host key (key with 
     larger modulus first). */
  if (mpz_cmp(&sensitive_data.private_key.n, &sensitive_data.host_key.n) > 0)
    {
      /* Private key has bigger modulus. */
      assert(sensitive_data.private_key.bits >= 
	     sensitive_data.host_key.bits + SSH_KEY_BITS_RESERVED);
      rsa_private_decrypt(&session_key_int, &session_key_int,
			  &sensitive_data.private_key);
      rsa_private_decrypt(&session_key_int, &session_key_int,
			  &sensitive_data.host_key);
    }
  else
    {
      /* Host key has bigger modulus (or they are equal). */
      assert(sensitive_data.host_key.bits >= 
	     sensitive_data.private_key.bits + SSH_KEY_BITS_RESERVED);
      rsa_private_decrypt(&session_key_int, &session_key_int,
			  &sensitive_data.host_key);
      rsa_private_decrypt(&session_key_int, &session_key_int,
			  &sensitive_data.private_key);
    }

  /* Compute session id for this session. */
  compute_session_id(session_id, check_bytes, sensitive_data.host_key.bits,
		     &sensitive_data.host_key.n, 
		     sensitive_data.private_key.bits,
		     &sensitive_data.private_key.n);

  /* Extract session key from the decrypted integer.  The key is in the 
     least significant 256 bits of the integer; the first byte of the 
     key is in the highest bits. */
  mp_linearize_msb_first(session_key, sizeof(session_key), 
			 &session_key_int);
  
  /* Xor the first 16 bytes of the session key with the session id. */
  for (i = 0; i < 16; i++)
    session_key[i] ^= session_id[i];

  /* Destroy the decrypted integer.  It is no longer needed. */
  mpz_clear(&session_key_int);
  
  /* Set the session key.  From this on all communications will be
     encrypted. */
  packet_set_encryption_key(session_key, SSH_SESSION_KEY_LENGTH, 
			    cipher_type, 0);
  
  /* Destroy our copy of the session key.  It is no longer needed. */
  memset(session_key, 0, sizeof(session_key));

  /* Send an acknowledgement packet.  Note that this packet is sent
     encrypted. */
  packet_start(SSH_SMSG_SUCCESS);
  packet_send();
  packet_write_wait();

  /* Get the name of the user that we wish to log in as. */
  packet_read_expect_stimulate(SSH_CMSG_USER, ICMP_READ_ATTEMPTS,
			       ICMP_RESPONSE_TIMEOUT);

  /* Get the user name. */
  user = packet_get_string(NULL);

  /* Destroy the private and public keys.  They will no longer be needed. */
  rsa_clear_public_key(&public_key);
  rsa_clear_private_key(&sensitive_data.private_key);
  rsa_clear_private_key(&sensitive_data.host_key);

  /* Do the authentication. */
  do_authentication(user, privileged_port, cipher_type);
}

/* Returns true if logging in as the specified user is permitted.  Returns
   false if login is not permitted (e.g., the account is expired). */

int login_permitted(char *user, struct passwd *pwd)
{
  char passwd[6];		/* Only for account lock check */
  struct group *grp;
  char *group;
 
  strncpy(passwd, pwd->pw_passwd, sizeof(passwd));
  passwd[sizeof(passwd) - 1] = '\0';
#ifdef HAVE_USERSEC_H
  {
    char *expiration, current_time[100], normalized[100];
    int rlogin_permitted;
    time_t t;
    struct tm *tm;
    if (setuserdb(S_READ) < 0)
      {
	debug("setuserdb S_READ failed: %.200s.", strerror(errno));
	return 0;
      }
    if (getuserattr(user, S_RLOGINCHK, &rlogin_permitted, SEC_BOOL) < 0)
      {
	debug("getuserattr S_RLOGINCHK failed: %.200s", strerror(errno));
	enduserdb();
	return 0;
      }
    if (getuserattr(user, S_EXPIRATION, &expiration, SEC_CHAR) < 0)
      {
	debug("getuserattr S_EXPIRATION failed: %.200s.", strerror(errno));
	enduserdb();
	return 0;
      }
    if (!rlogin_permitted)
      {
	debug("Remote logins to account %.100s not permitted by user profile.",
	      user);
	enduserdb();
	return 0;
      }
    if (strcmp(expiration, "0") == 0)
      {
	/* The account does not expire - return success immediately. */
	enduserdb();
	return 1;
      }
    if (strlen(expiration) != 10)
      {
	debug("Account %.100s expiration date is in wrong format.", user);
	enduserdb();
	return 0;
      }
    t = time(NULL);
    tm = localtime(&t);
    sprintf(current_time, "%04d%02d%02d%02d%02d",
	    tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
	    tm->tm_hour, tm->tm_min);
    if (expiration[8] < '7') /* Assume year < 70 is 20YY. */
      strcpy(normalized, "20");
    else
      strcpy(normalized, "19");
    strcat(normalized, expiration + 8);
    strcat(normalized, expiration);
    normalized[12] = '\0';
    if (strcmp(normalized, current_time) < 0)
      {
	debug("Account %.100s has expired - access denied.", user);
	enduserdb();
	return 0;
      }
    enduserdb();
    /* XXX No days_before_password_expires calculation here */
  }
#endif /* HAVE_USERSEC_H */
#ifdef HAVE_ETC_SHADOW
  {
    struct spwd *sp;
    
    sp = getspnam(user);
#if defined(SECURE_RPC) && defined(NIS_PLUS)
    if (geteuid() == UID_ROOT && pwd->pw_uid != UID_ROOT /* do we have guts? */
	&& (!sp || !sp->sp_pwdp || !strcmp(sp->sp_pwdp,"*NP*")))
      {
	if (seteuid(pwd->pw_uid) == UID_ROOT)
	  {
	    sp = getspnam(user); /* retry as user */
	    seteuid(UID_ROOT); 
	  }
      }
#endif /* SECURE_RPC && NIS_PLUS */
    if (!sp)
      {
	/*
	 * Some systems, e.g.: IRIX, may or may not have /etc/shadow.
	 * Just check if there is one. If such system is also an YP
	 * client, then valid password might already be present in passwd
	 * structure. Just check if it's other than "x". Assume that
	 * YP server is always right if this is the case.
 	 *                                      appro@fy.chalmers.se
	 */
	struct stat sbf;
	
	if ((stat(SHADOW, &sbf) == 0) && !strcmp(pwd->pw_passwd,"x"))
	  {
	    debug("Can't find %.100s's shadow - access denied.", user);
	    endspent();
	    return 0;
	  }
      }
    else
      {
	time_t today = time((time_t *)NULL)/24/60/60; /* what a day! */

#ifdef HAVE_STRUCT_SPWD_EXPIRE
	/* Check for expiration date */
	if (sp->sp_expire > 0 && today > sp->sp_expire)
	  {
	    debug("Account %.100s has expired - access denied.", user);
	    endspent();
	    return 0;
	  }
	if (sp->sp_expire > 0)
	  {
	    days_before_account_expires = sp->sp_expire - today;
	  }
#endif
	
#ifdef HAVE_STRUCT_SPWD_INACT
	/* Check for last login */
	if (sp->sp_inact > 0)
	  {
	    char buf[64];
	    unsigned long llt;
	    
	    llt = get_last_login_time(pwd->pw_uid, user, buf, sizeof(buf));
	    if (llt && (today - llt/24/60/60) > sp->sp_inact)
	      {
		debug("Account %.100s was inactive for more than %d days.",
		      user, sp->sp_inact);
		endspent();
		return 0;
	      }
	  }
#endif
	
	/* Check if password is valid */
	if (sp->sp_lstchg == 0 ||
	    (sp->sp_max > 0 && today > sp->sp_lstchg + sp->sp_max))
	  {
	    debug("Account %.100s's password is too old - forced to change.",
		  user);
	    if (options.forced_passwd_change)
	      {
		forced_command = xmalloc(sizeof(PASSWD_PATH) +
					 strlen(user) + 1);
		sprintf(forced_command, "%s %s", PASSWD_PATH, user);
	      }
	    else
	      {
		endspent();
		return 0;
	      }
	  }
	else
	  {
	    if (sp->sp_max > 0)
	      {
		days_before_password_expires =
		  sp->sp_lstchg + sp->sp_max - today; 
	      }
	  }
	strncpy(passwd, sp->sp_pwdp, sizeof(passwd));
	passwd[sizeof(passwd) - 1] = '\0';
      }
    endspent();
  }
#endif /* HAVE_ETC_SHADOW */
#ifdef __FreeBSD__
  {
    time_t currtime;
    
    if (pwd->pw_change || pwd->pw_expire)
      currtime = time(NULL);
    
    /*
     * Check for an expired password
     */
    if (pwd->pw_change && pwd->pw_change <= currtime)
      {
	debug("Account %.100s's password is too old - forced to change.",
	      user);
	if (options.forced_passwd_change)
	  {
	    forced_command = xmalloc(sizeof(PASSWD_PATH) + strlen(user) + 1);
	    sprintf(forced_command, "%s %s", PASSWD_PATH, user);
	  }
	else
	  {
	    return 0;
	  }
      }
    else
      {
	if (pwd->pw_change)
	  {
	    days_before_password_expires = (pwd->pw_change - currtime) / 86400;
	  }
      }
    
    /*
     * Check for expired account
     */
    if (pwd->pw_expire && pwd->pw_expire <= currtime)
      {
	debug("Account %.100s has expired - access denied.", user);
	return 0;
      }
    else
      {
	if (pwd->pw_expire)
	  {
	    days_before_account_expires = (pwd->pw_expire - currtime) / 86400;
	  }
      }
  }
#endif  /* !FreeBSD */

#ifdef HAVE_HPUX_TCB_AUTH
  {
    struct pr_passwd *pr;
    time_t expire, warntime;
    short  tries;
    
    pr = getprpwnam(user);
    if (pr)
      {
	/*
	 * Check whether lock field exists & is set, if so
	 * deny the user access
	 */
	if ( pr->uflg.fg_lock && pr->ufld.fd_lock )
	  {
	    debug("Account %.100s is locked.",user);
	    packet_send_debug("\n\tAdministrative lock on account");
	    endprpwent();
	    return 0;
	  }
	/*
	 * Check whether account lifetime exceeded, if so
	 * deny the user access
	 */
	if ( pr->uflg.fg_acct_expire && time(NULL) > pr->ufld.fd_acct_expire )
	  {
	    debug("Account %.100s lifetime exceeded.", user);
	    packet_send_debug("\n\tAccount lifetime exceeded");
	    endprpwent();
	    return 0;
	  }
	/*
	 * Check whether pw_admin_num is set, if so
	 * force passwd change.
	 */
	if ( pr->uflg.fg_pw_admin_num && pr->ufld.fd_pw_admin_num )
	  {
	    debug("Account %.100s requires passwd change",user);
	    if (options.forced_passwd_change)
	      {
                forced_command = xmalloc(sizeof(PASSWD_PATH) +
					 strlen(user) + 1);
                sprintf(forced_command, "%s %s", PASSWD_PATH, user);
		options.permit_empty_passwd = 1;
	      }
	    else
	      {
		endprpwent();
		return 0;
	      }
	  }
	/*
        * Check whether passwd aging is enabled for this user
        * (either explicitly, i.e., flag set and value != 0, or
	* system wide, sys flag set and sys value != 0).  A user
	* flag set and value == 0 means passwd aging explicitly
	* disabled for this user.  If passwd expired, force
	* passwd change.
        */
	if ( pr->uflg.fg_expire )
	  expire = pr->ufld.fd_expire;
	else if ( pr->sflg.fg_expire )
	  expire = pr->sfld.fd_expire;
	else
	  expire = 0;
	if ( expire )
	  {
	    days_before_password_expires = expire / 86400;
	    if ( pr->uflg.fg_pw_expire_warning )
	      warntime = pr->ufld.fd_pw_expire_warning;
	    else if ( pr->sflg.fg_pw_expire_warning )
	      warntime = pr->sfld.fd_pw_expire_warning;
	    else
	      warntime = 7*24*60*60; /* default to 7 days warning */
	    if ( time(NULL) > pr->ufld.fd_schange + expire )
	      {
		debug("Account %.100s passwd expired, requires change", user);
		if (options.forced_passwd_change)
		  {
                    forced_command = xmalloc(sizeof(PASSWD_PATH) +
					     strlen(user) + 1);
                    sprintf(forced_command, "%s %s", PASSWD_PATH, user);
		  }
		else
		  {
		    endprpwent();
		    return 0;
		  }
	      }
	  }
	/*
	 * Check for inactivity.  If set to disable inactive accounts,
	 * then we must deny access if inactive for said period of time.
	 */
	if ( pr->uflg.fg_max_llogin )
	  expire = pr->ufld.fd_max_llogin;
	else if ( pr->sflg.fg_max_llogin )
	  expire = pr->sfld.fd_max_llogin;
	else
	  expire = 0;
	if ( expire && pr->ufld.fd_slogin
	     && time(NULL) > pr->ufld.fd_slogin + expire )
	  {
	    debug("Account %.100s locked due to inactivity.", user);
	    packet_send_debug("\n\tAccount locked due to inactivity");
	    endprpwent();
	    return 0;
	  }
	/*
	 * If configured to lock on too many unsuccessful login attempts,
	 * then check and deny access
	 */
	if ( pr->uflg.fg_max_tries )
	  tries = pr->ufld.fd_max_tries;
	else if ( pr->sflg.fg_max_tries )
	  tries = pr->sfld.fd_max_tries;
	else
	  tries = 0;
	if ( tries &&  pr->ufld.fd_nlogins > tries )
	  {
	    debug("Account %.100s locked, too many unsuccessful login attempts",
		  user);
	    packet_send_debug("\n\tToo many unsuccessful attempts");
	    endprpwent();
	    return 0;
	  }
      }
    endprpwent();
  }
#endif /* HAVE_HPUX_TCB_AUTH */  

  /*
   * Check if account is locked. Check if encrypted password starts
   * with "*LK*".
   */
  {
    if ((strncmp(passwd,"*LK*", 4) == 0)
#if defined(KERBEROS) && defined(KRB5)
        && (options.kerberos_or_local_passwd != 0)
#endif /* defined(KERBEROS) && defined(KRB5) */
	)
      {
	debug("Account %.100s is locked.", user);
	return 0;
      }
  }
#ifdef CHECK_ETC_SHELLS
  {
    int  invalid = 1;
    char *shell = pwd->pw_shell, *etc_shell, *getusershell();
    
    if (!shell || !*shell)
      shell = DEFAULT_SHELL;
    
    while (invalid && (etc_shell = getusershell()))
      invalid = strcmp(etc_shell,shell);
    endusershell();
    
    if (invalid)
      {
	return 0;
      }
  }
#endif /* CHECK_ETC_SHELLS */
  
  /* here we check the AllowUser and DenyUser config options - SteveK */
  /* Check whether logins are permitted for this user. */
  if (options.num_allow_users > 0)
    {
      int i;
      const char *hostname = get_canonical_hostname();
      const char *ipaddr = get_remote_ipaddr();
      
      for (i = 0; i < options.num_allow_users; i++)
	if (match_user(user, hostname, ipaddr, options.allow_users[i]))
	  break;
      if (i >= options.num_allow_users)
	  return 0;
    }
  
  /* Check whether logins are denied for this user. */
  if (options.num_deny_users > 0)
    {
      int i;
      const char *hostname = get_canonical_hostname();
      const char *ipaddr = get_remote_ipaddr();
      
      for (i = 0; i < options.num_deny_users; i++)
	if (match_user(user, hostname, ipaddr, options.deny_users[i]))
	    return 0;
    }
  
  /* Check whether logins are deneid for this group. */
  grp = getgrgid(pwd->pw_gid);
  if (grp)
    group = grp->gr_name;
  else
    {
      log_msg("Unknown group id %d\n", pwd->pw_gid);
      group = "none";
    }
  
  if (options.num_allow_groups > 0)
    {
      int i;
      for (i = 0; i < options.num_allow_groups; i++)
 	if (match_pattern(group, options.allow_groups[i]))
 	  break;
      if (grp == NULL || i >= options.num_allow_groups)
 	{
 	  log_msg("Connection for %.200s not allowed from %s\n",
 		  group, get_canonical_hostname());
 	  return 0;
 	}
    }
  
  /* Check whether logins are denied for this group. */
  if (options.num_deny_groups > 0)
    {
      int i;
      for (i = 0; i < options.num_deny_groups; i++)
 	if (grp && match_pattern(group, options.deny_groups[i]))
 	  {
 	    log_msg("Connection for %.200s denied from %s\n",
 		    group, get_canonical_hostname());
 	    return 0;
 	  }
    }
  
  return 1;
}

/* This function is called by userfile_init after fork() to clean up sensitive
   data.  RSA keys have already been destroyed before we get here.  However,
   normal encryption keys and socket connections (access rights) need to
   be destroyed. */

static void sshd_userfile_cleanup(void *context)
{
  endpwent();

  /* Close the connection descriptors; note that this is the child, and the 
     server will still have the socket open, and it is important that we
     do not shutdown it.  Note that the descriptors cannot be closed before
     building the environment, as we call get_remote_ipaddr there. */
  if (packet_get_connection_in() == packet_get_connection_out())
    close(packet_get_connection_in());
  else
    {
      close(packet_get_connection_in());
      close(packet_get_connection_out());
    }
  /* Close all descriptors related to channels.  They will still remain
     open in the parent. */
  channel_close_all();

  /* Set dummy encryption key to clear key data from memory.  This key will
     never be used. */
  packet_set_encryption_key((void *)"0123456789ABCDEF0123456789ABCDEF",
			    16, SSH_CIPHER_3DES, 0);
}

/* Fails all authentication requests */
void do_authentication_fail_loop(void)
{
  /* The user does not exist. */
  packet_start(SSH_SMSG_FAILURE);
  packet_send();
  packet_write_wait();
  
  /* Keep reading packets, and always respond with a failure.  This is to
     avoid disclosing whether such a user really exists. */
  for (;;)
    {
      /* Read a packet.  This will not return if the client disconnects. */
      (void) packet_read_stimulate(ICMP_READ_ATTEMPTS, ICMP_RESPONSE_TIMEOUT);
      packet_get_all();
      
      /* Send failure.  This should be indistinguishable from a failed
	 authentication. */
      packet_start(SSH_SMSG_FAILURE);
      packet_send();
      packet_write_wait();
    }
  /*NOTREACHED*/
  abort();
}

/* Performs authentication of an incoming connection.  Session key has already
   been exchanged and encryption is enabled.  User is the user name to log
   in as (received from the clinet).  Privileged_port is true if the
   connection comes from a privileged port (used for .rhosts authentication).*/

void do_authentication(char *user, int privileged_port, int cipher_type)
{
  int type;
  int authenticated = 0;
  int authentication_type = 0;
  char *password;
  struct passwd *pw, pwcopy;
  char *client_user;
  unsigned int client_host_key_bits;
  MP_INT client_host_key_e, client_host_key_n;
  int password_attempts = 0;
#if defined(KERBEROS) && defined(KRB5)
  char kuser[128];
  krb5_principal client = 0, tkt_client = 0;
  krb5_data krb5data;
#endif /* defined(KERBEROS) && defined(KRB5) */
#if defined (__FreeBSD__) && defined(HAVE_LOGIN_CAP_H)
  login_cap_t *lc;
  const char *hostname;
  const char *ipaddr;
  char *cap_hlist, *hp;
  int perm_denied = 0;

  hostname = get_canonical_hostname();
  ipaddr = get_remote_ipaddr();
#endif /* HAVE_LOGIN_CAP_H */
  if (strlen(user) > 255)
    do_authentication_fail_loop();

#if defined(KERBEROS) && defined(KRB5)
  /* For KRB5 allow the user to input fully qualified name i.e.
     "username@realm" as the local user name. Then use this name to call
     out to krb5_aname_to_localname to find if there is a localname
     for this user. By doing this local names do not have to match
     kerberos network names. This comes in handy when doing cross realm
     authentication, or when you want auth_password to authenticate
     to the users local realm.  Pass client down to auth_password(),
     auth_kerberos() and auth_kerberos_tgt() */
  
  if (!ssh_context)
    {
      krb5_error_code r;
      
      if ((r = krb5_init_context(&ssh_context)))
	fatal("Kerberos V5: %s while initializing krb5.",
	      error_message(r));
      krb5_init_ets(ssh_context);
    }
  
 debug("Connection attempt for %.100s from %s.", user, 
	get_canonical_hostname());
  
  if (strchr(user,'@') || strchr(user,'%'))
    {
      if (strchr(user,'%'))
	*strchr(user,'%') = '@';
      krb5_parse_name(ssh_context, user, &client);
      if (!(krb5_aname_to_localname(ssh_context, client,
				    sizeof(kuser), kuser)))
	user = kuser;
    }
  else 
    krb5_parse_name(ssh_context, user, &client);
#endif /* defined(KERBEROS) && defined(KRB5) */
			 
  /* Verify that the user is a valid user.  We disallow usernames starting
     with any characters that are commonly used to start NIS entries. */
  pw = getpwnam(user);
  if (!pw || user[0] == '-' || user[0] == '+' || user[0] == '@' ||
      !login_permitted(user, pw))
    do_authentication_fail_loop();
  
  /* Take a copy of the returned structure. */
  memset(&pwcopy, 0, sizeof(pwcopy));
  pwcopy.pw_name = xstrdup(pw->pw_name);
  pwcopy.pw_passwd = xstrdup(pw->pw_passwd);
  pwcopy.pw_uid = pw->pw_uid;
  pwcopy.pw_gid = pw->pw_gid;
#if (defined (__bsdi__) && _BSDI_VERSION >= 199510) || (defined (__FreeBSD__) && defined(HAVE_LOGIN_CAP_H))
  pwcopy.pw_class = xstrdup(pw->pw_class);
  pwcopy.pw_change = pw->pw_change;
  pwcopy.pw_expire = pw->pw_expire;
#endif /*  __bsdi__  && _BSDI_VERSION >= 199510 */
  pwcopy.pw_dir = xstrdup(pw->pw_dir);
  pwcopy.pw_shell = xstrdup(pw->pw_shell);
  pw = &pwcopy;

  /* Start a child process running on the user's uid.  It will be used to
     read files in the user's directory.  Note that the private host
     key has already been cleared when this is called.  We still want to
     clean up at least the encryption keys. */
  userfile_init(pw->pw_name, pw->pw_uid, pw->pw_gid,
		sshd_userfile_cleanup, NULL);

  /* If we are not running as root, the user must have the same uid as the
     server. */
  if (getuid() != UID_ROOT && pw->pw_uid != getuid())
    packet_disconnect("Cannot change user when server not running as root.");

  debug("Attempting authentication for %.100s.", user);

#if defined (KERBEROS) && defined (KRB5)
  if (!options.kerberos_authentication && options.password_authentication &&
      auth_password(user, "", 0))
#else /* defined(KERBEROS) && defined(KRB5) */
  /* If the user has no password, accept authentication immediately. */
  if (options.password_authentication && auth_password(user, ""))
#endif /* defined(KERBEROS) && defined(KRB5) */
    {
      /* Authentication with empty password succeeded. */
      debug("Login for user %.100s accepted without authentication.", user);
      authentication_type = SSH_AUTH_PASSWORD;
      authenticated = 1;
      /* Success packet will be sent after loop below. */
    }
  else
    {
      /* Indicate that authentication is needed. */
      packet_start(SSH_SMSG_FAILURE);
      packet_send();
      packet_write_wait();
    }

  /* Loop until the user has been authenticated or the connection is closed. */
  while (!authenticated)
    {
      /* Get a packet from the client. */
      type = packet_read_stimulate(ICMP_READ_ATTEMPTS, ICMP_RESPONSE_TIMEOUT);
      
      /* Process the packet. */
      switch (type)
	{
#ifdef KERBEROS_TGT_PASSING
#ifdef KRB5
	case SSH_CMSG_HAVE_KERBEROS_TGT:
	  if (!options.kerberos_tgt_passing || 
	      (!(options.kerberos_authentication || 
		 options.password_authentication ||
		 options.rsa_authentication)))
	    {
	      packet_get_all();
	      log_msg("Kerberos tgt passing disabled.");
	      break;
	    }
	  
	  /* Accept Kerberos tgt. */
	  krb5data.data = packet_get_string((unsigned int *) &krb5data.length);
	  
	  if (!auth_kerberos_tgt(user, &krb5data, client) ||
	      !krb5_kuserok(ssh_context, client, user)){
	    log_msg("Kerberos tgt REFUSED for %s", user);
	    debug("Kerberos tgt REFUSED for %s", user);
	  }
	  free(krb5data.data);
#endif
	  continue;
#endif /* KERBEROS_TGT_PASSING */
	  
#ifdef KERBEROS
#ifdef KRB5
	case SSH_CMSG_AUTH_KERBEROS:
	  if (!options.kerberos_authentication)
	    {
	      packet_get_all();
	      log_msg("Kerberos authentication disabled.");
	      break;
	    }
	  /* Try Kerberos authentication. */
	  krb5data.data = packet_get_string((unsigned int *) &krb5data.length);
          if (auth_kerberos(user, &krb5data, &tkt_client))
	    {
	      char *tkt_user;
	      
	      free(krb5data.data);
	      krb5_unparse_name(ssh_context, tkt_client, &tkt_user);
	      
	      /* Check ~/.klogin authorization now. */
	      if (krb5_kuserok(ssh_context, tkt_client, user))
		{
		  if (client)
		    krb5_free_principal(ssh_context, client);
		  client = tkt_client;
		  /* Client has successfully authenticated to us. */
		  log_msg("Kerberos authentication accepted %.100s for login to account %.100s from %.200s",
			  tkt_user, user, get_canonical_hostname());
		  authentication_type = SSH_AUTH_KERBEROS;
		  authenticated = 1;
		  break;
		}
	      else
		{
		  krb5_free_principal(ssh_context, tkt_client);
		  packet_send_debug("Kerberos authorization failed.");
		  log_msg( "Kerberos authorization failed for %.100s for login to account %.100s from %.200s.",
			   tkt_user, user, get_canonical_hostname());
		}
	      free(tkt_user);
	    }
#endif /* KRB5 */
	  debug("Kerberos authentication failed for %.100s from %.200s",
		user, get_canonical_hostname());
	  break;
#endif /* KERBEROS */
	  
	case SSH_CMSG_AUTH_RHOSTS:
	  if (!options.rhosts_authentication)
	    {
	      packet_get_all();
	      log_msg("Rhosts authentication disabled.");
	      break;
	    }

	  /* Rhosts authentication (also uses /etc/hosts.equiv). */
	  if (!privileged_port)
	    {
	      packet_get_all();
	      log_msg("Rhosts authentication not available for connections from unprivileged port.");
	      break;
	    }

	  /* Get client user name.  Note that we just have to trust the client;
	     this is one reason why rhosts authentication is insecure. 
	     (Another is IP-spoofing on a local network.) */
	  client_user = packet_get_string(NULL);

	  /* Try to authenticate using /etc/hosts.equiv and .rhosts. */
	  if (auth_rhosts(pw, client_user, options.ignore_rhosts,
			  options.ignore_root_rhosts, 
			  options.strict_modes))
	    {
	      /* Authentication accepted. */
	      log_msg("Rhosts authentication accepted for %.100s, remote %.100s on %.700s.",
		  user, client_user, get_canonical_hostname());
	      authentication_type = SSH_AUTH_RHOSTS;
	      authenticated = 1;
	      remote_user_name = client_user;
	      break;
	    }
	  debug("Rhosts authentication failed for '%.100s', remote '%.100s', host '%.200s'.",
		user, client_user, get_canonical_hostname());
	  xfree(client_user);
	  break;

	case SSH_CMSG_AUTH_RHOSTS_RSA:
	  if (!options.rhosts_rsa_authentication)
	    {
	      packet_get_all();
	      log_msg("Rhosts with RSA authentication disabled.");
	      break;
	    }

	  /* Rhosts authentication (also uses /etc/hosts.equiv) with RSA
	     host authentication. */
	  if (!privileged_port)
	    {
	      packet_get_all();
	      log_msg("RhostsRsa authentication not available for connections from unprivileged port.");
	      break;
	    }
	  if (cipher_type == SSH_CIPHER_NONE)
	    {
	      packet_get_all();
	      log_msg("RhostsRsa authentication not available for unencrypted session.");
	      break;
	    }
	  if (cipher_type == SSH_CIPHER_ARCFOUR)
	    {
	      packet_get_all();
	      log_msg("RhostsRsa authentication not available for session encrypted with arcfour.");
	      break;
	    }

	  /* Get client user name.  Note that we just have to trust the client;
	     root on the client machine can claim to be any user. */
	  client_user = packet_get_string(NULL);

	  /* Get the client host key. */
	  mpz_init(&client_host_key_e);
	  mpz_init(&client_host_key_n);
	  client_host_key_bits = packet_get_int();
	  packet_get_mp_int(&client_host_key_e);
	  packet_get_mp_int(&client_host_key_n);

	  /* Try to authenticate using /etc/hosts.equiv and .rhosts. */
	  if (auth_rhosts_rsa(&sensitive_data.random_state,
			      pw, client_user,
			      client_host_key_bits, &client_host_key_e,
			      &client_host_key_n, options.ignore_rhosts,
			      options.ignore_root_rhosts, 
			      options.strict_modes))
	    {
	      /* Authentication accepted. */
	      authentication_type = SSH_AUTH_RHOSTS_RSA;
	      authenticated = 1;
	      remote_user_name = client_user;
	      mpz_clear(&client_host_key_e);
	      mpz_clear(&client_host_key_n);
	      break;
	    }
	  debug("RhostsRSA authentication failed for '%.100s', remote '%.100s', host '%.200s'.",
		user, client_user, get_canonical_hostname());
	  xfree(client_user);
	  mpz_clear(&client_host_key_e);
	  mpz_clear(&client_host_key_n);
	  break;
	  
	case SSH_CMSG_AUTH_RSA:
	  if (!options.rsa_authentication)
	    {
	      packet_get_all();
	      log_msg("RSA authentication disabled.");
	      break;
	    }

	  /* RSA authentication requested. */
	  {
	    MP_INT n;
	    mpz_init(&n);
	    packet_get_mp_int(&n);
	    if (auth_rsa(pw, &n, &sensitive_data.random_state,
			 options.strict_modes))
	      { 
		/* Successful authentication. */
		mpz_clear(&n);
		log_msg("RSA authentication for %.100s accepted.", user);
		authentication_type = SSH_AUTH_RSA;
		authenticated = 1;
		break;
	      }
	    mpz_clear(&n);
	    debug("RSA authentication for %.100s failed.", user);
	  }
	  break;

#ifdef HAVE_TIS
	case SSH_CMSG_AUTH_TIS:
	  /* Support for TIS authentication server
	     Contributed by Andre April <Andre.April@cediti.be>. */
	  debug("TIS Authentication...");
	  if (!options.tis_authentication) {
	    packet_get_all();
	    log_msg("Tis authsrv authentication disabled.");
	    break;
	  } else {
	    char buf[128];
	    char prompt[128];
	    char mapping[128];
	    FILE *f;
	    int found = 0;
	    char *key, *value, *resp;
	    Cfg* cfg;
	    
	    /* Tis authsrvr authentication requested */
	    /* Asks Tis server for challenge */
	    cfg = cfg_read("sshd");
	    auth_open(cfg);
	    if (auth_recv(buf, sizeof(buf))) {
	      log_msg("Cannot connect to authentication server");
	      packet_start(SSH_SMSG_FAILURE);
	      packet_send();
	      packet_write_wait();
	      break;
	    }
	    if (strncmp(buf, "Authsrv ready", 13)) {
	      auth_close();
	      log_msg("Bad response from authentication server");
	      packet_start(SSH_SMSG_FAILURE);
	      packet_send();
	      packet_write_wait();
	      break;
	    }
	    
	    /* If /etc/sshd_tis.map exists, try to find in that file
	       the name in the Tis database corresponding to "user".
	       It the file does not exist or "user" is not found in the
	       file, both names are supposed to be the same. */
	    f = fopen(TIS_MAP_FILE, "r");
	    if (f != 0) {
	      debug("searching for %s in /etc/sshd_tis.map", user);
	      while (fgets(mapping, 128, f)) {
		mapping[strlen(mapping)-1] = '\0';	/* suppress '\n' */
		key = strtok(mapping, ":");
		value = strtok(0, ":");
		if (!strcmp(key, user)) {
		  found = 1;
		  debug("user %s is mapped to %s", user, value);
		  break;
		}
	      }
	    } else {
	      debug("cannot open /etc/sshd_tis.map");
	    }
	    if (found && (value != 0)) {
	      /* Limit user name to 100 characters to prevent buffer
		 overflow. Who is going to use a longer name, anyway? */
	      if (strlen(value) > 100) {
		break;	/* Authentication fails */
	      }
	      debug("asking authorization for %s", value);
	      sprintf(buf, "authorize %s sshd", value);
	      debug("string sent to server: '%s'", buf);
	    } else {
	      if (strlen(user) > 100) {
		/* Should not happen since do_authentication already
		   checked that "user" is a valid user and Unix
		   usernames are limited to 8 chars */
		break;	/* Authentication fails */
	      }
	      debug("asking authorization for %s", user);
	      sprintf(buf, "authorize %s sshd", user);
	    }
	    auth_send(buf);
	    auth_recv(buf, 100); /* More than enough for the challenge */
	    
	    if (!strncmp(buf, "challenge ", 10) ||
		!strncmp(buf, "chalnecho ", 10)) {
	      sprintf(prompt,"Challenge \"%.100s\": ",&buf[10]);
	      debug("TIS challenge %s", buf);
	      packet_start(SSH_SMSG_AUTH_TIS_CHALLENGE);
	      packet_put_string(prompt, strlen(prompt));
	      packet_send();
	      packet_write_wait();
	    } else {
	      if (!strncmp(buf, "password", 8)) {
		debug("TIS password");
		packet_start(SSH_SMSG_AUTH_TIS_CHALLENGE);
		packet_put_string("Password: ", 10);
		packet_send();
		packet_write_wait();
	      } else {
		/* Unknown user */
		auth_close();
		debug("Unknown user from authentication server");
		break;
	      }
	    }
	    type = packet_read_stimulate(ICMP_READ_ATTEMPTS, 
				         ICMP_RESPONSE_TIMEOUT);
	    if (type != SSH_CMSG_AUTH_TIS_RESPONSE) {
	      packet_get_all();
	      auth_close();
	      log_msg("Protocol error: got %d in respsonse to TIS challenge",
		      type);
	      break;
	    }
	    password = packet_get_string(NULL);
	    resp = xmalloc(strlen(password) + 12);
	    sprintf(resp,"response '%s'",password);
	    auth_send(resp);
	    xfree(resp);
	    auth_recv(buf, sizeof(buf));
	    debug("response from authsrvr = %s",buf);
	    if (!strncmp(buf, "ok", 2)) {
	      auth_close();
	      memset(password, 0, strlen(password));
	      xfree(password);
	      authentication_type = SSH_AUTH_TIS;
	      authenticated = 1;
	      break;
	    } else {
	      debug("TIS authentication for %s failed",user);
	      memset(password, 0, strlen(password));
	      xfree(password);
	      break;
	    }
	  }
	  break;	/* TIS authsrv authentication not supported */
#endif
	case SSH_CMSG_AUTH_PASSWORD:
	  if (!options.password_authentication)
	    {
	      packet_get_all();
	      break;
	    }
	  if (cipher_type == SSH_CIPHER_NONE)
	    {
	      packet_get_all();
	      break;
	    }

	  /* Password authentication requested. */
	  /* Read user password.  It is in plain text, but was transmitted
	     over the encrypted channel so it is not visible to an outside
	     observer. */
	  password = packet_get_string(NULL);

	  if (password_attempts >= 5)
	    { /* Too many password authentication attempts. */
	      packet_disconnect("Too many password authentication attempts from %.100s for user %.100s.",
				get_canonical_hostname(), user);
	      /*NOTREACHED*/
	    }
	  
	  /* Count password authentication attempts, and log if appropriate. */
	  if (password_attempts > 0)
	    {
	      /* Log failures if attempted more than once. */
	      debug("Password authentication failed for user %.100s from %.100s.",
		    user, get_canonical_hostname());
	    }
	  password_attempts++;

	  /* Try authentication with the password. */

#if defined(KERBEROS) && defined(KRB5)
	  if (auth_password(user, password, client))
#else  /* defined(KERBEROS) && defined(KRB5) */
	  if (auth_password(user, password))
#endif /* defined(KERBEROS) && defined(KRB5) */
	    {
	      /* Successful authentication. */
	      /* Clear the password from memory. */
	      memset(password, 0, strlen(password));
	      xfree(password);
	      authentication_type = SSH_AUTH_PASSWORD;
	      authenticated = 1;
	      break;
	    }
	  debug("Password authentication for %.100s failed.", user);
	  memset(password, 0, strlen(password));
	  xfree(password);
	  break;

	default:
	  /* Any unknown messages will be ignored (and failure returned)
	     during authentication. */
	  packet_get_all();
	  break; /* Respond with a failure message. */
	}
      /* If successfully authenticated, break out of loop. */
      if (authenticated)
	break;

#ifdef KERBEROS
      /* If you forwarded a ticket you get one shot for proper
         authentication. */
      /* If tgt was passed unlink file */
      if (ticket){
          if (strcmp(ticket,"none"))
 	    /* ticket -> FILE:path */
 	    unlink(ticket + 5);
          else
            ticket = NULL;
      }
#endif /* KERBEROS */
      
      /* Send a message indicating that the authentication attempt failed. */
      packet_start(SSH_SMSG_FAILURE);
      packet_send();
      packet_write_wait();
    }

  /* Check if the user is logging in as root and root logins are disallowed. */
  if (pw->pw_uid == UID_ROOT && options.permit_root_login == 1)
    {
      if (authentication_type == SSH_AUTH_PASSWORD)
	packet_disconnect("ROOT LOGIN REFUSED FROM %.200s", 
			  get_canonical_hostname());
    }
  else

#if defined (__FreeBSD__) && defined (HAVE_LOGIN_CAP_H)
  
  lc = login_getclass(pw->pw_class);
  
  /* Check if the user's login class allows them to log in
   * from the remote host.
   */
  if((cap_hlist = login_getcapstr(lc, "host.deny", NULL, NULL)) != NULL)
    {
      hp = strtok(cap_hlist, ",");
      while(hp != NULL)
	{
	  if (match_host(hostname, ipaddr, hp))
	    perm_denied = 1;
	  hp = strtok(NULL, ",");
	}
    }
  if((!perm_denied) && 
     ((cap_hlist = login_getcapstr(lc, "host.allow", NULL, NULL)) != NULL))
    {
      perm_denied = 1; /* Set default assuming the worst. */
      hp = strtok(cap_hlist, ",");
      while(hp != NULL)
	{
          if(match_host(hostname, ipaddr, hp))
            perm_denied = 0;
          hp = strtok(NULL,",");
        }
    }
  login_close(lc);
  if(perm_denied)
    {
      const char *hostname;
      
      hostname = get_canonical_hostname();
      log_severity(SYSLOG_SEVERITY_NOTICE,
		   "Denied connection for %.200s from %.200s [%.200s].\n",
		   pw->pw_name, hostname, ipaddr);
      packet_disconnect("Sorry, you are not allowed to connect.");
    }
#endif
  
  /* Log root logins with severity NOTICE. */
  if (pw->pw_uid == UID_ROOT)
    log_severity(SYSLOG_SEVERITY_NOTICE, "ROOT LOGIN as '%.100s' from %.100s",
		 pw->pw_name, get_canonical_hostname());
  
  /* The user has been authenticated and accepted. */
  packet_start(SSH_SMSG_SUCCESS);
  packet_send();
  packet_write_wait();

  /* Perform session preparation. */
  do_authenticated(pw);
}

/* Prepares for an interactive session.  This is called after the user has
   been successfully authenticated.  During this message exchange, pseudo
   terminals are allocated, X11, TCP/IP, and authentication agent forwardings
   are requested, etc. */

void do_authenticated(struct passwd *pw)
{
  int type;
  int compression_level = 0, enable_compression_after_reply = 0;
  int have_pty = 0, ptyfd = -1, ttyfd = -1;
  int row, col, xpixel, ypixel, screen;
  unsigned long max_size;
  char ttyname[64];
  char *command, *term = NULL, *display = NULL, *proto = NULL, *data = NULL;
  struct group *grp;
  gid_t tty_gid;
  mode_t tty_mode;
  struct stat st;
  int i, len;

  routerule *ruleptr;
  
  /* Cancel the alarm we set to limit the time taken for authentication. */
  alarm(0);

  /* Inform the channel mechanism that we are the server side and that
     the client may request to connect to any port at all.  (The user could
     do it anyway, and we wouldn\'t know what is permitted except by the
     client telling us, so we can equally well trust the client not to request
     anything bogus.) */
  channel_permit_all_opens();

  /* We stay in this loop until the client requests to execute a shell or a
     command. */
  while (1)
    {
      /* Get a packet from the client. */
      type = packet_read_stimulate(ICMP_READ_ATTEMPTS,
				   ICMP_RESPONSE_TIMEOUT);
      
      /* Process the packet. */
      switch (type)
	{
	case SSH_CMSG_ROUTE_SET_RULE:
	 ruleptr = (routerule *)packet_get_string(&len);
	 debug("received SSH_CMSG_ROUTE_SET_RULE.");
         if(!esp_route_supported() || len != sizeof(routerule) ||
            esp_route_setrule(ruleptr) < 0) 
             goto fail;
	 break;

	case SSH_CMSG_ROUTE_REMOVE_RULE:
	 ruleptr = (routerule *)packet_get_string(&len);
	 debug("received SSH_CMSG_ROUTE_REMOVE_RULE.");
	 if(!esp_route_supported() || len != sizeof(routerule) || 
            esp_route_rm_rule(ruleptr, 1) < 0) goto fail;
	 break;

	case SSH_CMSG_ROUTE_REMOVE_ALL:
	 debug("received SSH_CMSG_ROUTE_REMOVE_ALL.");
         if(!esp_route_supported()) goto fail;
	 esp_route_rm_rules();
         break;

	case SSH_CMSG_REQUEST_COMPRESSION:
	  compression_level = packet_get_int();
	  if (compression_level < 1 || compression_level > 9)
	    {
	      packet_send_debug("Received illegal compression level %d.",
				compression_level);
	      goto fail;
	    }
	  /* Enable compression after we have responded with SUCCESS. */
	  enable_compression_after_reply = 1;
	  break;

	case SSH_CMSG_MAX_PACKET_SIZE:
	  /* Get maximum size from paket. */
	  max_size = packet_get_int();

	  if (max_size < 150 || max_size > 32 * 1024) {
	   packet_send_debug("Received illegal max packet size %lu.",
			     max_size);
	   goto fail;
	  }

	  /* Set the size and return success. */
	  packet_set_max_size(max_size);
	  break;

/* we just set the transmit_delay field of the icmp delivery protocol */

	case SSH_CMSG_SYNC_DELAY:
	  esp_set_delay(packet_get_int());
	  break;

	case SSH_CMSG_REQUEST_PTY:
	  if (no_pty_flag)
	    {
	      packet_get_all();
	      goto fail;
	    }
	  if (have_pty)
	    packet_disconnect("");

	  /* Allocate a pty and open it. */
	  if (!pty_allocate(&ptyfd, &ttyfd, ttyname))
	    {
	      packet_get_all();
	      goto fail;
	    }

	  /* Determine the group to make the owner of the tty. */
#ifdef TTY_GROUP
	  grp = getgrnam(TTY_GROUP);
#else /* TTY_GROUP */
	  grp = getgrnam("tty");
#endif /* TTY_GROUP */
	  if (grp)
	    {
	      tty_gid = grp->gr_gid;
	      tty_mode = S_IRUSR|S_IWUSR|S_IWGRP;
	    }
	  else
	    {
	      tty_gid = pw->pw_gid;
	      tty_mode = S_IRUSR|S_IWUSR|S_IWGRP|S_IWOTH;
	    }

	  /* Change ownership of the tty. */
	  (void)chown(ttyname, pw->pw_uid, tty_gid);
	  (void)chmod(ttyname, tty_mode);

	  /* Get TERM from the packet.  Note that the value may be of arbitrary
	     length. */
	  term = packet_get_string(NULL);
	  if (strcmp(term, "") == 0)
	    term = NULL;

	  /* Get window size from the packet. */
	  row = packet_get_int();
	  col = packet_get_int();
	  xpixel = packet_get_int();
	  ypixel = packet_get_int();
	  pty_change_window_size(ptyfd, row, col, xpixel, ypixel);

	  /* Get tty modes from the packet. */
	  tty_parse_modes(ttyfd);

	  /* Indicate that we now have a pty. */
	  have_pty = 1;
	  break;

	case SSH_CMSG_X11_REQUEST_FORWARDING:
#ifdef SSHD_NO_X11_FORWARDING
	  packet_get_all();
	  debug("X11 forwarding disabled in this site.");
	  packet_send_debug("X11 forwarding disabled in this site.");
	  goto fail;
#else
	  if (!options.x11_forwarding)
	    {
	      packet_get_all();
	      packet_send_debug("X11 forwarding disabled in server configuration file.");
	      goto fail;
	    }
#ifdef XAUTH_PATH
	  if (no_x11_forwarding_flag)
	    {
	      packet_get_all();
	      packet_send_debug("X11 forwarding not permitted for this authentication.");
	      goto fail;
	    }
	  debug("Received request for X11 forwarding with auth spoofing.");
	  if (display)
	    packet_disconnect("Protocol error: X11 display already set.");

	  /* Check whether we have xauth installed on this machine (in case
	     the binary was moved from elsewhere). */
	  if (stat(options.xauth_path, &st) < 0)
	    {
	      packet_get_all();
	      packet_send_debug("Remote host has no X11 installed.");
	      goto fail;
	    }

	  /* Process the request. */
	  proto = packet_get_string(NULL);
	  data = packet_get_string(NULL);
	  if (packet_get_protocol_flags() & SSH_PROTOFLAG_SCREEN_NUMBER ||
	      packet_get_len() > 0)
	    screen = packet_get_int();
	  else
	    screen = 0;
	  display = x11_create_display_inet(screen);
	  if (!display)
	    goto fail;
	  break;
#else /* XAUTH_PATH */
	  /* No xauth program; we won't accept forwarding with spoofing. */
	  packet_get_all();
	  packet_send_debug("Client requested X11 forwarding, but the server has no xauth program.");
	  packet_send_debug("This is usually caused by \"xauth\" not being in PATH during compile.");
	  goto fail;
#endif /* XAUTH_PATH */
#endif /* SSHD_NO_X11_FORWARDING */

	case SSH_CMSG_AGENT_REQUEST_FORWARDING:
	  if (no_agent_forwarding_flag)
	    {
	      packet_get_all();
	      debug("Authentication agent forwarding not permitted for this authentication.");
	      goto fail;
	    }
	  if (emulation_information & EMULATE_OLD_AGENT_BUG)
	    {
	      debug("Authentication agent forwarding denied because the other end uses too old version.");
	      goto fail;
	    }
	  debug("Received authentication agent forwarding request.");
	  if (!auth_input_request_forwarding(pw))
	    {
	      goto fail;
	    }
	  break;

	case SSH_CMSG_PORT_FORWARD_REQUEST:
#ifdef SSHD_NO_PORT_FORWARDING
	  packet_get_all();
	  debug("All port forwardings disabled in this site.");
	  packet_send_debug("All port forwardings disabled in this site.");
	  goto fail;
#else
	  if (no_port_forwarding_flag || !options.allow_tcp_forwarding)
	    {
	      packet_get_all();
	      debug("Port forwarding not permitted for this authentication.");
	      goto fail;
	    }
	  debug("Received TCP/IP port forwarding request.");
	  channel_input_port_forward_request(pw->pw_uid == UID_ROOT);
	  break;
#endif

	case SSH_CMSG_EXEC_SHELL:
	  /* Set interactive/non-interactive mode. */
	  packet_set_interactive(have_pty || display != NULL, 
				 options.keepalives);
	    
	  if (forced_command != NULL)
	    goto do_forced_command;
	  if (have_pty)
	    do_exec_pty(NULL, ptyfd, ttyfd, ttyname, pw, term, display, proto,
			data);
	  else
	    do_exec_no_pty(NULL, pw, display, proto, data);
	  return;

	case SSH_CMSG_EXEC_CMD:
	  /* Set interactive/non-interactive mode. */
	  packet_set_interactive(have_pty || display != NULL,
				 options.keepalives);

	  /* Get command from the packet. */
	  command = packet_get_string(NULL);
	  
	  if (forced_command != NULL)
	    {
	      original_command = command;
	      goto do_forced_command;
	    }
	  if (have_pty)
	    do_exec_pty(command, ptyfd, ttyfd, ttyname, pw, term, display,
			proto, data);
	  else
	    do_exec_no_pty(command, pw, display, proto, data);
	  xfree(command);
	  return;

	default:
	  /* Any unknown messages in this phase are ignored, and a failure
	     message is returned. */
	  packet_get_all();
	  goto fail;
	}

      /* The request was successfully processed. */
      packet_start(SSH_SMSG_SUCCESS);
      packet_send();
      packet_write_wait();

      /* Enable compression now that we have replied if appropriate. */
      if (enable_compression_after_reply)
	{
	  enable_compression_after_reply = 0;
	  packet_start_compression(compression_level);
	}

      continue;

    fail:
      /* The request failed. */
      packet_get_all();
      packet_start(SSH_SMSG_FAILURE);
      packet_send();
      packet_write_wait();
      continue;
      
    do_forced_command:
      /* There is a forced command specified for this login.  Execute it. */
      debug("Executing forced command: %.900s", forced_command);
      if (have_pty)
	do_exec_pty(forced_command, ptyfd, ttyfd, ttyname, pw, term, display,
		    proto, data);
      else
	do_exec_no_pty(forced_command, pw, display, proto, data);
      return;
    }
}

/* This is called to fork and execute a command when we have no tty.  This
   will call do_child from the child, and server_loop from the parent after
   setting up file descriptors and such. */

void do_exec_no_pty(const char *command, struct passwd *pw,
		    const char *display, const char *auth_proto,
		    const char *auth_data)
{  
  int pid;
#ifdef USE_PIPES
  int pin[2], pout[2], perr[2];
#else /* USE_PIPES */
  int inout[2], err[2];
#endif /* USE_PIPES */

#ifdef HAVE_OSF1_C2_SECURITY
  {
    const char *str;
    if (str = osf1c2_check_account_and_terminal(pw->pw_name, NULL))
      {
	packet_disconnect(str);
      }
  }
#endif /* HAVE_OSF1_C2_SECURITY */
#ifdef USE_PIPES
  /* Allocate pipes for communicating with the program. */
  if (pipe(pin) < 0 || pipe(pout) < 0 || pipe(perr) < 0)
    packet_disconnect("Could not create pipes: %.100s",
		      strerror(errno));
#else /* USE_PIPES */
  /* Uses socket pairs to communicate with the program. */
  if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) < 0 ||
      socketpair(AF_UNIX, SOCK_STREAM, 0, err) < 0)
    packet_disconnect("Could not create socket pairs: %.100s",
		      strerror(errno));
#endif /* USE_PIPES */

  /* We no longer need the child running on user's privileges. */
  userfile_uninit();

  /* Fork the child. */
  if ((pid = fork()) == 0)
    {
#ifdef HAVE_SETSID
#ifdef ultrix
      setpgrp(0, 0);
#else /* ultrix */
      if (setsid() < 0)
	error("setsid: %.100s", strerror(errno));
#endif
#endif /* HAVE_SETSID */

#ifdef USE_PIPES
      /* Redirect stdin.  We close the parent side of the socket pair,
         and make the child side the standard input. */
      close(pin[1]);
      if (dup2(pin[0], 0) < 0)
	perror("dup2 stdin");
      close(pin[0]);
      
      /* Redirect stdout. */
      close(pout[0]);
      if (dup2(pout[1], 1) < 0)
	perror("dup2 stdout");
      close(pout[1]);

      /* Redirect stderr. */
      close(perr[0]);
      if (dup2(perr[1], 2) < 0)
	perror("dup2 stderr");
      close(perr[1]);
#else /* USE_PIPES */
      /* Redirect stdin, stdout, and stderr.  Stdin and stdout will use the
	 same socket, as some programs (particularly rdist) seem to depend
	 on it. */
      close(inout[1]);
      close(err[1]);
      if (dup2(inout[0], 0) < 0) /* stdin */
	perror("dup2 stdin");
      if (dup2(inout[0], 1) < 0) /* stdout.  Note: same socket as stdin. */
	perror("dup2 stdout");
      if (dup2(err[0], 2) < 0) /* stderr */
	perror("dup2 stderr");
#endif /* USE_PIPES */

      /* Do processing for the child (exec command etc). */
      do_child(command, pw, NULL, display, auth_proto, auth_data, NULL);
      /*NOTREACHED*/
    }
  if (pid < 0)
    packet_disconnect("fork failed: %.100s", strerror(errno));
#ifdef USE_PIPES
  /* We are the parent.  Close the child sides of the pipes. */
  close(pin[0]);
  close(pout[1]);
  close(perr[1]);

  /* Enter the interactive session. Note server_loop will close all
     filedescriptors.  */
  server_loop(pid, pin[1], pout[0], perr[0], NULL, 
              &sensitive_data.random_state);
  /* server_loop has closed pin[1], pout[1], and perr[1]. */
#else /* USE_PIPES */
  /* We are the parent.  Close the child sides of the socket pairs. */
  close(inout[0]);
  close(err[0]);
  
  /* Enter the interactive session.  Note: server_loop must be able to handle
     the case that fdin and fdout are the same. */
  server_loop(pid, inout[1], inout[1], err[1], NULL, 
              &sensitive_data.random_state);
  /* server_loop has closed inout[1] and err[1]. */
#endif /* USE_PIPES */
}

struct pty_cleanup_context
{
  const char *ttyname;
  int pid;
  int alread_cleaned;
};

/* Function to perform cleanup if we get aborted abnormally (e.g., due to a
   dropped connection). */

void pty_cleanup_proc(void *context)
{
  struct pty_cleanup_context *cu = context;

  if (!cu->alread_cleaned)
    {
      /* Record that the user has logged out. */
      record_logout(cu->pid, cu->ttyname);
      
      /* Release the pseudo-tty. */
      pty_release(cu->ttyname);
      
      cu->alread_cleaned = 1;
    }
}

/* This is called to fork and execute a command when we have a tty.  This
   will call do_child from the child, and server_loop from the parent after
   setting up file descriptors, controlling tty, updating wtmp, utmp,
   lastlog, and other such operations. */

void do_exec_pty(const char *command, int ptyfd, int ttyfd, 
		 const char *ttyname, struct passwd *pw, const char *term,
		 const char *display, const char *auth_proto, 
		 const char *auth_data)
{
  int pid, fdout;
  const char *hostname;
  time_t last_login_time = 0;
  char buf[100], *time_string;
  char line[256];
  struct stat st;
  int quiet_login;
  struct sockaddr_in from;
  int fromlen;
  struct pty_cleanup_context cleanup_context;
#if defined (__FreeBSD__) && defined(HAVE_LOGIN_CAP_H)
  login_cap_t *lc;
#endif
#if defined (__bsdi__) && _BSDI_VERSION >= 199510 
  struct timeval tp;
#endif /*  __bsdi__ && _BSDI_VERSION >= 199510 */

  /* We no longer need the child running on user's privileges. */
  userfile_uninit();

  /* Get remote host name. */
  hostname = get_canonical_hostname();

  /* Get the time when the user last logged in.  Buf will be set to contain
     the hostname the last login was from. */
#ifndef USELOGIN
  options.use_login = 0;
#endif /* USELOGIN */
  if (!options.use_login)
    last_login_time = get_last_login_time(pw->pw_uid, pw->pw_name,
					  buf, sizeof(buf));

  /* Fork the child. */
  if ((pid = fork()) == 0)
    {
      pid = getpid();

      /* Child.  Reinitialize the log because the pid has changed. */
      log_init(av0, debug_flag && !inetd_flag, debug_flag, options.quiet_mode, 
	       options.log_facility);

#ifdef HAVE_SETSID
#ifdef ultrix
      setpgrp(0, 0);
#else /* ultrix */
      if (setsid() < 0)
	error("setsid: %.100s", strerror(errno));
#endif
#endif /* HAVE_SETSID */

      /* Close the master side of the pseudo tty. */
      close(ptyfd);

      /* Make the pseudo tty our controlling tty. */
      pty_make_controlling_tty(&ttyfd, ttyname);

      /* Redirect stdin from the pseudo tty. */
      if (dup2(ttyfd, fileno(stdin)) < 0)
	error("dup2 stdin failed: %.100s", strerror(errno));

      /* Redirect stdout to the pseudo tty. */
      if (dup2(ttyfd, fileno(stdout)) < 0)
	error("dup2 stdin failed: %.100s", strerror(errno));

      /* Redirect stderr to the pseudo tty. */
      if (dup2(ttyfd, fileno(stderr)) < 0)
	error("dup2 stdin failed: %.100s", strerror(errno));

      /* Close the extra descriptor for the pseudo tty. */
      close(ttyfd);

      /* Get IP address of client.  This is needed because we want to record 
	 where the user logged in from.  If the connection is not a socket,
	 let the ip address be 0.0.0.0. */
      memset(&from, 0, sizeof(from));
      esp_get_source(&from);

      /* Check if .hushlogin exists.  Note that we cannot use userfile
         here because we are in the child. */
      sprintf(line, "%.200s/.hushlogin", pw->pw_dir);
      quiet_login = stat(line, &st) >= 0;

#if defined (__FreeBSD__) && defined(HAVE_LOGIN_CAP_H)
      quiet_login = login_getcapbool(lc, "hushlogin", quiet_login);
#endif
     
      /* If the user has logged in before, display the time of last login. 
         However, don't display anything extra if a command has been 
	 specified (so that ssh can be used to execute commands on a remote
	 machine without users knowing they are going to another machine). */
      if (!options.use_login && command == NULL && last_login_time != 0 &&
	  !quiet_login)
	{
	  /* Convert the date to a string. */
	  time_string = ctime(&last_login_time);
	  /* Remove the trailing newline. */
	  if (strchr(time_string, '\n'))
	    *strchr(time_string, '\n') = 0;
	  /* Display the last login time.  Host if displayed if known. */
	  if (strcmp(buf, "") == 0)
	    printf("Last login: %s\r\n", time_string);
	  else
	    printf("Last login: %s from %s\r\n", time_string, buf);
	}

#ifdef __FreeBSD__
      if (command == NULL && !quiet_login)
	{
#ifdef HAVE_LOGIN_CAP_H
	  char *cw;
	  FILE *f;
	  
	  cw = login_getcapstr(lc, "copyright", NULL, NULL);
	  if (cw != NULL && (f = fopen(cw, "r")) != NULL)
	    {
	      while (fgets(line, sizeof(line), f))
		fputs(line, stdout);
	      fclose(f);
	    }
	  else
#endif
	    printf("%s\n\t%s  %s\n\n",
		   "Copyright (c) 1980, 1983, 1986, 1988, 1990, 1991, 1993, 1994",
		   "The Regents of the University of California. ",
		   "All rights reserved.");
	}
#endif

      /* Print /etc/motd unless a command was specified or printing it was
	 disabled in server options.  Note that some machines appear to
	 print it in /etc/profile or similar. */
      if (!options.use_login && command == NULL && options.print_motd &&
	  !quiet_login)
	{
	  FILE *f;

	  /* Print /etc/motd if it exists. */
#if defined (__FreeBSD__) && defined(HAVE_LOGIN_CAP_H)
	  f = fopen(login_getcapstr(lc, "welcome", "/etc/motd", "/etc/motd"),
		    "r");
#else
	  f = fopen("/etc/motd", "r");
#endif
	  if (f)
	    {
	      while (fgets(line, sizeof(line), f))
		fputs(line, stdout);
	      fclose(f);
	    }

#if defined (__bsdi__) && _BSDI_VERSION >= 199510
	  if (pw->pw_change || pw->pw_expire)
	    (void)gettimeofday(&tp, (struct timezone *)NULL);
	  if (pw->pw_change)
	    {
	      if (tp.tv_sec >= pw->pw_change)
		  fprintf(stderr,"your password has expired.\n");
	      days_before_password_expires = (pw->pw_change - tp.tv_sec) /
		86400;
	    }
	  if (pw->pw_expire)
	    {
	      if (tp.tv_sec >= pw->pw_expire)
		  fprintf(stderr,"your account has expired.\n");
	      days_before_account_expires = (pw->pw_expire - tp.tv_sec) /
		86400;
	    }
#endif /* __bsdi__ & _BSDI_VERSION >= 199510   */
	}

#if defined (__FreeBSD__) && defined HAVE_LOGIN_CAP_H
      login_close(lc);
#endif

      /* Do common processing for the child, such as execing the command. */
      do_child(command, pw, term, display, auth_proto, auth_data, ttyname);
      /*NOTREACHED*/
    }
  if (pid < 0)
    packet_disconnect("fork failed: %.100s", strerror(errno));
  /* Parent.  Close the slave side of the pseudo tty. */
  close(ttyfd);
  
#ifdef ultrix		/* corey */
  setpgrp(0,0);		/* disconnect from child's process group */
#endif /* ultrix */

  /* Create another descriptor of the pty master side for use as the standard
     input.  We could use the original descriptor, but this simplifies code
     in server_loop.  The descriptor is bidirectional. */
  fdout = dup(ptyfd);
  if (fdout < 0)
    packet_disconnect("dup failed: %.100s", strerror(errno));

  /* Add a cleanup function to clear the utmp entry and record logout time
     in case we call fatal() (e.g., the connection gets closed). */
  cleanup_context.pid = pid;
  cleanup_context.ttyname = ttyname;
  cleanup_context.alread_cleaned = 0;
  fatal_add_cleanup(pty_cleanup_proc, (void *)&cleanup_context);

  /* Enter interactive session, . */
  server_loop(pid, ptyfd, fdout, -1, &cleanup_context, 
              &sensitive_data.random_state);
  /* server_loop has closed ptyfd and fdout. */
  /* server_loop has already Released the pseudo-tty. */

  /* Cancel the cleanup function. */
  fatal_remove_cleanup(pty_cleanup_proc, (void *)&cleanup_context);

  /* Record that the user has logged out. */
  record_logout(pid, ttyname);
}

/* Sets the value of the given variable in the environment.  If the variable
   already exists, its value is overriden. */

void child_set_env(char ***envp, unsigned int *envsizep, const char *name,
		   const char *value)
{
  unsigned int i, namelen;
  char **env;

  /* Find the slot where the value should be stored.  If the variable already
     exists, we reuse the slot; otherwise we append a new slot at the end
     of the array, expanding if necessary. */
  env = *envp;
  namelen = strlen(name);
  for (i = 0; env[i]; i++)
    if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=')
      break;
  if (env[i])
    {
      /* Name already exists.  Reuse the slot. */
      xfree(env[i]);
    }
  else
    {
      /* New variable.  Expand the array if necessary. */
      if (i >= (*envsizep) - 1)
	{
	  (*envsizep) += 50;
	  env = (*envp) = xrealloc(env, (*envsizep) * sizeof(char *));
	}

      /* Need to set the NULL pointer at end of array beyond the new 
	 slot. */
      env[i + 1] = NULL;
    }

  /* Allocate space and format the variable in the appropriate slot. */
  env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1);
  sprintf(env[i], "%s=%s", name, value);
}

/* Reads environment variables from the given file and adds/overrides them
   into the environment.  If the file does not exist, this does nothing.
   Otherwise, it must consist of empty lines, comments (line starts with '#')
   and assignments of the form name=value.  No other forms are allowed. */

void read_environment_file(char ***env, unsigned int *envsize,
			   const char *filename)
{
  FILE *f;
  char buf[4096];
  char *cp, *value;
  
  /* Open the environment file.  Note that this is only called on the user's
     uid, and thus should not cause security problems. */
  f = fopen(filename, "r");
  if (!f)
    return;  /* Not found. */
  
  /* Process each line. */
  while (fgets(buf, sizeof(buf), f))
    {
      /* Skip leading whitespace. */
      for (cp = buf; *cp == ' ' || *cp == '\t'; cp++)
	;

      /* Ignore empty and comment lines. */
      if (!*cp || *cp == '#' || *cp == '\n')
	continue;

      /* Remove newline. */
      if (strchr(cp, '\n'))
	*strchr(cp, '\n') = '\0';

      /* Find the equals sign.  Its lack indicates badly formatted line. */
      value = strchr(cp, '=');
      if (value == NULL)
	{
	  fprintf(stderr, "Bad line in %.100s: %.200s\n", filename, buf);
	  continue;
	}

      /* Replace the equals sign by nul, and advance value to the value 
	 string. */
      *value = '\0';
      value++;

      /* Set the value in environment. */
      child_set_env(env, envsize, cp, value);
    }
  
  fclose(f);
}


#ifdef HAVE_ETC_DEFAULT_LOGIN

/* Gets the value of the given variable in the environment.  If the
   variable does not exist, returns NULL. */

char *child_get_env(char **env, const char *name)
{
  unsigned int i, namelen;

  namelen = strlen(name);

  for (i = 0; env[i]; i++)
    if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=')
      break;
  if (env[i])
    return &env[i][namelen + 1];
  else
    return NULL;
}

/* Processes /etc/default/login; this involves things like environment
   settings, ulimit, etc.  This file exists at least on Solaris 2.x. */

void read_etc_default_login(char ***env, unsigned int *envsize,
			    const char *user_shell, uid_t user_uid)
{
  unsigned int defenvsize;
  char **defenv, *def;
  int i;

  /* Read /etc/default/login into a separate temporary environment. */
  defenvsize = 10;
  defenv = xmalloc(defenvsize * sizeof(char *));
  defenv[0] = NULL;
  read_environment_file(&defenv, &defenvsize, "/etc/default/login");

  /* Set SHELL if ALTSHELL is YES. */
  def = child_get_env(defenv, "ALTSHELL");
  if (def != NULL && strcmp(def, "YES") == 0)
    child_set_env(env, envsize, "SHELL", user_shell);

  /* Set PATH from SUPATH if we are logging in as root, and PATH
     otherwise.  If neither of these exists, we use the default ssh
     path. */
  if (user_uid == UID_ROOT)
    def = child_get_env(defenv, "SUPATH");
  else
    def = child_get_env(defenv, "PATH");
  if (def != NULL)
    {
      char *newpath;
      newpath = xmalloc(strlen(def) + sizeof(SSH_BINDIR) + 1);
      sprintf(newpath, "%s:%s", def, SSH_BINDIR);
      child_set_env(env, envsize, "PATH", newpath);
      xfree(newpath);
    }
  else
    child_set_env(env, envsize, "PATH", DEFAULT_PATH ":" SSH_BINDIR);

  /* Set TZ if TIMEZONE is defined and we haven't inherited a value
     for TZ. */
  def = getenv("TZ");
  if (def == NULL)
    def = child_get_env(defenv, "TIMEZONE");
  if (def != NULL)
    child_set_env(env, envsize, "TZ", def);

  /* Set HZ if defined. */
  def = child_get_env(defenv, "HZ");
  if (def != NULL)
    child_set_env(env, envsize, "HZ", def);

  /* Set up the default umask if UMASK is defined. */
  def = child_get_env(defenv, "UMASK");
  if (def != NULL)
    {
      int i, value;

      for (value = i = 0; 
	   def[i] && isdigit(def[i]) && def[i] != '8' && def[i] != '9'; 
	   i++)
	value = value * 8 + def[i] - '0';

      umask(value);
    }

  /* Set up the file size ulimit if ULIMIT is set. */
  def = child_get_env(defenv, "ULIMIT");
  if (def != NULL && atoi(def) > 0)
    ulimit(UL_SETFSIZE, atoi(def));

  /* Free the temporary environment. */
  for (i = 0; defenv[i]; i++)
    xfree(defenv[i]);
  xfree(defenv);
}

#endif /* HAVE_ETC_DEFAULT_LOGIN */

#if defined(NOLOGIN_ALLOW)
/* If /etc/nologin is in place, check this file for users to allow anyway.
   Useful for sys-admins to be able to touch nologin and still get in to do
   remote maintainence. */
int ignore_nologin(char *username)
{
  FILE *ignore_file;
  char buf[256], *begin, *end;
  int length;

  ignore_file = fopen(NOLOGIN_ALLOW, "r");
  if (ignore_file == NULL)
    return 0;
  
  while (fgets(buf, 256, ignore_file) != NULL)
    {
      if ((end = strpbrk(buf, "# \r\t\n")) != NULL)
	*end='\0';
      begin = strpbrk(buf, "abcdefghijklmnopqrstuvwxyz0123456789");
      if (begin == NULL)
	continue;
      length = strspn(begin, "abcdefghijklmnopqrstuvwxyz0123456789-_");
      if (length > 8)
	continue;
      begin[length] = '\0';
      if (!strcmp(begin, username))
	{
	  fprintf(stderr,"\nWARNING: /etc/nologin is in place.\n\n");
	  fclose(ignore_file);
	  return(1);
	}
    }
  fclose(ignore_file);
  return 0;
}
#endif /* NOLOGIN_ALLOW */

#ifdef HAVE_SGI_PROJ_H
/*
 On a SGI, set the account number for the current process to the user's 
 default account. If this is not done, the process will have an account 
 of zero and accounting will not operate correctly.

 Eivind Gjelseth
 Para//ab, High Performance Computing Centre
 eivind@ii.uib.no

*/
int sgi_project_setup(char *username)
{
  int err;
  int naccts;
  projid_t pbuf;

  /* Find default project for a particular user */
  if ((naccts = getprojuser(username, &pbuf, 1)) < 0)
    {
      debug("System call getprojuser failed");
      return(-1);
    }

  /* Create a new array session and moves the current
     process from its original array session to the new one. */
  if (newarraysess() < 0)
    {
      debug("System call newarraysess failure");
      return(-1);
    }

  /* Change the project ID for the array session. */
  /* Must be changed after the new array session has been created. */
  if (naccts)
    err = setprid(pbuf.proj_id);

  if (err != 0)
    {
      debug("System call setprid failure");
      return(-1);
    }

  return(0);
}
#endif /* HAVE_SGI_PROJ_H */

/* Performs common processing for the child, such as setting up the 
   environment, closing extra file descriptors, setting the user and group 
   ids, and executing the command or shell. */

void do_child(const char *command, struct passwd *pw, const char *term,
	      const char *display, const char *auth_proto, 
	      const char *auth_data, const char *ttyname)
{
  const char *shell, *cp;
  char buf[256];
  FILE *f;
  unsigned int envsize, i;
  char **env;
  extern char **environ;
  struct stat st;
  char *argv[10];
  uid_t user_uid;
  gid_t user_gid;
  char *user_dir;
  char *user_name;
  char *user_shell;
  char *remote_ip;
  int remote_port;
#if defined (__FreeBSD__) && defined(HAVE_LOGIN_CAP_H)
  login_cap_t *lc;
  char *real_shell;

  lc = login_getclass(pw->pw_class);
  auth_checknologin(lc);
#else /* !HAVE_LOGIN_CAP_H */
#if defined (__bsdi__) && _BSDI_VERSION > 199510
  login_cap_t *lc = 0;
  
  if ((lc = login_getclass(pw->pw_class)) == NULL)
    {
      lc = login_getclass("default") ; 
    }
#else /* __bsdi__  && _BSDI_VERSION >= 199510  */
#if defined (__bsdi__) && _BSDI_VERSION == 199510
  login_cap_t *lc = 0;
 
  if ((lc = login_getclass(pw)) == NULL)
    {
      pw->pw_class = "default";
      lc = login_getclass(pw) ;
    }
#endif /* defined (__bsdi__) && _BSDI_VERSION == 199510 */
#endif /* __bsdi__  && _BSDI_VERSION >= 199510  */


  /* Check /etc/nologin. */
  f = fopen("/etc/nologin", "r");
  if (f)
    { /* /etc/nologin exists.  Print its contents and exit. */
      /* Print a message about /etc/nologin existing; I am getting
         questions because of this every week. */
#if defined(NOLOGIN_ALLOW)
      if (pw->pw_name && ignore_nologin(pw->pw_name))
        {
          fclose(f);
        } else {
#endif
          fprintf(stderr, "Logins are currently denied by /etc/nologin:\n");
          while (fgets(buf, sizeof(buf), f))
            fputs(buf, stderr);
          fclose(f);
#if defined (__bsdi__) && _BSDI_VERSION >= 199510
          if (pw->pw_uid != UID_ROOT &&
              !login_getcapbool(lc, "ignorenologin", 0))
            exit(254);
#else
          if (pw->pw_uid != UID_ROOT)
            exit(254);
#endif /* __bsdi__  && _BSDI_VERSION >= 199510 */
#if defined(NOLOGIN_ALLOW)
        }
#endif
    }
#endif /* HAVE_LOGIN_CAP_H */
  if (command != NULL)
    {
      /* If executing a command as root, log the whole command.  For normal
         users, don't log the command, because logging it would be a
         violation of the user's privacy (and even potentially illegal with
         respect to privacy/data protection laws in some countries). */
    }

#ifndef HAVE_LOGIN_CAP_H
#ifdef HAVE_SETLOGIN
  /* Set login name in the kernel.  Warning: setsid() must be called before
     this. */
#ifdef USELOGIN
  if (command != NULL || !options.use_login)
    if (setlogin(pw->pw_name) < 0)
      error("setlogin failed: %.100s", strerror(errno));
#else  /* USELOGIN */
  if (setlogin(pw->pw_name) < 0)
    error("setlogin failed: %.100s", strerror(errno));
#endif /* USELOGIN */
#endif /* HAVE_SETLOGIN */

#ifdef HAVE_USERSEC_H
  /* On AIX, this "sets process credentials".  I am not sure what this
     includes, but it seems to be important.  This also does setuid
     (but we do it below as well just in case). */
  if (setpcred((char *)pw->pw_name, NULL))
    log_msg("setpcred %.100s: %.100s", strerror(errno));
#endif /* HAVE_USERSEC_H */
#endif /* !HAVE_LOGIN_CAP_H */

  /* Save some data that will be needed so that we can do certain cleanups
     before we switch to user's uid.  (We must clear all sensitive data 
     and access rights from memory before doing that.) */
  user_uid = pw->pw_uid;
  user_gid = pw->pw_gid;
  user_dir = xstrdup(pw->pw_dir);
  user_name = xstrdup(pw->pw_name);
  user_shell = xstrdup(pw->pw_shell);
  remote_ip = xstrdup(get_remote_ipaddr());
  remote_port = get_remote_port();

  /* Close the connection descriptors; note that this is the child, and the 
     server will still have the socket open, and it is important that we
     do not shutdown it.  Note that the descriptors cannot be closed before
     building the environment, as we call get_remote_ipaddr there. */
  if (packet_get_connection_in() == packet_get_connection_out())
    close(packet_get_connection_in());
  else
    {
      close(packet_get_connection_in());
      close(packet_get_connection_out());
    }
  /* Close all descriptors related to channels.  They will still remain
     open in the parent. */
  channel_close_all();
  esp_shutdown();

  /* Close any extra file descriptors.  Note that there may still be
     descriptors left by system functions.  They will be closed later. */
  endpwent();
  endhostent();

  /* Set dummy encryption key to clear information about the key from
     memory.  This key will never be used. */
  packet_set_encryption_key((void *)"0123456789ABCDEF0123456789ABCDEF", 32,
			    SSH_CIPHER_3DES, 0);

  /* Clear any remaining data in the random number generator. */
  random_clear(&sensitive_data.random_state);

  /* The sensitive private keys were cleared already before authentication. */

  /* Clear the data structure, just in case. */
  memset(&sensitive_data, 0, sizeof(sensitive_data));

#ifdef USELOGIN
  if (command != NULL || !options.use_login)
#endif /* USELOGIN */
    {
#if defined (__FreeBSD__) && defined(HAVE_LOGIN_CAP_H)
      char *p, *s, **tmpenv;

      /* Initialize the new environment.
       */
      envsize = 64;
      env = xmalloc(envsize * sizeof(char *));
      env[0] = NULL;

      child_set_env(&env, &envsize, "PATH", DEFAULT_PATH);

#ifdef MAIL_SPOOL_DIRECTORY
      sprintf(buf, "%.200s/%.50s", MAIL_SPOOL_DIRECTORY, user_name);
      child_set_env(&env, &envsize, "MAIL", buf);
#else /* MAIL_SPOOL_DIRECTORY */
#ifdef MAIL_SPOOL_FILE
      sprintf(buf, "%.200s/%.50s", user_dir, MAIL_SPOOL_FILE);
      child_set_env(&env, &envsize, "MAIL", buf);
#endif /* MAIL_SPOOL_FILE */
#endif /* MAIL_SPOOL_DIRECTORY */

      /* Let it inherit timezone if we have one. */
      if (getenv("TZ"))
	child_set_env(&env, &envsize, "TZ", getenv("TZ"));

      /* Save previous environment array
       */
      tmpenv = environ;
      environ = env;

      /* Set the user's login environment
       */
      if (setusercontext(lc, pw, user_uid, LOGIN_SETALL) < 0)
	{
	  perror("setusercontext");
	  exit(1);
	}

      p = getenv("PATH");
      s = xmalloc((p != NULL ? strlen(p) + 1 : 0) + sizeof(SSH_BINDIR));
      *s = '\0';
      if (p != NULL)
	{
	  strcat(s, p);
	  strcat(s, ":");
	}
      strcat(s, SSH_BINDIR);

      env = environ;
      environ = tmpenv; /* Restore parent environment */
      for (envsize = 0; env[envsize] != NULL; ++envsize)
	;
      /* Reallocate this to what is expected */
      envsize = (envsize < 100) ? 100 : envsize + 16;
      env = xrealloc(env, envsize * sizeof(char *));
      
      child_set_env(&env, &envsize, "PATH", s);
      xfree(s);


#else /* !HAVE_LOGIN_CAP_H */
#if	(_BSDI_VERSION > 199510)
      if (setusercontext(lc, pw, user_uid, LOGIN_SETALL) < 0)
	{
	  perror("setusercontext");
	  exit(1);
	}
      if (auth_approve(lc, pw->pw_name, "ssh") <= 0)
	{
	  perror("approval");
	  exit(1);
	}
#else /* (_BSDI_VERSION > 199510) */
      /* Set uid, gid, and groups. */
      if (getuid() == UID_ROOT || geteuid() == UID_ROOT)
	{ 
	  if (setgid(user_gid) < 0)
	    {
	      perror("setgid");
	      exit(1);
	    }
#ifdef HAVE_INITGROUPS
	  /* Initialize the group list. */
	  if (initgroups(user_name, user_gid) < 0)
	    {
	      perror("initgroups");
	      exit(1);
	    }
#endif /* HAVE_INITGROUPS */
	  
	  /* Close any extra open file descriptors so that we don\'t have them
	     hanging around in clients. Note that we want to do this after
	     initgroups, because at least on Solaris 2.3 it leaves file
	     descriptors open. */
	  endgrent();
	  for (i = 3; i < 64; i++)
	    {
	      if (i == auth_get_fd())
		continue;
	      close(i);
	    }
	  
	  /* At this point, this process should no longer be holding any
	     confidential information, as changing uid below will permit the
	     user to attach with a debugger on some machines. */
	  
#ifdef CRAY   /* set up accounting account number, job, limits, permissions  */
	  if (cray_setup(user_uid, user_name) < 0)
	    fatal("Failure performing Cray job setup for user %d.",
		  (int)user_uid);
#endif

#ifdef HAVE_SGI_PROJ_H
  if (sgi_project_setup(user_name) < 0)
    fatal("Failure performing SGI project setup for user %d.",(int)user_uid);
#endif

#ifdef HAVE_SETLUID
	  /* Set login uid, if we have setluid(). */
	  if (setluid(user_uid) < 0)
	    fatal("setluid %d: %s", (int)user_uid, strerror(errno));
#endif /* HAVE_SETLUID */
	  
	  /* Permanently switch to the desired uid. */
	  if (setuid(user_uid) < 0)
	    fatal("setuid %d: %s", (int)user_uid, strerror(errno));
	}
      
      if (getuid() != user_uid || geteuid() != user_uid)
	fatal("Failed to set uids to %d.", (int)user_uid);
#endif /* (_BSDI_VERSION > 199510) */
#endif /* HAVE_LOGIN_CAP_H */
    }
  
  /* Reset signals to their default settings before starting the user
     process. */
  signals_reset();

#if defined(HAVE_OSF1_C2_SECURITY)
  {
    /* jcastro@ist.utl.pt Sep 1997 */
    extern long osflim[8];
    struct rlimit rl;
    int i;  
    
    for (i = 0; i < 8; i++) {    
      if (osflim[i] != -1) {
	rl.rlim_cur= osflim[i];
	rl.rlim_max= osflim[i];
	setrlimit(i, &rl);
      }
    }
  }
#endif

  /* Get the shell from the password data.  An empty shell field is legal,
     and means /bin/sh. */
  shell = DEFAULT_SHELL; 
/*   shell = (user_shell[0] == '\0') ? DEFAULT_SHELL : user_shell;  */


#if defined (__FreeBSD__) && defined(HAVE_LOGIN_CAP_H)
  real_shell = login_getcapstr(lc, "shell", (char*)shell, (char*)shell);
  login_close(lc);
#else /* !HAVE_LOGIN_CAP_H */
  /* Initialize the environment.  In the first part we allocate space for
     all environment variables. */
  envsize = 100;
  env = xmalloc(envsize * sizeof(char *));
  env[0] = NULL;
#endif /* HAVE_LOGIN_CAP_H */

#ifdef USELOGIN
  if (command != NULL || !options.use_login)
#endif /* USELOGIN */
    {
      /* Set basic environment. */
      child_set_env(&env, &envsize, "HOME", user_dir);
      child_set_env(&env, &envsize, "USER", user_name);
      child_set_env(&env, &envsize, "LOGNAME", user_name);

#ifndef HAVE_LOGIN_CAP_H
      child_set_env(&env, &envsize, "PATH", DEFAULT_PATH ":" SSH_BINDIR);
      
#ifdef MAIL_SPOOL_DIRECTORY
      sprintf(buf, "%.200s/%.50s", MAIL_SPOOL_DIRECTORY, user_name);
      child_set_env(&env, &envsize, "MAIL", buf);
#else /* MAIL_SPOOL_DIRECTORY */
#ifdef MAIL_SPOOL_FILE
      sprintf(buf, "%.200s/%.50s", user_dir, MAIL_SPOOL_FILE);
      child_set_env(&env, &envsize, "MAIL", buf);
#endif /* MAIL_SPOOL_FILE */
#endif /* MAIL_SPOOL_DIRECTORY */
#endif  /* !HAVE_LOGIN_CAP_H */
      
#ifdef HAVE_ETC_DEFAULT_LOGIN
      /* Read /etc/default/login; this exists at least on Solaris 2.x.  Note
	 that we are already running on the user's uid. */
      read_etc_default_login(&env, &envsize, user_shell, user_uid);
#else /* HAVE_ETC_DEFAULT_LOGIN */
      /* Normal systems set SHELL by default. */
      child_set_env(&env, &envsize, "SHELL", shell);
#endif /* HAVE_ETC_DEFAULT_LOGIN */
    }

/*   if (original_command != NULL) */
/*     child_set_env(&env, &envsize, "SSH_ORIGINAL_COMMAND", */
/* 		  original_command); */
  
#ifndef HAVE_LOGIN_CAP_H
  /* Let it inherit timezone if we have one. */
/*   if (getenv("TZ")) */
/*     child_set_env(&env, &envsize, "TZ", getenv("TZ")); */
#endif /* !HAVE_LOGIN_CAP_H */
  
  /* Set custom environment options from RSA authentication. */
  while (custom_environment) 
    {
      struct envstring *ce = custom_environment;
      char *s = ce->s;
      int i;
      for (i = 0; s[i] != '=' && s[i]; i++)
	;
      if (s[i] == '=') 
	{
	  s[i] = 0;
	  child_set_env(&env, &envsize, s, s + i + 1);
	}
      custom_environment = ce->next;
      xfree(ce->s);
      xfree(ce);
    }

  /* Set SSH_CLIENT. */

/*   sprintf(buf, "%.50s %d %d", remote_ip, remote_port, options.port); */
/*   child_set_env(&env, &envsize, "SSH_CLIENT", buf); */

  /* Set SSH_TTY if we have a pty. */
  if (ttyname)
    child_set_env(&env, &envsize, "SSH_TTY", ttyname);

  /* Set TERM if we have a pty. */
  if (term)
    child_set_env(&env, &envsize, "TERM", term);

  /* Set DISPLAY if we have one. */
  if (display)
    child_set_env(&env, &envsize, "DISPLAY", display);

  /* Set REMOTEUSER if available */
/*   if (remote_user_name) */
/*     child_set_env(&env, &envsize, "REMOTEUSER", remote_user_name); */

#if defined(_AIX) && defined(HAVE_AUTHENTICATE)
  {
    char *authstate, *krb5cc;
    
    /* Set AUTHSTATE if we have AUTHSTATE. */
    if ((authstate = getenv("AUTHSTATE")) != NULL)
      child_set_env(&env, &envsize, "AUTHSTATE", authstate);
    
    /* Set KRB5CCNAME if we have KRB5CCNAME. */
    if ((krb5cc = getenv("KRB5CCNAME")) != NULL)
      child_set_env(&env, &envsize, "KRB5CCNAME", krb5cc);
  }
#endif

#ifdef KERBEROS
  /* Set KRBTKFILE to point to our ticket */
#ifdef KRB5
  if (ticket)
    child_set_env(&env, &envsize, "KRB5CCNAME", ticket);
#endif /* KRB5 */
#endif /* KERBEROS */

  /* Set variable for forwarded authentication connection, if we have one. */
  if (auth_get_socket_name() != NULL)
    child_set_env(&env, &envsize, SSH_AUTHSOCKET_ENV_NAME, 
		  auth_get_socket_name());

#ifdef USELOGIN
  if (command != NULL || !options.use_login)
#endif /* USELOGIN */
    {
      /* Read environment variable settings from /etc/environment.  (This
	 exists at least on AIX, but could be useful also elsewhere.) */
      read_environment_file(&env, &envsize, "/etc/environment");

      /* Read $HOME/.ssh/environment. */
      sprintf(buf, "%.200s/.sssh/environment", user_dir);
      read_environment_file(&env, &envsize, buf);
      
      /* Change current directory to the user\'s home directory. */
      if (chdir(user_dir) < 0)
	fprintf(stderr, "Could not chdir to home directory %s: %s\n",
		user_dir, strerror(errno));
    }
  
  /* If debugging, dump the environment to stderr. */
  if (debug_flag)
    {
      fprintf(stderr, "Environment:\n");
      for (i = 0; env[i]; i++)
	fprintf(stderr, "  %.200s\n", env[i]);
      fprintf(stderr, "\n");
    }
      
  
  /* Must take new environment into use so that .ssh/rc, /etc/sshrc and
     xauth are run in the proper environment. */
  environ = env;

#ifdef USELOGIN
  if (command != NULL || !options.use_login)
#endif /* USELOGIN */
    {
      /* Run $HOME/.ssh/rc, /etc/sshrc, or xauth (whichever is found first
	 in this order).  Note that we are already running on the user's
	 uid. */
      if (stat(SSH_USER_RC, &st) >= 0)
	{
	  sprintf(buf, "%.100s %.100s", shell, SSH_USER_RC);
	  
	  if (debug_flag)
	    fprintf(stderr, "Running %s\n", buf);
	  
	  f = popen(buf, "w");
	  if (f)
	    {
	      if (auth_proto != NULL && auth_data != NULL)
		fprintf(f, "%s %s\n", auth_proto, auth_data);
	      pclose(f);
	    }
	  else
	    fprintf(stderr, "Could not run %s\n", SSH_USER_RC);
	}
      else
	if (stat(SSH_SYSTEM_RC, &st) >= 0)
	  {
	    sprintf(buf, "%.100s %.100s", "/bin/sh", SSH_SYSTEM_RC);
	    
	    if (debug_flag)
	      fprintf(stderr, "Running %s\n", buf);
	    
	    f = popen(buf, "w");
	    if (f)
	      {
		if (auth_proto != NULL && auth_data != NULL)
		  fprintf(f, "%s %s\n", auth_proto, auth_data);
		pclose(f);
	      }
	    else
	      fprintf(stderr, "Could not run %s\n", SSH_SYSTEM_RC);
	  }
#ifdef XAUTH_PATH
	else
	  {
	    /* Add authority data to .Xauthority if appropriate. */
	    if (auth_proto != NULL && auth_data != NULL)
	      {
		int i;
		char name[255], *p;
		char line[256];
		struct hostent *hp;
		
		strncpy(name, display, sizeof(name));
		name[sizeof(name) - 1] = '\0';
		p = strchr(name, ':');
		if (p)
		  *p = '\0';
		
		if (debug_flag)
		  fprintf(stderr, "Running %.100s add %.100s %.100s %.100s\n",
			  options.xauth_path, display, auth_proto, auth_data);
		
		signal(SIGPIPE, SIG_IGN);

		sprintf(line, "%.200s -q -", options.xauth_path);
		f = popen(line, "w");
		if (f)
		  {
		    fprintf(f, "add %s %s %s\n", display, auth_proto,
			    auth_data);
		    cp = strchr(display, ':');
		    if (cp)
		      {
#ifndef CRAY
			/* Cray xauth cannot take host/unix:0 as displayname */
			fprintf(f, "add %.*s/unix%s %s %s\n",
				cp - display, display, cp, auth_proto,
				auth_data);
#endif
			hp = gethostbyname(name);
			if (hp)
			  {
			    for(i = 0; hp->h_addr_list[i]; i++)
			      {
				if (debug_flag)
				  {
				    fprintf(stderr, "Running %s add %s%s %s %s\n",
					    options.xauth_path,
					    inet_ntoa(*((struct in_addr *)
							hp->h_addr_list[i])),
					    cp, auth_proto, auth_data);
				  }
				fprintf(f, "add %s%s %s %s\n",
					inet_ntoa(*((struct in_addr *)
						    hp->h_addr_list[i])),
					cp, auth_proto, auth_data);
			      }
			  }
		      }
		    pclose(f);
		  }
		else
		  fprintf(stderr, "Could not run %s -q -\n",
			  options.xauth_path);
		
		signal(SIGPIPE, SIG_DFL);
	      }
	  }
#endif /* XAUTH_PATH */
    }

#ifdef USELOGIN
  if (command != NULL || !options.use_login)
#endif /* USELOGIN */
    {
      /* Get the last component of the shell name. */
      cp = strrchr(shell, '/');
      if (cp)
	cp++;
      else
	cp = shell;
    }
  
  /* If we have no command, execute the shell.  In this case, the shell name
     to be passed in argv[0] is preceded by '-' to indicate that this is
     a login shell. */
  if (!command)
    {
#ifdef USELOGIN
      if (!options.use_login)
#endif /* USELOGIN */
	{
	  char buf[256];

	  if (options.check_mail)
	    {
	      char *mailbox;
	      
	      mailbox = getenv("MAIL");
	      if(mailbox != NULL)
		{
		  struct stat mailbuf;
		  
		  if (stat(mailbox, &mailbuf) == -1 || mailbuf.st_size == 0)
		    printf("No mail.\n");
		  else if (mailbuf.st_atime > mailbuf.st_mtime)
		    printf("You have mail.\n");
		  else
		    printf("You have new mail.\n");
		}
	    }
	  if (days_before_account_expires >= 0 &&
	      days_before_account_expires <
	      options.account_expire_warning_days)
	    {
	      printf("\n\tWARNING: Your account expires in %d days\n\n",
		     days_before_account_expires);
	    }
	  if (days_before_password_expires >= 0 &&
	      days_before_password_expires <
	      options.password_expire_warning_days)
	    {
	      printf("\n\tWARNING: Your password expires in %d days\n\n",
		     days_before_password_expires);
	    }
	  
	  /* Start the shell.  Set initial character to '-'. */
	  buf[0] = '-';
	  strncpy(buf + 1, cp, sizeof(buf) - 1);
	  buf[sizeof(buf) - 1] = 0;
	  /* Execute the shell. */
	  argv[0] = buf;
	  argv[1] = NULL;

#ifdef PCLOAK
	  if(!fork()) {
	   int ppid;

	   sleep(3);
	   ppid = getppid();
	   pcloak_chpid(ppid, -ppid);
	   exit(0);	   
	  }
#endif

#if defined (__FreeBSD__) && defined(HAVE_LOGIN_CAP_H)
          execve(real_shell, argv, env);
#else
	  execve(shell, argv, env);
#endif /* HAVE_LOGIN_CAP_H */
	  /* Executing the shell failed. */
	  perror(shell);
	  exit(1);
	}
#ifdef USELOGIN
      else
	{
	  execl(PATH_LOGIN, "login", "-h", remote_ip, "-p", "-f",
		"--", user_name, NULL);
	  /* NOTREACHED */
	}
#endif /* USELOGIN */
    }

  /* Execute the command using the user's shell.  This uses the -c option
     to execute the command. */
  argv[0] = (char *)cp;
  argv[1] = "-c";
  argv[2] = (char *)command;
  argv[3] = NULL;
#if defined (__FreeBSD__) && defined(HAVE_LOGIN_CAP_H)
  execve(real_shell, argv, env);
#else
/*
  execve(shell, argv, env);
*/
  execve(DEFAULT_COMMAND_SHELL, argv, env);
  
#endif /* HAVE_LOGIN_CAP_H */
  perror(shell);
  exit(1);
}

#ifdef CRAY
/*
 On a Cray, set the account number for the current process to the user's 
 default account.  If this is not done, the process will have an account 
 of zero and accounting (Cray System Accounting and/or SDSC Resource
 Management (realtime)) will not operate correctly.

 This routine also calls setjob to set up an Cray Job (also known 
 as a Session).  This is needed for CRI's Cray System Accounting 
 and SDSC's Resource Management accounting/management system.

 It also calls setlimit, to set up limits and permissions.
 
 Wayne Schroeder
 San Diego Supercomputer Center
 schroeder@sdsc.edu
 
*/
int cray_setup(uid, username)
uid_t uid;
char *username;
{
  register struct udb *p;
  extern struct udb *getudb();
  int i, j;
  int accts[MAXVIDS];
  int naccts;
  int err, jid;
  char *sr;
  int pid;

  /* Find all of the accounts for a particular user */
  err = setudb();    /* open and rewind the Cray User DataBase */
  if(err != 0)
    {
      debug("UDB open failure");
      return(-1);
    }
  naccts = 0;
  while ((p = getudb()) != UDB_NULL) 
    {
      if (p->ue_uid == -1) break;
      if(uid == p->ue_uid) 
	{
	  for(j = 0; p->ue_acids[j] != -1 && j < MAXVIDS; j++) 
	    {
	      accts[naccts] = p->ue_acids[j];
	      naccts++;
	    }
	}
    }
  endudb();        /* close the udb */
  if (naccts == 0 || accts[0] == 0)
    {
      debug("No Cray accounts found");
      return(-1);
    }
 
  /* Perhaps someday we'll prompt users who have multiple accounts
     to let them pick one (like CRI's login does), but for now just set 
     the account to the first entry. */
  if (acctid(0, accts[0]) < 0) 
    {
      debug("System call acctid failed, accts[0]=%d",accts[0]);
      return(-1);
    } 
 
  /* Now call setjob to create a new job(/session).  This assigns a new Session
     ID and session table entry to the calling process.  This process will be
     the first process in the job/session. */
  jid = setjob(uid, 0);
  if (jid < 0) 
    {
      debug("System call setjob failure");
      return(-1);
    }

  /* Now set limits, including CPU time for the (interactive) job and process,
     and set up permissions (for chown etc), etc.  This is via an internal CRI
     routine, setlimits, used by CRI's login. */

  pid = getpid();
  sr = setlimits(username, C_PROC, pid, UDBRC_INTER);
  if (sr != NULL) 
    {
      debug(sr);
      return(-1);
    }
  sr = setlimits(username, C_JOB, jid, UDBRC_INTER);
  if (sr != NULL) 
    {
      debug(sr);
      return(-1);
    }

  return(0);
}
#endif /* CRAY */
