diff -crN openssh-4.7p1/Makefile openssh-4.7p1-logging/Makefile *** openssh-4.7p1/Makefile Wed Jan 23 15:36:09 2008 --- openssh-4.7p1-logging/Makefile Wed Jan 23 18:18:29 2008 *************** *** 71,77 **** atomicio.o key.o dispatch.o kex.o mac.o uidswap.o uuencode.o misc.o \ monitor_fdpass.o rijndael.o ssh-dss.o ssh-rsa.o dh.o kexdh.o \ kexgex.o kexdhc.o kexgexc.o scard.o msg.o progressmeter.o dns.o \ ! entropy.o scard-opensc.o gss-genr.o umac.o SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ sshconnect.o sshconnect1.o sshconnect2.o --- 71,77 ---- atomicio.o key.o dispatch.o kex.o mac.o uidswap.o uuencode.o misc.o \ monitor_fdpass.o rijndael.o ssh-dss.o ssh-rsa.o dh.o kexdh.o \ kexgex.o kexdhc.o kexgexc.o scard.o msg.o progressmeter.o dns.o \ ! entropy.o scard-opensc.o gss-genr.o umac.o script.o audit.o SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ sshconnect.o sshconnect1.o sshconnect2.o diff -crN openssh-4.7p1/Makefile.in openssh-4.7p1-logging/Makefile.in *** openssh-4.7p1/Makefile.in Mon Jun 11 07:01:42 2007 --- openssh-4.7p1-logging/Makefile.in Wed Jan 23 18:16:32 2008 *************** *** 76,82 **** SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ sshconnect.o sshconnect1.o sshconnect2.o ! SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ sshpty.o sshlogin.o servconf.o serverloop.o \ auth.o auth1.o auth2.o auth-options.o session.o \ auth-chall.o auth2-chall.o groupaccess.o \ --- 76,82 ---- SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ sshconnect.o sshconnect1.o sshconnect2.o ! SSHDOBJS=sshd.o auth-rhosts.o script.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ sshpty.o sshlogin.o servconf.o serverloop.o \ auth.o auth1.o auth2.o auth-options.o session.o \ auth-chall.o auth2-chall.o groupaccess.o \ diff -crN openssh-4.7p1/Makefile.in.orig openssh-4.7p1-logging/Makefile.in.orig *** openssh-4.7p1/Makefile.in.orig Thu Jan 1 02:00:00 1970 --- openssh-4.7p1-logging/Makefile.in.orig Mon Jun 11 07:01:42 2007 *************** *** 0 **** --- 1,435 ---- + # $Id: Makefile.in,v 1.285 2007/06/11 04:01:42 djm Exp $ + + # uncomment if you run a non bourne compatable shell. Ie. csh + #SHELL = @SH@ + + AUTORECONF=autoreconf + + prefix=@prefix@ + exec_prefix=@exec_prefix@ + bindir=@bindir@ + sbindir=@sbindir@ + libexecdir=@libexecdir@ + datadir=@datadir@ + datarootdir=@datarootdir@ + mandir=@mandir@ + mansubdir=@mansubdir@ + sysconfdir=@sysconfdir@ + piddir=@piddir@ + srcdir=@srcdir@ + top_srcdir=@top_srcdir@ + + DESTDIR= + VPATH=@srcdir@ + SSH_PROGRAM=@bindir@/ssh + ASKPASS_PROGRAM=$(libexecdir)/ssh-askpass + SFTP_SERVER=$(libexecdir)/sftp-server + SSH_KEYSIGN=$(libexecdir)/ssh-keysign + RAND_HELPER=$(libexecdir)/ssh-rand-helper + PRIVSEP_PATH=@PRIVSEP_PATH@ + SSH_PRIVSEP_USER=@SSH_PRIVSEP_USER@ + STRIP_OPT=@STRIP_OPT@ + + PATHS= -DSSHDIR=\"$(sysconfdir)\" \ + -D_PATH_SSH_PROGRAM=\"$(SSH_PROGRAM)\" \ + -D_PATH_SSH_ASKPASS_DEFAULT=\"$(ASKPASS_PROGRAM)\" \ + -D_PATH_SFTP_SERVER=\"$(SFTP_SERVER)\" \ + -D_PATH_SSH_KEY_SIGN=\"$(SSH_KEYSIGN)\" \ + -D_PATH_SSH_PIDDIR=\"$(piddir)\" \ + -D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\" \ + -DSSH_RAND_HELPER=\"$(RAND_HELPER)\" + + CC=@CC@ + LD=@LD@ + CFLAGS=@CFLAGS@ + CPPFLAGS=-I. -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@ + LIBS=@LIBS@ + SSHDLIBS=@SSHDLIBS@ + LIBEDIT=@LIBEDIT@ + AR=@AR@ + AWK=@AWK@ + RANLIB=@RANLIB@ + INSTALL=@INSTALL@ + PERL=@PERL@ + SED=@SED@ + ENT=@ENT@ + XAUTH_PATH=@XAUTH_PATH@ + LDFLAGS=-L. -Lopenbsd-compat/ @LDFLAGS@ + EXEEXT=@EXEEXT@ + + INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@ + INSTALL_SSH_RAND_HELPER=@INSTALL_SSH_RAND_HELPER@ + + TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT) + + LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \ + canohost.o channels.o cipher.o cipher-acss.o cipher-aes.o \ + cipher-bf1.o cipher-ctr.o cipher-3des1.o cleanup.o \ + compat.o compress.o crc32.o deattack.o fatal.o hostfile.o \ + log.o match.o md-sha256.o moduli.o nchan.o packet.o \ + readpass.o rsa.o ttymodes.o xmalloc.o \ + atomicio.o key.o dispatch.o kex.o mac.o uidswap.o uuencode.o misc.o \ + monitor_fdpass.o rijndael.o ssh-dss.o ssh-rsa.o dh.o kexdh.o \ + kexgex.o kexdhc.o kexgexc.o scard.o msg.o progressmeter.o dns.o \ + entropy.o scard-opensc.o gss-genr.o umac.o + + SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ + sshconnect.o sshconnect1.o sshconnect2.o + + SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ + sshpty.o sshlogin.o servconf.o serverloop.o \ + auth.o auth1.o auth2.o auth-options.o session.o \ + auth-chall.o auth2-chall.o groupaccess.o \ + auth-skey.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \ + auth2-none.o auth2-passwd.o auth2-pubkey.o \ + monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o \ + auth-krb5.o \ + auth2-gss.o gss-serv.o gss-serv-krb5.o \ + loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ + audit.o audit-bsm.o platform.o + + MANPAGES = scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-rand-helper.8.out ssh-keysign.8.out sshd_config.5.out ssh_config.5.out + MANPAGES_IN = scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-rand-helper.8 ssh-keysign.8 sshd_config.5 ssh_config.5 + MANTYPE = @MANTYPE@ + + CONFIGFILES=sshd_config.out ssh_config.out moduli.out + CONFIGFILES_IN=sshd_config ssh_config moduli + + PATHSUBS = \ + -e 's|/etc/ssh/ssh_prng_cmds|$(sysconfdir)/ssh_prng_cmds|g' \ + -e 's|/etc/ssh/ssh_config|$(sysconfdir)/ssh_config|g' \ + -e 's|/etc/ssh/ssh_known_hosts|$(sysconfdir)/ssh_known_hosts|g' \ + -e 's|/etc/ssh/sshd_config|$(sysconfdir)/sshd_config|g' \ + -e 's|/usr/libexec|$(libexecdir)|g' \ + -e 's|/etc/shosts.equiv|$(sysconfdir)/shosts.equiv|g' \ + -e 's|/etc/ssh/ssh_host_key|$(sysconfdir)/ssh_host_key|g' \ + -e 's|/etc/ssh/ssh_host_dsa_key|$(sysconfdir)/ssh_host_dsa_key|g' \ + -e 's|/etc/ssh/ssh_host_rsa_key|$(sysconfdir)/ssh_host_rsa_key|g' \ + -e 's|/var/run/sshd.pid|$(piddir)/sshd.pid|g' \ + -e 's|/etc/ssh/moduli|$(sysconfdir)/moduli|g' \ + -e 's|/etc/ssh/sshrc|$(sysconfdir)/sshrc|g' \ + -e 's|/usr/X11R6/bin/xauth|$(XAUTH_PATH)|g' \ + -e 's|/var/empty|$(PRIVSEP_PATH)|g' \ + -e 's|/usr/bin:/bin:/usr/sbin:/sbin|@user_path@|g' + + FIXPATHSCMD = $(SED) $(PATHSUBS) + + all: $(CONFIGFILES) ssh_prng_cmds.out $(MANPAGES) $(TARGETS) + + $(LIBSSH_OBJS): Makefile.in config.h + $(SSHOBJS): Makefile.in config.h + $(SSHDOBJS): Makefile.in config.h + + .c.o: + $(CC) $(CFLAGS) $(CPPFLAGS) -c $< + + LIBCOMPAT=openbsd-compat/libopenbsd-compat.a + $(LIBCOMPAT): always + (cd openbsd-compat && $(MAKE)) + always: + + libssh.a: $(LIBSSH_OBJS) + $(AR) rv $@ $(LIBSSH_OBJS) + $(RANLIB) $@ + + ssh$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHOBJS) + $(LD) -o $@ $(SSHOBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) + + sshd$(EXEEXT): libssh.a $(LIBCOMPAT) $(SSHDOBJS) + $(LD) -o $@ $(SSHDOBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(SSHDLIBS) $(LIBS) + + scp$(EXEEXT): $(LIBCOMPAT) libssh.a scp.o progressmeter.o + $(LD) -o $@ scp.o progressmeter.o bufaux.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) + + ssh-add$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-add.o + $(LD) -o $@ ssh-add.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) + + ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o + $(LD) -o $@ ssh-agent.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) + + ssh-keygen$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keygen.o + $(LD) -o $@ ssh-keygen.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) + + ssh-keysign$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keysign.o + $(LD) -o $@ ssh-keysign.o readconf.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) + + ssh-keyscan$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keyscan.o + $(LD) -o $@ ssh-keyscan.o $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS) + + sftp-server$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-common.o sftp-server.o + $(LD) -o $@ sftp-server.o sftp-common.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) + + sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-common.o sftp-glob.o progressmeter.o + $(LD) -o $@ progressmeter.o sftp.o sftp-client.o sftp-common.o sftp-glob.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(LIBEDIT) + + ssh-rand-helper${EXEEXT}: $(LIBCOMPAT) libssh.a ssh-rand-helper.o + $(LD) -o $@ ssh-rand-helper.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) + + # test driver for the loginrec code - not built by default + logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o + $(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS) + + $(MANPAGES): $(MANPAGES_IN) + if test "$(MANTYPE)" = "cat"; then \ + manpage=$(srcdir)/`echo $@ | sed 's/\.[1-9]\.out$$/\.0/'`; \ + else \ + manpage=$(srcdir)/`echo $@ | sed 's/\.out$$//'`; \ + fi; \ + if test "$(MANTYPE)" = "man"; then \ + $(FIXPATHSCMD) $${manpage} | $(AWK) -f $(srcdir)/mdoc2man.awk > $@; \ + else \ + $(FIXPATHSCMD) $${manpage} > $@; \ + fi + + $(CONFIGFILES): $(CONFIGFILES_IN) + conffile=`echo $@ | sed 's/.out$$//'`; \ + $(FIXPATHSCMD) $(srcdir)/$${conffile} > $@ + + ssh_prng_cmds.out: ssh_prng_cmds + if test ! -z "$(INSTALL_SSH_PRNG_CMDS)"; then \ + $(PERL) $(srcdir)/fixprogs ssh_prng_cmds $(ENT); \ + fi + + # fake rule to stop make trying to compile moduli.o into a binary "moduli.o" + moduli: + echo + + clean: regressclean + rm -f *.o *.a $(TARGETS) logintest config.cache config.log + rm -f *.out core survey + (cd openbsd-compat && $(MAKE) clean) + + distclean: regressclean + rm -f *.o *.a $(TARGETS) logintest config.cache config.log + rm -f *.out core opensshd.init openssh.xml + rm -f Makefile buildpkg.sh config.h config.status ssh_prng_cmds + rm -f survey.sh openbsd-compat/regress/Makefile *~ + rm -rf autom4te.cache + (cd openbsd-compat && $(MAKE) distclean) + (cd scard && $(MAKE) distclean) + if test -d pkg ; then \ + rm -fr pkg ; \ + fi + + veryclean: distclean + rm -f configure config.h.in *.0 + + mrproper: veryclean + + realclean: veryclean + + catman-do: + @for f in $(MANPAGES_IN) ; do \ + base=`echo $$f | sed 's/\..*$$//'` ; \ + echo "$$f -> $$base.0" ; \ + nroff -mandoc $$f | cat -v | sed -e 's/.\^H//g' \ + >$$base.0 ; \ + done + + distprep: catman-do + $(AUTORECONF) + -rm -rf autom4te.cache + (cd scard && $(MAKE) -f Makefile.in distprep) + + install: $(CONFIGFILES) ssh_prng_cmds.out $(MANPAGES) $(TARGETS) install-files install-sysconf host-key check-config + install-nokeys: $(CONFIGFILES) ssh_prng_cmds.out $(MANPAGES) $(TARGETS) install-files install-sysconf + install-nosysconf: $(CONFIGFILES) ssh_prng_cmds.out $(MANPAGES) $(TARGETS) install-files + + check-config: + -$(DESTDIR)$(sbindir)/sshd -t -f $(DESTDIR)$(sysconfdir)/sshd_config + + scard-install: + (cd scard && $(MAKE) DESTDIR=$(DESTDIR) install) + + install-files: scard-install + $(srcdir)/mkinstalldirs $(DESTDIR)$(bindir) + $(srcdir)/mkinstalldirs $(DESTDIR)$(sbindir) + $(srcdir)/mkinstalldirs $(DESTDIR)$(mandir) + $(srcdir)/mkinstalldirs $(DESTDIR)$(datadir) + $(srcdir)/mkinstalldirs $(DESTDIR)$(mandir)/$(mansubdir)1 + $(srcdir)/mkinstalldirs $(DESTDIR)$(mandir)/$(mansubdir)5 + $(srcdir)/mkinstalldirs $(DESTDIR)$(mandir)/$(mansubdir)8 + $(srcdir)/mkinstalldirs $(DESTDIR)$(libexecdir) + (umask 022 ; $(srcdir)/mkinstalldirs $(DESTDIR)$(PRIVSEP_PATH)) + $(INSTALL) -m 0755 $(STRIP_OPT) ssh $(DESTDIR)$(bindir)/ssh + $(INSTALL) -m 0755 $(STRIP_OPT) scp $(DESTDIR)$(bindir)/scp + $(INSTALL) -m 0755 $(STRIP_OPT) ssh-add $(DESTDIR)$(bindir)/ssh-add + $(INSTALL) -m 0755 $(STRIP_OPT) ssh-agent $(DESTDIR)$(bindir)/ssh-agent + $(INSTALL) -m 0755 $(STRIP_OPT) ssh-keygen $(DESTDIR)$(bindir)/ssh-keygen + $(INSTALL) -m 0755 $(STRIP_OPT) ssh-keyscan $(DESTDIR)$(bindir)/ssh-keyscan + $(INSTALL) -m 0755 $(STRIP_OPT) sshd $(DESTDIR)$(sbindir)/sshd + if test ! -z "$(INSTALL_SSH_RAND_HELPER)" ; then \ + $(INSTALL) -m 0755 $(STRIP_OPT) ssh-rand-helper $(DESTDIR)$(libexecdir)/ssh-rand-helper ; \ + fi + $(INSTALL) -m 4711 $(STRIP_OPT) ssh-keysign $(DESTDIR)$(SSH_KEYSIGN) + $(INSTALL) -m 0755 $(STRIP_OPT) sftp $(DESTDIR)$(bindir)/sftp + $(INSTALL) -m 0755 $(STRIP_OPT) sftp-server $(DESTDIR)$(SFTP_SERVER) + $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1 + $(INSTALL) -m 644 scp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1 + $(INSTALL) -m 644 ssh-add.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1 + $(INSTALL) -m 644 ssh-agent.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-agent.1 + $(INSTALL) -m 644 ssh-keygen.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1 + $(INSTALL) -m 644 ssh-keyscan.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1 + $(INSTALL) -m 644 sshd_config.5.out $(DESTDIR)$(mandir)/$(mansubdir)5/sshd_config.5 + $(INSTALL) -m 644 ssh_config.5.out $(DESTDIR)$(mandir)/$(mansubdir)5/ssh_config.5 + $(INSTALL) -m 644 sshd.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8 + if [ ! -z "$(INSTALL_SSH_RAND_HELPER)" ]; then \ + $(INSTALL) -m 644 ssh-rand-helper.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-rand-helper.8 ; \ + fi + $(INSTALL) -m 644 sftp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1 + $(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8 + $(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8 + -rm -f $(DESTDIR)$(bindir)/slogin + ln -s ./ssh$(EXEEXT) $(DESTDIR)$(bindir)/slogin + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1 + ln -s ./ssh.1 $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1 + + install-sysconf: + if [ ! -d $(DESTDIR)$(sysconfdir) ]; then \ + $(srcdir)/mkinstalldirs $(DESTDIR)$(sysconfdir); \ + fi + @if [ ! -f $(DESTDIR)$(sysconfdir)/ssh_config ]; then \ + $(INSTALL) -m 644 ssh_config.out $(DESTDIR)$(sysconfdir)/ssh_config; \ + else \ + echo "$(DESTDIR)$(sysconfdir)/ssh_config already exists, install will not overwrite"; \ + fi + @if [ ! -f $(DESTDIR)$(sysconfdir)/sshd_config ]; then \ + $(INSTALL) -m 644 sshd_config.out $(DESTDIR)$(sysconfdir)/sshd_config; \ + else \ + echo "$(DESTDIR)$(sysconfdir)/sshd_config already exists, install will not overwrite"; \ + fi + @if [ -f ssh_prng_cmds ] && [ ! -z "$(INSTALL_SSH_PRNG_CMDS)" ]; then \ + if [ ! -f $(DESTDIR)$(sysconfdir)/ssh_prng_cmds ] ; then \ + $(INSTALL) -m 644 ssh_prng_cmds.out $(DESTDIR)$(sysconfdir)/ssh_prng_cmds; \ + else \ + echo "$(DESTDIR)$(sysconfdir)/ssh_prng_cmds already exists, install will not overwrite"; \ + fi ; \ + fi + @if [ ! -f $(DESTDIR)$(sysconfdir)/moduli ]; then \ + if [ -f $(DESTDIR)$(sysconfdir)/primes ]; then \ + echo "moving $(DESTDIR)$(sysconfdir)/primes to $(DESTDIR)$(sysconfdir)/moduli"; \ + mv "$(DESTDIR)$(sysconfdir)/primes" "$(DESTDIR)$(sysconfdir)/moduli"; \ + else \ + $(INSTALL) -m 644 moduli.out $(DESTDIR)$(sysconfdir)/moduli; \ + fi ; \ + else \ + echo "$(DESTDIR)$(sysconfdir)/moduli already exists, install will not overwrite"; \ + fi + + host-key: ssh-keygen$(EXEEXT) + @if [ -z "$(DESTDIR)" ] ; then \ + if [ -f "$(DESTDIR)$(sysconfdir)/ssh_host_key" ] ; then \ + echo "$(DESTDIR)$(sysconfdir)/ssh_host_key already exists, skipping." ; \ + else \ + ./ssh-keygen -t rsa1 -f $(DESTDIR)$(sysconfdir)/ssh_host_key -N "" ; \ + fi ; \ + if [ -f $(DESTDIR)$(sysconfdir)/ssh_host_dsa_key ] ; then \ + echo "$(DESTDIR)$(sysconfdir)/ssh_host_dsa_key already exists, skipping." ; \ + else \ + ./ssh-keygen -t dsa -f $(DESTDIR)$(sysconfdir)/ssh_host_dsa_key -N "" ; \ + fi ; \ + if [ -f $(DESTDIR)$(sysconfdir)/ssh_host_rsa_key ] ; then \ + echo "$(DESTDIR)$(sysconfdir)/ssh_host_rsa_key already exists, skipping." ; \ + else \ + ./ssh-keygen -t rsa -f $(DESTDIR)$(sysconfdir)/ssh_host_rsa_key -N "" ; \ + fi ; \ + fi ; + + host-key-force: ssh-keygen$(EXEEXT) + ./ssh-keygen -t rsa1 -f $(DESTDIR)$(sysconfdir)/ssh_host_key -N "" + ./ssh-keygen -t dsa -f $(DESTDIR)$(sysconfdir)/ssh_host_dsa_key -N "" + ./ssh-keygen -t rsa -f $(DESTDIR)$(sysconfdir)/ssh_host_rsa_key -N "" + + uninstallall: uninstall + -rm -f $(DESTDIR)$(sysconfdir)/ssh_config + -rm -f $(DESTDIR)$(sysconfdir)/sshd_config + -rm -f $(DESTDIR)$(sysconfdir)/ssh_prng_cmds + -rmdir $(DESTDIR)$(sysconfdir) + -rmdir $(DESTDIR)$(bindir) + -rmdir $(DESTDIR)$(sbindir) + -rmdir $(DESTDIR)$(mandir)/$(mansubdir)1 + -rmdir $(DESTDIR)$(mandir)/$(mansubdir)8 + -rmdir $(DESTDIR)$(mandir) + -rmdir $(DESTDIR)$(libexecdir) + + uninstall: + -rm -f $(DESTDIR)$(bindir)/slogin + -rm -f $(DESTDIR)$(bindir)/ssh$(EXEEXT) + -rm -f $(DESTDIR)$(bindir)/scp$(EXEEXT) + -rm -f $(DESTDIR)$(bindir)/ssh-add$(EXEEXT) + -rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT) + -rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT) + -rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT) + -rm -f $(DESTDIR)$(bindir)/sftp$(EXEEXT) + -rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT) + -rm -r $(DESTDIR)$(SFTP_SERVER)$(EXEEXT) + -rm -f $(DESTDIR)$(SSH_KEYSIGN)$(EXEEXT) + -rm -f $(DESTDIR)$(RAND_HELPER)$(EXEEXT) + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-agent.1 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-rand-helper.8 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1 + + tests: $(TARGETS) + BUILDDIR=`pwd`; \ + [ -d `pwd`/regress ] || mkdir -p `pwd`/regress; \ + [ -f `pwd`/regress/Makefile ] || \ + ln -s `cd $(srcdir) && pwd`/regress/Makefile `pwd`/regress/Makefile ; \ + TEST_SHELL="@TEST_SHELL@"; \ + TEST_SSH_SSH="$${BUILDDIR}/ssh"; \ + TEST_SSH_SSHD="$${BUILDDIR}/sshd"; \ + TEST_SSH_SSHAGENT="$${BUILDDIR}/ssh-agent"; \ + TEST_SSH_SSHADD="$${BUILDDIR}/ssh-add"; \ + TEST_SSH_SSHKEYGEN="$${BUILDDIR}/ssh-keygen"; \ + TEST_SSH_SSHKEYSCAN="$${BUILDDIR}/ssh-keyscan"; \ + TEST_SSH_SFTP="$${BUILDDIR}/sftp"; \ + TEST_SSH_SFTPSERVER="$${BUILDDIR}/sftp-server"; \ + cd $(srcdir)/regress || exit $$?; \ + $(MAKE) \ + .OBJDIR="$${BUILDDIR}/regress" \ + .CURDIR="`pwd`" \ + BUILDDIR="$${BUILDDIR}" \ + OBJ="$${BUILDDIR}/regress/" \ + PATH="$${BUILDDIR}:$${PATH}" \ + TEST_SHELL="$${TEST_SHELL}" \ + TEST_SSH_SSH="$${TEST_SSH_SSH}" \ + TEST_SSH_SSHD="$${TEST_SSH_SSHD}" \ + TEST_SSH_SSHAGENT="$${TEST_SSH_SSHAGENT}" \ + TEST_SSH_SSHADD="$${TEST_SSH_SSHADD}" \ + TEST_SSH_SSHKEYGEN="$${TEST_SSH_SSHKEYGEN}" \ + TEST_SSH_SSHKEYSCAN="$${TEST_SSH_SSHKEYSCAN}" \ + TEST_SSH_SFTP="$${TEST_SSH_SFTP}" \ + TEST_SSH_SFTPSERVER="$${TEST_SSH_SFTPSERVER}" \ + EXEEXT="$(EXEEXT)" \ + $@ + + compat-tests: $(LIBCOMPAT) + (cd openbsd-compat/regress && $(MAKE)) + + regressclean: + if [ -f regress/Makefile ] && [ -r regress/Makefile ]; then \ + (cd regress && $(MAKE) clean) \ + fi + + survey: survey.sh ssh + @$(SHELL) ./survey.sh > survey + @echo 'The survey results have been placed in the file "survey" in the' + @echo 'current directory. Please review the file then send with' + @echo '"make send-survey".' + + send-survey: survey + mail portable-survey@mindrot.org datagram) { buffer_put_string(&c->input, buf, len); } else { + script(buf, len); buffer_append(&c->input, buf, len); } } *************** *** 1616,1621 **** --- 1618,1624 ---- c->self, c->efd); channel_close_fd(&c->efd); } else { + script(buf, len); buffer_append(&c->extended, buf, len); } } diff -crN openssh-4.7p1/channels.c.orig openssh-4.7p1-logging/channels.c.orig *** openssh-4.7p1/channels.c.orig Thu Jan 1 02:00:00 1970 --- openssh-4.7p1-logging/channels.c.orig Mon Jun 25 12:04:47 2007 *************** *** 0 **** --- 1,3237 ---- + /* $OpenBSD: channels.c,v 1.270 2007/06/25 08:20:03 dtucker Exp $ */ + /* + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * This file contains functions for generic socket connection forwarding. + * There is also code for initiating connection forwarding for X11 connections, + * arbitrary tcp/ip connections, and the authentication agent connection. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + * + * SSH2 support added by Markus Friedl. + * Copyright (c) 1999, 2000, 2001, 2002 Markus Friedl. All rights reserved. + * Copyright (c) 1999 Dug Song. All rights reserved. + * Copyright (c) 1999 Theo de Raadt. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + #include "includes.h" + + #include + #include + #include + #include + #ifdef HAVE_SYS_TIME_H + # include + #endif + + #include + #include + + #include + #include + #include + #include + #include + #include + #include + #include + + #include "xmalloc.h" + #include "ssh.h" + #include "ssh1.h" + #include "ssh2.h" + #include "packet.h" + #include "log.h" + #include "misc.h" + #include "buffer.h" + #include "channels.h" + #include "compat.h" + #include "canohost.h" + #include "key.h" + #include "authfd.h" + #include "pathnames.h" + + /* -- channel core */ + + /* + * Pointer to an array containing all allocated channels. The array is + * dynamically extended as needed. + */ + static Channel **channels = NULL; + + /* + * Size of the channel array. All slots of the array must always be + * initialized (at least the type field); unused slots set to NULL + */ + static u_int channels_alloc = 0; + + /* + * Maximum file descriptor value used in any of the channels. This is + * updated in channel_new. + */ + static int channel_max_fd = 0; + + + /* -- tcp forwarding */ + + /* + * Data structure for storing which hosts are permitted for forward requests. + * The local sides of any remote forwards are stored in this array to prevent + * a corrupt remote server from accessing arbitrary TCP/IP ports on our local + * network (which might be behind a firewall). + */ + typedef struct { + char *host_to_connect; /* Connect to 'host'. */ + u_short port_to_connect; /* Connect to 'port'. */ + u_short listen_port; /* Remote side should listen port number. */ + } ForwardPermission; + + /* List of all permitted host/port pairs to connect by the user. */ + static ForwardPermission permitted_opens[SSH_MAX_FORWARDS_PER_DIRECTION]; + + /* List of all permitted host/port pairs to connect by the admin. */ + static ForwardPermission permitted_adm_opens[SSH_MAX_FORWARDS_PER_DIRECTION]; + + /* Number of permitted host/port pairs in the array permitted by the user. */ + static int num_permitted_opens = 0; + + /* Number of permitted host/port pair in the array permitted by the admin. */ + static int num_adm_permitted_opens = 0; + + /* + * If this is true, all opens are permitted. This is the case on the server + * on which we have to trust the client anyway, and the user could do + * anything after logging in anyway. + */ + static int all_opens_permitted = 0; + + + /* -- X11 forwarding */ + + /* Maximum number of fake X11 displays to try. */ + #define MAX_DISPLAYS 1000 + + /* Saved X11 local (client) display. */ + static char *x11_saved_display = NULL; + + /* Saved X11 authentication protocol name. */ + static char *x11_saved_proto = NULL; + + /* Saved X11 authentication data. This is the real data. */ + static char *x11_saved_data = NULL; + static u_int x11_saved_data_len = 0; + + /* + * Fake X11 authentication data. This is what the server will be sending us; + * we should replace any occurrences of this by the real data. + */ + static u_char *x11_fake_data = NULL; + static u_int x11_fake_data_len; + + + /* -- agent forwarding */ + + #define NUM_SOCKS 10 + + /* AF_UNSPEC or AF_INET or AF_INET6 */ + static int IPv4or6 = AF_UNSPEC; + + /* helper */ + static void port_open_helper(Channel *c, char *rtype); + + /* -- channel core */ + + Channel * + channel_by_id(int id) + { + Channel *c; + + if (id < 0 || (u_int)id >= channels_alloc) { + logit("channel_by_id: %d: bad id", id); + return NULL; + } + c = channels[id]; + if (c == NULL) { + logit("channel_by_id: %d: bad id: channel free", id); + return NULL; + } + return c; + } + + /* + * Returns the channel if it is allowed to receive protocol messages. + * Private channels, like listening sockets, may not receive messages. + */ + Channel * + channel_lookup(int id) + { + Channel *c; + + if ((c = channel_by_id(id)) == NULL) + return (NULL); + + switch (c->type) { + case SSH_CHANNEL_X11_OPEN: + case SSH_CHANNEL_LARVAL: + case SSH_CHANNEL_CONNECTING: + case SSH_CHANNEL_DYNAMIC: + case SSH_CHANNEL_OPENING: + case SSH_CHANNEL_OPEN: + case SSH_CHANNEL_INPUT_DRAINING: + case SSH_CHANNEL_OUTPUT_DRAINING: + return (c); + } + logit("Non-public channel %d, type %d.", id, c->type); + return (NULL); + } + + /* + * Register filedescriptors for a channel, used when allocating a channel or + * when the channel consumer/producer is ready, e.g. shell exec'd + */ + static void + channel_register_fds(Channel *c, int rfd, int wfd, int efd, + int extusage, int nonblock) + { + /* Update the maximum file descriptor value. */ + channel_max_fd = MAX(channel_max_fd, rfd); + channel_max_fd = MAX(channel_max_fd, wfd); + channel_max_fd = MAX(channel_max_fd, efd); + + /* XXX set close-on-exec -markus */ + + c->rfd = rfd; + c->wfd = wfd; + c->sock = (rfd == wfd) ? rfd : -1; + c->ctl_fd = -1; /* XXX: set elsewhere */ + c->efd = efd; + c->extended_usage = extusage; + + /* XXX ugly hack: nonblock is only set by the server */ + if (nonblock && isatty(c->rfd)) { + debug2("channel %d: rfd %d isatty", c->self, c->rfd); + c->isatty = 1; + if (!isatty(c->wfd)) { + error("channel %d: wfd %d is not a tty?", + c->self, c->wfd); + } + } else { + c->isatty = 0; + } + c->wfd_isatty = isatty(c->wfd); + + /* enable nonblocking mode */ + if (nonblock) { + if (rfd != -1) + set_nonblock(rfd); + if (wfd != -1) + set_nonblock(wfd); + if (efd != -1) + set_nonblock(efd); + } + } + + /* + * Allocate a new channel object and set its type and socket. This will cause + * remote_name to be freed. + */ + Channel * + channel_new(char *ctype, int type, int rfd, int wfd, int efd, + u_int window, u_int maxpack, int extusage, char *remote_name, int nonblock) + { + int found; + u_int i; + Channel *c; + + /* Do initial allocation if this is the first call. */ + if (channels_alloc == 0) { + channels_alloc = 10; + channels = xcalloc(channels_alloc, sizeof(Channel *)); + for (i = 0; i < channels_alloc; i++) + channels[i] = NULL; + } + /* Try to find a free slot where to put the new channel. */ + for (found = -1, i = 0; i < channels_alloc; i++) + if (channels[i] == NULL) { + /* Found a free slot. */ + found = (int)i; + break; + } + if (found < 0) { + /* There are no free slots. Take last+1 slot and expand the array. */ + found = channels_alloc; + if (channels_alloc > 10000) + fatal("channel_new: internal error: channels_alloc %d " + "too big.", channels_alloc); + channels = xrealloc(channels, channels_alloc + 10, + sizeof(Channel *)); + channels_alloc += 10; + debug2("channel: expanding %d", channels_alloc); + for (i = found; i < channels_alloc; i++) + channels[i] = NULL; + } + /* Initialize and return new channel. */ + c = channels[found] = xcalloc(1, sizeof(Channel)); + buffer_init(&c->input); + buffer_init(&c->output); + buffer_init(&c->extended); + c->ostate = CHAN_OUTPUT_OPEN; + c->istate = CHAN_INPUT_OPEN; + c->flags = 0; + channel_register_fds(c, rfd, wfd, efd, extusage, nonblock); + c->self = found; + c->type = type; + c->ctype = ctype; + c->local_window = window; + c->local_window_max = window; + c->local_consumed = 0; + c->local_maxpacket = maxpack; + c->remote_id = -1; + c->remote_name = xstrdup(remote_name); + c->remote_window = 0; + c->remote_maxpacket = 0; + c->force_drain = 0; + c->single_connection = 0; + c->detach_user = NULL; + c->detach_close = 0; + c->confirm = NULL; + c->confirm_ctx = NULL; + c->input_filter = NULL; + c->output_filter = NULL; + debug("channel %d: new [%s]", found, remote_name); + return c; + } + + static int + channel_find_maxfd(void) + { + u_int i; + int max = 0; + Channel *c; + + for (i = 0; i < channels_alloc; i++) { + c = channels[i]; + if (c != NULL) { + max = MAX(max, c->rfd); + max = MAX(max, c->wfd); + max = MAX(max, c->efd); + } + } + return max; + } + + int + channel_close_fd(int *fdp) + { + int ret = 0, fd = *fdp; + + if (fd != -1) { + ret = close(fd); + *fdp = -1; + if (fd == channel_max_fd) + channel_max_fd = channel_find_maxfd(); + } + return ret; + } + + /* Close all channel fd/socket. */ + static void + channel_close_fds(Channel *c) + { + debug3("channel %d: close_fds r %d w %d e %d c %d", + c->self, c->rfd, c->wfd, c->efd, c->ctl_fd); + + channel_close_fd(&c->sock); + channel_close_fd(&c->ctl_fd); + channel_close_fd(&c->rfd); + channel_close_fd(&c->wfd); + channel_close_fd(&c->efd); + } + + /* Free the channel and close its fd/socket. */ + void + channel_free(Channel *c) + { + char *s; + u_int i, n; + + for (n = 0, i = 0; i < channels_alloc; i++) + if (channels[i]) + n++; + debug("channel %d: free: %s, nchannels %u", c->self, + c->remote_name ? c->remote_name : "???", n); + + s = channel_open_message(); + debug3("channel %d: status: %s", c->self, s); + xfree(s); + + if (c->sock != -1) + shutdown(c->sock, SHUT_RDWR); + if (c->ctl_fd != -1) + shutdown(c->ctl_fd, SHUT_RDWR); + channel_close_fds(c); + buffer_free(&c->input); + buffer_free(&c->output); + buffer_free(&c->extended); + if (c->remote_name) { + xfree(c->remote_name); + c->remote_name = NULL; + } + channels[c->self] = NULL; + xfree(c); + } + + void + channel_free_all(void) + { + u_int i; + + for (i = 0; i < channels_alloc; i++) + if (channels[i] != NULL) + channel_free(channels[i]); + } + + /* + * Closes the sockets/fds of all channels. This is used to close extra file + * descriptors after a fork. + */ + void + channel_close_all(void) + { + u_int i; + + for (i = 0; i < channels_alloc; i++) + if (channels[i] != NULL) + channel_close_fds(channels[i]); + } + + /* + * Stop listening to channels. + */ + void + channel_stop_listening(void) + { + u_int i; + Channel *c; + + for (i = 0; i < channels_alloc; i++) { + c = channels[i]; + if (c != NULL) { + switch (c->type) { + case SSH_CHANNEL_AUTH_SOCKET: + case SSH_CHANNEL_PORT_LISTENER: + case SSH_CHANNEL_RPORT_LISTENER: + case SSH_CHANNEL_X11_LISTENER: + channel_close_fd(&c->sock); + channel_free(c); + break; + } + } + } + } + + /* + * Returns true if no channel has too much buffered data, and false if one or + * more channel is overfull. + */ + int + channel_not_very_much_buffered_data(void) + { + u_int i; + Channel *c; + + for (i = 0; i < channels_alloc; i++) { + c = channels[i]; + if (c != NULL && c->type == SSH_CHANNEL_OPEN) { + #if 0 + if (!compat20 && + buffer_len(&c->input) > packet_get_maxsize()) { + debug2("channel %d: big input buffer %d", + c->self, buffer_len(&c->input)); + return 0; + } + #endif + if (buffer_len(&c->output) > packet_get_maxsize()) { + debug2("channel %d: big output buffer %u > %u", + c->self, buffer_len(&c->output), + packet_get_maxsize()); + return 0; + } + } + } + return 1; + } + + /* Returns true if any channel is still open. */ + int + channel_still_open(void) + { + u_int i; + Channel *c; + + for (i = 0; i < channels_alloc; i++) { + c = channels[i]; + if (c == NULL) + continue; + switch (c->type) { + case SSH_CHANNEL_X11_LISTENER: + case SSH_CHANNEL_PORT_LISTENER: + case SSH_CHANNEL_RPORT_LISTENER: + case SSH_CHANNEL_CLOSED: + case SSH_CHANNEL_AUTH_SOCKET: + case SSH_CHANNEL_DYNAMIC: + case SSH_CHANNEL_CONNECTING: + case SSH_CHANNEL_ZOMBIE: + continue; + case SSH_CHANNEL_LARVAL: + if (!compat20) + fatal("cannot happen: SSH_CHANNEL_LARVAL"); + continue; + case SSH_CHANNEL_OPENING: + case SSH_CHANNEL_OPEN: + case SSH_CHANNEL_X11_OPEN: + return 1; + case SSH_CHANNEL_INPUT_DRAINING: + case SSH_CHANNEL_OUTPUT_DRAINING: + if (!compat13) + fatal("cannot happen: OUT_DRAIN"); + return 1; + default: + fatal("channel_still_open: bad channel type %d", c->type); + /* NOTREACHED */ + } + } + return 0; + } + + /* Returns the id of an open channel suitable for keepaliving */ + int + channel_find_open(void) + { + u_int i; + Channel *c; + + for (i = 0; i < channels_alloc; i++) { + c = channels[i]; + if (c == NULL || c->remote_id < 0) + continue; + switch (c->type) { + case SSH_CHANNEL_CLOSED: + case SSH_CHANNEL_DYNAMIC: + case SSH_CHANNEL_X11_LISTENER: + case SSH_CHANNEL_PORT_LISTENER: + case SSH_CHANNEL_RPORT_LISTENER: + case SSH_CHANNEL_OPENING: + case SSH_CHANNEL_CONNECTING: + case SSH_CHANNEL_ZOMBIE: + continue; + case SSH_CHANNEL_LARVAL: + case SSH_CHANNEL_AUTH_SOCKET: + case SSH_CHANNEL_OPEN: + case SSH_CHANNEL_X11_OPEN: + return i; + case SSH_CHANNEL_INPUT_DRAINING: + case SSH_CHANNEL_OUTPUT_DRAINING: + if (!compat13) + fatal("cannot happen: OUT_DRAIN"); + return i; + default: + fatal("channel_find_open: bad channel type %d", c->type); + /* NOTREACHED */ + } + } + return -1; + } + + + /* + * Returns a message describing the currently open forwarded connections, + * suitable for sending to the client. The message contains crlf pairs for + * newlines. + */ + char * + channel_open_message(void) + { + Buffer buffer; + Channel *c; + char buf[1024], *cp; + u_int i; + + buffer_init(&buffer); + snprintf(buf, sizeof buf, "The following connections are open:\r\n"); + buffer_append(&buffer, buf, strlen(buf)); + for (i = 0; i < channels_alloc; i++) { + c = channels[i]; + if (c == NULL) + continue; + switch (c->type) { + case SSH_CHANNEL_X11_LISTENER: + case SSH_CHANNEL_PORT_LISTENER: + case SSH_CHANNEL_RPORT_LISTENER: + case SSH_CHANNEL_CLOSED: + case SSH_CHANNEL_AUTH_SOCKET: + case SSH_CHANNEL_ZOMBIE: + continue; + case SSH_CHANNEL_LARVAL: + case SSH_CHANNEL_OPENING: + case SSH_CHANNEL_CONNECTING: + case SSH_CHANNEL_DYNAMIC: + case SSH_CHANNEL_OPEN: + case SSH_CHANNEL_X11_OPEN: + case SSH_CHANNEL_INPUT_DRAINING: + case SSH_CHANNEL_OUTPUT_DRAINING: + snprintf(buf, sizeof buf, + " #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d cfd %d)\r\n", + c->self, c->remote_name, + c->type, c->remote_id, + c->istate, buffer_len(&c->input), + c->ostate, buffer_len(&c->output), + c->rfd, c->wfd, c->ctl_fd); + buffer_append(&buffer, buf, strlen(buf)); + continue; + default: + fatal("channel_open_message: bad channel type %d", c->type); + /* NOTREACHED */ + } + } + buffer_append(&buffer, "\0", 1); + cp = xstrdup(buffer_ptr(&buffer)); + buffer_free(&buffer); + return cp; + } + + void + channel_send_open(int id) + { + Channel *c = channel_lookup(id); + + if (c == NULL) { + logit("channel_send_open: %d: bad id", id); + return; + } + debug2("channel %d: send open", id); + packet_start(SSH2_MSG_CHANNEL_OPEN); + packet_put_cstring(c->ctype); + packet_put_int(c->self); + packet_put_int(c->local_window); + packet_put_int(c->local_maxpacket); + packet_send(); + } + + void + channel_request_start(int id, char *service, int wantconfirm) + { + Channel *c = channel_lookup(id); + + if (c == NULL) { + logit("channel_request_start: %d: unknown channel id", id); + return; + } + debug2("channel %d: request %s confirm %d", id, service, wantconfirm); + packet_start(SSH2_MSG_CHANNEL_REQUEST); + packet_put_int(c->remote_id); + packet_put_cstring(service); + packet_put_char(wantconfirm); + } + + void + channel_register_confirm(int id, channel_callback_fn *fn, void *ctx) + { + Channel *c = channel_lookup(id); + + if (c == NULL) { + logit("channel_register_comfirm: %d: bad id", id); + return; + } + c->confirm = fn; + c->confirm_ctx = ctx; + } + + void + channel_register_cleanup(int id, channel_callback_fn *fn, int do_close) + { + Channel *c = channel_by_id(id); + + if (c == NULL) { + logit("channel_register_cleanup: %d: bad id", id); + return; + } + c->detach_user = fn; + c->detach_close = do_close; + } + + void + channel_cancel_cleanup(int id) + { + Channel *c = channel_by_id(id); + + if (c == NULL) { + logit("channel_cancel_cleanup: %d: bad id", id); + return; + } + c->detach_user = NULL; + c->detach_close = 0; + } + + void + channel_register_filter(int id, channel_infilter_fn *ifn, + channel_outfilter_fn *ofn) + { + Channel *c = channel_lookup(id); + + if (c == NULL) { + logit("channel_register_filter: %d: bad id", id); + return; + } + c->input_filter = ifn; + c->output_filter = ofn; + } + + void + channel_set_fds(int id, int rfd, int wfd, int efd, + int extusage, int nonblock, u_int window_max) + { + Channel *c = channel_lookup(id); + + if (c == NULL || c->type != SSH_CHANNEL_LARVAL) + fatal("channel_activate for non-larval channel %d.", id); + channel_register_fds(c, rfd, wfd, efd, extusage, nonblock); + c->type = SSH_CHANNEL_OPEN; + c->local_window = c->local_window_max = window_max; + packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST); + packet_put_int(c->remote_id); + packet_put_int(c->local_window); + packet_send(); + } + + /* + * 'channel_pre*' are called just before select() to add any bits relevant to + * channels in the select bitmasks. + */ + /* + * 'channel_post*': perform any appropriate operations for channels which + * have events pending. + */ + typedef void chan_fn(Channel *c, fd_set *readset, fd_set *writeset); + chan_fn *channel_pre[SSH_CHANNEL_MAX_TYPE]; + chan_fn *channel_post[SSH_CHANNEL_MAX_TYPE]; + + /* ARGSUSED */ + static void + channel_pre_listener(Channel *c, fd_set *readset, fd_set *writeset) + { + FD_SET(c->sock, readset); + } + + /* ARGSUSED */ + static void + channel_pre_connecting(Channel *c, fd_set *readset, fd_set *writeset) + { + debug3("channel %d: waiting for connection", c->self); + FD_SET(c->sock, writeset); + } + + static void + channel_pre_open_13(Channel *c, fd_set *readset, fd_set *writeset) + { + if (buffer_len(&c->input) < packet_get_maxsize()) + FD_SET(c->sock, readset); + if (buffer_len(&c->output) > 0) + FD_SET(c->sock, writeset); + } + + static void + channel_pre_open(Channel *c, fd_set *readset, fd_set *writeset) + { + u_int limit = compat20 ? c->remote_window : packet_get_maxsize(); + + if (c->istate == CHAN_INPUT_OPEN && + limit > 0 && + buffer_len(&c->input) < limit && + buffer_check_alloc(&c->input, CHAN_RBUF)) + FD_SET(c->rfd, readset); + if (c->ostate == CHAN_OUTPUT_OPEN || + c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { + if (buffer_len(&c->output) > 0) { + FD_SET(c->wfd, writeset); + } else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { + if (CHANNEL_EFD_OUTPUT_ACTIVE(c)) + debug2("channel %d: obuf_empty delayed efd %d/(%d)", + c->self, c->efd, buffer_len(&c->extended)); + else + chan_obuf_empty(c); + } + } + /** XXX check close conditions, too */ + if (compat20 && c->efd != -1) { + if (c->extended_usage == CHAN_EXTENDED_WRITE && + buffer_len(&c->extended) > 0) + FD_SET(c->efd, writeset); + else if (!(c->flags & CHAN_EOF_SENT) && + c->extended_usage == CHAN_EXTENDED_READ && + buffer_len(&c->extended) < c->remote_window) + FD_SET(c->efd, readset); + } + /* XXX: What about efd? races? */ + if (compat20 && c->ctl_fd != -1 && + c->istate == CHAN_INPUT_OPEN && c->ostate == CHAN_OUTPUT_OPEN) + FD_SET(c->ctl_fd, readset); + } + + /* ARGSUSED */ + static void + channel_pre_input_draining(Channel *c, fd_set *readset, fd_set *writeset) + { + if (buffer_len(&c->input) == 0) { + packet_start(SSH_MSG_CHANNEL_CLOSE); + packet_put_int(c->remote_id); + packet_send(); + c->type = SSH_CHANNEL_CLOSED; + debug2("channel %d: closing after input drain.", c->self); + } + } + + /* ARGSUSED */ + static void + channel_pre_output_draining(Channel *c, fd_set *readset, fd_set *writeset) + { + if (buffer_len(&c->output) == 0) + chan_mark_dead(c); + else + FD_SET(c->sock, writeset); + } + + /* + * This is a special state for X11 authentication spoofing. An opened X11 + * connection (when authentication spoofing is being done) remains in this + * state until the first packet has been completely read. The authentication + * data in that packet is then substituted by the real data if it matches the + * fake data, and the channel is put into normal mode. + * XXX All this happens at the client side. + * Returns: 0 = need more data, -1 = wrong cookie, 1 = ok + */ + static int + x11_open_helper(Buffer *b) + { + u_char *ucp; + u_int proto_len, data_len; + + /* Check if the fixed size part of the packet is in buffer. */ + if (buffer_len(b) < 12) + return 0; + + /* Parse the lengths of variable-length fields. */ + ucp = buffer_ptr(b); + if (ucp[0] == 0x42) { /* Byte order MSB first. */ + proto_len = 256 * ucp[6] + ucp[7]; + data_len = 256 * ucp[8] + ucp[9]; + } else if (ucp[0] == 0x6c) { /* Byte order LSB first. */ + proto_len = ucp[6] + 256 * ucp[7]; + data_len = ucp[8] + 256 * ucp[9]; + } else { + debug2("Initial X11 packet contains bad byte order byte: 0x%x", + ucp[0]); + return -1; + } + + /* Check if the whole packet is in buffer. */ + if (buffer_len(b) < + 12 + ((proto_len + 3) & ~3) + ((data_len + 3) & ~3)) + return 0; + + /* Check if authentication protocol matches. */ + if (proto_len != strlen(x11_saved_proto) || + memcmp(ucp + 12, x11_saved_proto, proto_len) != 0) { + debug2("X11 connection uses different authentication protocol."); + return -1; + } + /* Check if authentication data matches our fake data. */ + if (data_len != x11_fake_data_len || + memcmp(ucp + 12 + ((proto_len + 3) & ~3), + x11_fake_data, x11_fake_data_len) != 0) { + debug2("X11 auth data does not match fake data."); + return -1; + } + /* Check fake data length */ + if (x11_fake_data_len != x11_saved_data_len) { + error("X11 fake_data_len %d != saved_data_len %d", + x11_fake_data_len, x11_saved_data_len); + return -1; + } + /* + * Received authentication protocol and data match + * our fake data. Substitute the fake data with real + * data. + */ + memcpy(ucp + 12 + ((proto_len + 3) & ~3), + x11_saved_data, x11_saved_data_len); + return 1; + } + + static void + channel_pre_x11_open_13(Channel *c, fd_set *readset, fd_set *writeset) + { + int ret = x11_open_helper(&c->output); + + if (ret == 1) { + /* Start normal processing for the channel. */ + c->type = SSH_CHANNEL_OPEN; + channel_pre_open_13(c, readset, writeset); + } else if (ret == -1) { + /* + * We have received an X11 connection that has bad + * authentication information. + */ + logit("X11 connection rejected because of wrong authentication."); + buffer_clear(&c->input); + buffer_clear(&c->output); + channel_close_fd(&c->sock); + c->sock = -1; + c->type = SSH_CHANNEL_CLOSED; + packet_start(SSH_MSG_CHANNEL_CLOSE); + packet_put_int(c->remote_id); + packet_send(); + } + } + + static void + channel_pre_x11_open(Channel *c, fd_set *readset, fd_set *writeset) + { + int ret = x11_open_helper(&c->output); + + /* c->force_drain = 1; */ + + if (ret == 1) { + c->type = SSH_CHANNEL_OPEN; + channel_pre_open(c, readset, writeset); + } else if (ret == -1) { + logit("X11 connection rejected because of wrong authentication."); + debug2("X11 rejected %d i%d/o%d", c->self, c->istate, c->ostate); + chan_read_failed(c); + buffer_clear(&c->input); + chan_ibuf_empty(c); + buffer_clear(&c->output); + /* for proto v1, the peer will send an IEOF */ + if (compat20) + chan_write_failed(c); + else + c->type = SSH_CHANNEL_OPEN; + debug2("X11 closed %d i%d/o%d", c->self, c->istate, c->ostate); + } + } + + /* try to decode a socks4 header */ + /* ARGSUSED */ + static int + channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset) + { + char *p, *host; + u_int len, have, i, found; + char username[256]; + struct { + u_int8_t version; + u_int8_t command; + u_int16_t dest_port; + struct in_addr dest_addr; + } s4_req, s4_rsp; + + debug2("channel %d: decode socks4", c->self); + + have = buffer_len(&c->input); + len = sizeof(s4_req); + if (have < len) + return 0; + p = buffer_ptr(&c->input); + for (found = 0, i = len; i < have; i++) { + if (p[i] == '\0') { + found = 1; + break; + } + if (i > 1024) { + /* the peer is probably sending garbage */ + debug("channel %d: decode socks4: too long", + c->self); + return -1; + } + } + if (!found) + return 0; + buffer_get(&c->input, (char *)&s4_req.version, 1); + buffer_get(&c->input, (char *)&s4_req.command, 1); + buffer_get(&c->input, (char *)&s4_req.dest_port, 2); + buffer_get(&c->input, (char *)&s4_req.dest_addr, 4); + have = buffer_len(&c->input); + p = buffer_ptr(&c->input); + len = strlen(p); + debug2("channel %d: decode socks4: user %s/%d", c->self, p, len); + if (len > have) + fatal("channel %d: decode socks4: len %d > have %d", + c->self, len, have); + strlcpy(username, p, sizeof(username)); + buffer_consume(&c->input, len); + buffer_consume(&c->input, 1); /* trailing '\0' */ + + host = inet_ntoa(s4_req.dest_addr); + strlcpy(c->path, host, sizeof(c->path)); + c->host_port = ntohs(s4_req.dest_port); + + debug2("channel %d: dynamic request: socks4 host %s port %u command %u", + c->self, host, c->host_port, s4_req.command); + + if (s4_req.command != 1) { + debug("channel %d: cannot handle: socks4 cn %d", + c->self, s4_req.command); + return -1; + } + s4_rsp.version = 0; /* vn: 0 for reply */ + s4_rsp.command = 90; /* cd: req granted */ + s4_rsp.dest_port = 0; /* ignored */ + s4_rsp.dest_addr.s_addr = INADDR_ANY; /* ignored */ + buffer_append(&c->output, &s4_rsp, sizeof(s4_rsp)); + return 1; + } + + /* try to decode a socks5 header */ + #define SSH_SOCKS5_AUTHDONE 0x1000 + #define SSH_SOCKS5_NOAUTH 0x00 + #define SSH_SOCKS5_IPV4 0x01 + #define SSH_SOCKS5_DOMAIN 0x03 + #define SSH_SOCKS5_IPV6 0x04 + #define SSH_SOCKS5_CONNECT 0x01 + #define SSH_SOCKS5_SUCCESS 0x00 + + /* ARGSUSED */ + static int + channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset) + { + struct { + u_int8_t version; + u_int8_t command; + u_int8_t reserved; + u_int8_t atyp; + } s5_req, s5_rsp; + u_int16_t dest_port; + u_char *p, dest_addr[255+1]; + u_int have, need, i, found, nmethods, addrlen, af; + + debug2("channel %d: decode socks5", c->self); + p = buffer_ptr(&c->input); + if (p[0] != 0x05) + return -1; + have = buffer_len(&c->input); + if (!(c->flags & SSH_SOCKS5_AUTHDONE)) { + /* format: ver | nmethods | methods */ + if (have < 2) + return 0; + nmethods = p[1]; + if (have < nmethods + 2) + return 0; + /* look for method: "NO AUTHENTICATION REQUIRED" */ + for (found = 0, i = 2; i < nmethods + 2; i++) { + if (p[i] == SSH_SOCKS5_NOAUTH) { + found = 1; + break; + } + } + if (!found) { + debug("channel %d: method SSH_SOCKS5_NOAUTH not found", + c->self); + return -1; + } + buffer_consume(&c->input, nmethods + 2); + buffer_put_char(&c->output, 0x05); /* version */ + buffer_put_char(&c->output, SSH_SOCKS5_NOAUTH); /* method */ + FD_SET(c->sock, writeset); + c->flags |= SSH_SOCKS5_AUTHDONE; + debug2("channel %d: socks5 auth done", c->self); + return 0; /* need more */ + } + debug2("channel %d: socks5 post auth", c->self); + if (have < sizeof(s5_req)+1) + return 0; /* need more */ + memcpy(&s5_req, p, sizeof(s5_req)); + if (s5_req.version != 0x05 || + s5_req.command != SSH_SOCKS5_CONNECT || + s5_req.reserved != 0x00) { + debug2("channel %d: only socks5 connect supported", c->self); + return -1; + } + switch (s5_req.atyp){ + case SSH_SOCKS5_IPV4: + addrlen = 4; + af = AF_INET; + break; + case SSH_SOCKS5_DOMAIN: + addrlen = p[sizeof(s5_req)]; + af = -1; + break; + case SSH_SOCKS5_IPV6: + addrlen = 16; + af = AF_INET6; + break; + default: + debug2("channel %d: bad socks5 atyp %d", c->self, s5_req.atyp); + return -1; + } + need = sizeof(s5_req) + addrlen + 2; + if (s5_req.atyp == SSH_SOCKS5_DOMAIN) + need++; + if (have < need) + return 0; + buffer_consume(&c->input, sizeof(s5_req)); + if (s5_req.atyp == SSH_SOCKS5_DOMAIN) + buffer_consume(&c->input, 1); /* host string length */ + buffer_get(&c->input, (char *)&dest_addr, addrlen); + buffer_get(&c->input, (char *)&dest_port, 2); + dest_addr[addrlen] = '\0'; + if (s5_req.atyp == SSH_SOCKS5_DOMAIN) + strlcpy(c->path, (char *)dest_addr, sizeof(c->path)); + else if (inet_ntop(af, dest_addr, c->path, sizeof(c->path)) == NULL) + return -1; + c->host_port = ntohs(dest_port); + + debug2("channel %d: dynamic request: socks5 host %s port %u command %u", + c->self, c->path, c->host_port, s5_req.command); + + s5_rsp.version = 0x05; + s5_rsp.command = SSH_SOCKS5_SUCCESS; + s5_rsp.reserved = 0; /* ignored */ + s5_rsp.atyp = SSH_SOCKS5_IPV4; + ((struct in_addr *)&dest_addr)->s_addr = INADDR_ANY; + dest_port = 0; /* ignored */ + + buffer_append(&c->output, &s5_rsp, sizeof(s5_rsp)); + buffer_append(&c->output, &dest_addr, sizeof(struct in_addr)); + buffer_append(&c->output, &dest_port, sizeof(dest_port)); + return 1; + } + + /* dynamic port forwarding */ + static void + channel_pre_dynamic(Channel *c, fd_set *readset, fd_set *writeset) + { + u_char *p; + u_int have; + int ret; + + have = buffer_len(&c->input); + c->delayed = 0; + debug2("channel %d: pre_dynamic: have %d", c->self, have); + /* buffer_dump(&c->input); */ + /* check if the fixed size part of the packet is in buffer. */ + if (have < 3) { + /* need more */ + FD_SET(c->sock, readset); + return; + } + /* try to guess the protocol */ + p = buffer_ptr(&c->input); + switch (p[0]) { + case 0x04: + ret = channel_decode_socks4(c, readset, writeset); + break; + case 0x05: + ret = channel_decode_socks5(c, readset, writeset); + break; + default: + ret = -1; + break; + } + if (ret < 0) { + chan_mark_dead(c); + } else if (ret == 0) { + debug2("channel %d: pre_dynamic: need more", c->self); + /* need more */ + FD_SET(c->sock, readset); + } else { + /* switch to the next state */ + c->type = SSH_CHANNEL_OPENING; + port_open_helper(c, "direct-tcpip"); + } + } + + /* This is our fake X11 server socket. */ + /* ARGSUSED */ + static void + channel_post_x11_listener(Channel *c, fd_set *readset, fd_set *writeset) + { + Channel *nc; + struct sockaddr addr; + int newsock; + socklen_t addrlen; + char buf[16384], *remote_ipaddr; + int remote_port; + + if (FD_ISSET(c->sock, readset)) { + debug("X11 connection requested."); + addrlen = sizeof(addr); + newsock = accept(c->sock, &addr, &addrlen); + if (c->single_connection) { + debug2("single_connection: closing X11 listener."); + channel_close_fd(&c->sock); + chan_mark_dead(c); + } + if (newsock < 0) { + error("accept: %.100s", strerror(errno)); + return; + } + set_nodelay(newsock); + remote_ipaddr = get_peer_ipaddr(newsock); + remote_port = get_peer_port(newsock); + snprintf(buf, sizeof buf, "X11 connection from %.200s port %d", + remote_ipaddr, remote_port); + + nc = channel_new("accepted x11 socket", + SSH_CHANNEL_OPENING, newsock, newsock, -1, + c->local_window_max, c->local_maxpacket, 0, buf, 1); + if (compat20) { + packet_start(SSH2_MSG_CHANNEL_OPEN); + packet_put_cstring("x11"); + packet_put_int(nc->self); + packet_put_int(nc->local_window_max); + packet_put_int(nc->local_maxpacket); + /* originator ipaddr and port */ + packet_put_cstring(remote_ipaddr); + if (datafellows & SSH_BUG_X11FWD) { + debug2("ssh2 x11 bug compat mode"); + } else { + packet_put_int(remote_port); + } + packet_send(); + } else { + packet_start(SSH_SMSG_X11_OPEN); + packet_put_int(nc->self); + if (packet_get_protocol_flags() & + SSH_PROTOFLAG_HOST_IN_FWD_OPEN) + packet_put_cstring(buf); + packet_send(); + } + xfree(remote_ipaddr); + } + } + + static void + port_open_helper(Channel *c, char *rtype) + { + int direct; + char buf[1024]; + char *remote_ipaddr = get_peer_ipaddr(c->sock); + int remote_port = get_peer_port(c->sock); + + direct = (strcmp(rtype, "direct-tcpip") == 0); + + snprintf(buf, sizeof buf, + "%s: listening port %d for %.100s port %d, " + "connect from %.200s port %d", + rtype, c->listening_port, c->path, c->host_port, + remote_ipaddr, remote_port); + + xfree(c->remote_name); + c->remote_name = xstrdup(buf); + + if (compat20) { + packet_start(SSH2_MSG_CHANNEL_OPEN); + packet_put_cstring(rtype); + packet_put_int(c->self); + packet_put_int(c->local_window_max); + packet_put_int(c->local_maxpacket); + if (direct) { + /* target host, port */ + packet_put_cstring(c->path); + packet_put_int(c->host_port); + } else { + /* listen address, port */ + packet_put_cstring(c->path); + packet_put_int(c->listening_port); + } + /* originator host and port */ + packet_put_cstring(remote_ipaddr); + packet_put_int((u_int)remote_port); + packet_send(); + } else { + packet_start(SSH_MSG_PORT_OPEN); + packet_put_int(c->self); + packet_put_cstring(c->path); + packet_put_int(c->host_port); + if (packet_get_protocol_flags() & + SSH_PROTOFLAG_HOST_IN_FWD_OPEN) + packet_put_cstring(c->remote_name); + packet_send(); + } + xfree(remote_ipaddr); + } + + static void + channel_set_reuseaddr(int fd) + { + int on = 1; + + /* + * Set socket options. + * Allow local port reuse in TIME_WAIT. + */ + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) + error("setsockopt SO_REUSEADDR fd %d: %s", fd, strerror(errno)); + } + + /* + * This socket is listening for connections to a forwarded TCP/IP port. + */ + /* ARGSUSED */ + static void + channel_post_port_listener(Channel *c, fd_set *readset, fd_set *writeset) + { + Channel *nc; + struct sockaddr addr; + int newsock, nextstate; + socklen_t addrlen; + char *rtype; + + if (FD_ISSET(c->sock, readset)) { + debug("Connection to port %d forwarding " + "to %.100s port %d requested.", + c->listening_port, c->path, c->host_port); + + if (c->type == SSH_CHANNEL_RPORT_LISTENER) { + nextstate = SSH_CHANNEL_OPENING; + rtype = "forwarded-tcpip"; + } else { + if (c->host_port == 0) { + nextstate = SSH_CHANNEL_DYNAMIC; + rtype = "dynamic-tcpip"; + } else { + nextstate = SSH_CHANNEL_OPENING; + rtype = "direct-tcpip"; + } + } + + addrlen = sizeof(addr); + newsock = accept(c->sock, &addr, &addrlen); + if (newsock < 0) { + error("accept: %.100s", strerror(errno)); + return; + } + set_nodelay(newsock); + nc = channel_new(rtype, nextstate, newsock, newsock, -1, + c->local_window_max, c->local_maxpacket, 0, rtype, 1); + nc->listening_port = c->listening_port; + nc->host_port = c->host_port; + strlcpy(nc->path, c->path, sizeof(nc->path)); + + if (nextstate == SSH_CHANNEL_DYNAMIC) { + /* + * do not call the channel_post handler until + * this flag has been reset by a pre-handler. + * otherwise the FD_ISSET calls might overflow + */ + nc->delayed = 1; + } else { + port_open_helper(nc, rtype); + } + } + } + + /* + * This is the authentication agent socket listening for connections from + * clients. + */ + /* ARGSUSED */ + static void + channel_post_auth_listener(Channel *c, fd_set *readset, fd_set *writeset) + { + Channel *nc; + int newsock; + struct sockaddr addr; + socklen_t addrlen; + + if (FD_ISSET(c->sock, readset)) { + addrlen = sizeof(addr); + newsock = accept(c->sock, &addr, &addrlen); + if (newsock < 0) { + error("accept from auth socket: %.100s", strerror(errno)); + return; + } + nc = channel_new("accepted auth socket", + SSH_CHANNEL_OPENING, newsock, newsock, -1, + c->local_window_max, c->local_maxpacket, + 0, "accepted auth socket", 1); + if (compat20) { + packet_start(SSH2_MSG_CHANNEL_OPEN); + packet_put_cstring("auth-agent@openssh.com"); + packet_put_int(nc->self); + packet_put_int(c->local_window_max); + packet_put_int(c->local_maxpacket); + } else { + packet_start(SSH_SMSG_AGENT_OPEN); + packet_put_int(nc->self); + } + packet_send(); + } + } + + /* ARGSUSED */ + static void + channel_post_connecting(Channel *c, fd_set *readset, fd_set *writeset) + { + int err = 0; + socklen_t sz = sizeof(err); + + if (FD_ISSET(c->sock, writeset)) { + if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &err, &sz) < 0) { + err = errno; + error("getsockopt SO_ERROR failed"); + } + if (err == 0) { + debug("channel %d: connected", c->self); + c->type = SSH_CHANNEL_OPEN; + if (compat20) { + packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + packet_put_int(c->remote_id); + packet_put_int(c->self); + packet_put_int(c->local_window); + packet_put_int(c->local_maxpacket); + } else { + packet_start(SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + packet_put_int(c->remote_id); + packet_put_int(c->self); + } + } else { + debug("channel %d: not connected: %s", + c->self, strerror(err)); + if (compat20) { + packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(c->remote_id); + packet_put_int(SSH2_OPEN_CONNECT_FAILED); + if (!(datafellows & SSH_BUG_OPENFAILURE)) { + packet_put_cstring(strerror(err)); + packet_put_cstring(""); + } + } else { + packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(c->remote_id); + } + chan_mark_dead(c); + } + packet_send(); + } + } + + /* ARGSUSED */ + static int + channel_handle_rfd(Channel *c, fd_set *readset, fd_set *writeset) + { + char buf[CHAN_RBUF]; + int len, force; + + force = c->isatty && c->detach_close && c->istate != CHAN_INPUT_CLOSED; + if (c->rfd != -1 && (force || FD_ISSET(c->rfd, readset))) { + errno = 0; + len = read(c->rfd, buf, sizeof(buf)); + if (len < 0 && (errno == EINTR || (errno == EAGAIN && !force))) + return 1; + #ifndef PTY_ZEROREAD + if (len <= 0) { + #else + if ((!c->isatty && len <= 0) || + (c->isatty && (len < 0 || (len == 0 && errno != 0)))) { + #endif + debug2("channel %d: read<=0 rfd %d len %d", + c->self, c->rfd, len); + if (c->type != SSH_CHANNEL_OPEN) { + debug2("channel %d: not open", c->self); + chan_mark_dead(c); + return -1; + } else if (compat13) { + buffer_clear(&c->output); + c->type = SSH_CHANNEL_INPUT_DRAINING; + debug2("channel %d: input draining.", c->self); + } else { + chan_read_failed(c); + } + return -1; + } + if (c->input_filter != NULL) { + if (c->input_filter(c, buf, len) == -1) { + debug2("channel %d: filter stops", c->self); + chan_read_failed(c); + } + } else if (c->datagram) { + buffer_put_string(&c->input, buf, len); + } else { + buffer_append(&c->input, buf, len); + } + } + return 1; + } + + /* ARGSUSED */ + static int + channel_handle_wfd(Channel *c, fd_set *readset, fd_set *writeset) + { + struct termios tio; + u_char *data = NULL, *buf; + u_int dlen; + int len; + + /* Send buffered output data to the socket. */ + if (c->wfd != -1 && + FD_ISSET(c->wfd, writeset) && + buffer_len(&c->output) > 0) { + if (c->output_filter != NULL) { + if ((buf = c->output_filter(c, &data, &dlen)) == NULL) { + debug2("channel %d: filter stops", c->self); + if (c->type != SSH_CHANNEL_OPEN) + chan_mark_dead(c); + else + chan_write_failed(c); + return -1; + } + } else if (c->datagram) { + buf = data = buffer_get_string(&c->output, &dlen); + } else { + buf = data = buffer_ptr(&c->output); + dlen = buffer_len(&c->output); + } + + if (c->datagram) { + /* ignore truncated writes, datagrams might get lost */ + c->local_consumed += dlen + 4; + len = write(c->wfd, buf, dlen); + xfree(data); + if (len < 0 && (errno == EINTR || errno == EAGAIN)) + return 1; + if (len <= 0) { + if (c->type != SSH_CHANNEL_OPEN) + chan_mark_dead(c); + else + chan_write_failed(c); + return -1; + } + return 1; + } + #ifdef _AIX + /* XXX: Later AIX versions can't push as much data to tty */ + if (compat20 && c->wfd_isatty) + dlen = MIN(dlen, 8*1024); + #endif + + len = write(c->wfd, buf, dlen); + if (len < 0 && (errno == EINTR || errno == EAGAIN)) + return 1; + if (len <= 0) { + if (c->type != SSH_CHANNEL_OPEN) { + debug2("channel %d: not open", c->self); + chan_mark_dead(c); + return -1; + } else if (compat13) { + buffer_clear(&c->output); + debug2("channel %d: input draining.", c->self); + c->type = SSH_CHANNEL_INPUT_DRAINING; + } else { + chan_write_failed(c); + } + return -1; + } + if (compat20 && c->isatty && dlen >= 1 && buf[0] != '\r') { + if (tcgetattr(c->wfd, &tio) == 0 && + !(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { + /* + * Simulate echo to reduce the impact of + * traffic analysis. We need to match the + * size of a SSH2_MSG_CHANNEL_DATA message + * (4 byte channel id + buf) + */ + packet_send_ignore(4 + len); + packet_send(); + } + } + buffer_consume(&c->output, len); + if (compat20 && len > 0) { + c->local_consumed += len; + } + } + return 1; + } + + static int + channel_handle_efd(Channel *c, fd_set *readset, fd_set *writeset) + { + char buf[CHAN_RBUF]; + int len; + + /** XXX handle drain efd, too */ + if (c->efd != -1) { + if (c->extended_usage == CHAN_EXTENDED_WRITE && + FD_ISSET(c->efd, writeset) && + buffer_len(&c->extended) > 0) { + len = write(c->efd, buffer_ptr(&c->extended), + buffer_len(&c->extended)); + debug2("channel %d: written %d to efd %d", + c->self, len, c->efd); + if (len < 0 && (errno == EINTR || errno == EAGAIN)) + return 1; + if (len <= 0) { + debug2("channel %d: closing write-efd %d", + c->self, c->efd); + channel_close_fd(&c->efd); + } else { + buffer_consume(&c->extended, len); + c->local_consumed += len; + } + } else if (c->extended_usage == CHAN_EXTENDED_READ && + (c->detach_close || FD_ISSET(c->efd, readset))) { + len = read(c->efd, buf, sizeof(buf)); + debug2("channel %d: read %d from efd %d", + c->self, len, c->efd); + if (len < 0 && (errno == EINTR || + (errno == EAGAIN && !c->detach_close))) + return 1; + if (len <= 0) { + debug2("channel %d: closing read-efd %d", + c->self, c->efd); + channel_close_fd(&c->efd); + } else { + buffer_append(&c->extended, buf, len); + } + } + } + return 1; + } + + /* ARGSUSED */ + static int + channel_handle_ctl(Channel *c, fd_set *readset, fd_set *writeset) + { + char buf[16]; + int len; + + /* Monitor control fd to detect if the slave client exits */ + if (c->ctl_fd != -1 && FD_ISSET(c->ctl_fd, readset)) { + len = read(c->ctl_fd, buf, sizeof(buf)); + if (len < 0 && (errno == EINTR || errno == EAGAIN)) + return 1; + if (len <= 0) { + debug2("channel %d: ctl read<=0", c->self); + if (c->type != SSH_CHANNEL_OPEN) { + debug2("channel %d: not open", c->self); + chan_mark_dead(c); + return -1; + } else { + chan_read_failed(c); + chan_write_failed(c); + } + return -1; + } else + fatal("%s: unexpected data on ctl fd", __func__); + } + return 1; + } + + static int + channel_check_window(Channel *c) + { + if (c->type == SSH_CHANNEL_OPEN && + !(c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD)) && + ((c->local_window_max - c->local_window > + c->local_maxpacket*3) || + c->local_window < c->local_window_max/2) && + c->local_consumed > 0) { + packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST); + packet_put_int(c->remote_id); + packet_put_int(c->local_consumed); + packet_send(); + debug2("channel %d: window %d sent adjust %d", + c->self, c->local_window, + c->local_consumed); + c->local_window += c->local_consumed; + c->local_consumed = 0; + } + return 1; + } + + static void + channel_post_open(Channel *c, fd_set *readset, fd_set *writeset) + { + if (c->delayed) + return; + channel_handle_rfd(c, readset, writeset); + channel_handle_wfd(c, readset, writeset); + if (!compat20) + return; + channel_handle_efd(c, readset, writeset); + channel_handle_ctl(c, readset, writeset); + channel_check_window(c); + } + + /* ARGSUSED */ + static void + channel_post_output_drain_13(Channel *c, fd_set *readset, fd_set *writeset) + { + int len; + + /* Send buffered output data to the socket. */ + if (FD_ISSET(c->sock, writeset) && buffer_len(&c->output) > 0) { + len = write(c->sock, buffer_ptr(&c->output), + buffer_len(&c->output)); + if (len <= 0) + buffer_clear(&c->output); + else + buffer_consume(&c->output, len); + } + } + + static void + channel_handler_init_20(void) + { + channel_pre[SSH_CHANNEL_OPEN] = &channel_pre_open; + channel_pre[SSH_CHANNEL_X11_OPEN] = &channel_pre_x11_open; + channel_pre[SSH_CHANNEL_PORT_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_RPORT_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_X11_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; + channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; + + channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; + channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; + channel_post[SSH_CHANNEL_RPORT_LISTENER] = &channel_post_port_listener; + channel_post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; + channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; + channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; + channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; + } + + static void + channel_handler_init_13(void) + { + channel_pre[SSH_CHANNEL_OPEN] = &channel_pre_open_13; + channel_pre[SSH_CHANNEL_X11_OPEN] = &channel_pre_x11_open_13; + channel_pre[SSH_CHANNEL_X11_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_PORT_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_INPUT_DRAINING] = &channel_pre_input_draining; + channel_pre[SSH_CHANNEL_OUTPUT_DRAINING] = &channel_pre_output_draining; + channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; + channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; + + channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; + channel_post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; + channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; + channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; + channel_post[SSH_CHANNEL_OUTPUT_DRAINING] = &channel_post_output_drain_13; + channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; + channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; + } + + static void + channel_handler_init_15(void) + { + channel_pre[SSH_CHANNEL_OPEN] = &channel_pre_open; + channel_pre[SSH_CHANNEL_X11_OPEN] = &channel_pre_x11_open; + channel_pre[SSH_CHANNEL_X11_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_PORT_LISTENER] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; + channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; + channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; + + channel_post[SSH_CHANNEL_X11_LISTENER] = &channel_post_x11_listener; + channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; + channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; + channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; + channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; + channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; + } + + static void + channel_handler_init(void) + { + int i; + + for (i = 0; i < SSH_CHANNEL_MAX_TYPE; i++) { + channel_pre[i] = NULL; + channel_post[i] = NULL; + } + if (compat20) + channel_handler_init_20(); + else if (compat13) + channel_handler_init_13(); + else + channel_handler_init_15(); + } + + /* gc dead channels */ + static void + channel_garbage_collect(Channel *c) + { + if (c == NULL) + return; + if (c->detach_user != NULL) { + if (!chan_is_dead(c, c->detach_close)) + return; + debug2("channel %d: gc: notify user", c->self); + c->detach_user(c->self, NULL); + /* if we still have a callback */ + if (c->detach_user != NULL) + return; + debug2("channel %d: gc: user detached", c->self); + } + if (!chan_is_dead(c, 1)) + return; + debug2("channel %d: garbage collecting", c->self); + channel_free(c); + } + + static void + channel_handler(chan_fn *ftab[], fd_set *readset, fd_set *writeset) + { + static int did_init = 0; + u_int i; + Channel *c; + + if (!did_init) { + channel_handler_init(); + did_init = 1; + } + for (i = 0; i < channels_alloc; i++) { + c = channels[i]; + if (c == NULL) + continue; + if (ftab[c->type] != NULL) + (*ftab[c->type])(c, readset, writeset); + channel_garbage_collect(c); + } + } + + /* + * Allocate/update select bitmasks and add any bits relevant to channels in + * select bitmasks. + */ + void + channel_prepare_select(fd_set **readsetp, fd_set **writesetp, int *maxfdp, + u_int *nallocp, int rekeying) + { + u_int n, sz, nfdset; + + n = MAX(*maxfdp, channel_max_fd); + + nfdset = howmany(n+1, NFDBITS); + /* Explicitly test here, because xrealloc isn't always called */ + if (nfdset && SIZE_T_MAX / nfdset < sizeof(fd_mask)) + fatal("channel_prepare_select: max_fd (%d) is too large", n); + sz = nfdset * sizeof(fd_mask); + + /* perhaps check sz < nalloc/2 and shrink? */ + if (*readsetp == NULL || sz > *nallocp) { + *readsetp = xrealloc(*readsetp, nfdset, sizeof(fd_mask)); + *writesetp = xrealloc(*writesetp, nfdset, sizeof(fd_mask)); + *nallocp = sz; + } + *maxfdp = n; + memset(*readsetp, 0, sz); + memset(*writesetp, 0, sz); + + if (!rekeying) + channel_handler(channel_pre, *readsetp, *writesetp); + } + + /* + * After select, perform any appropriate operations for channels which have + * events pending. + */ + void + channel_after_select(fd_set *readset, fd_set *writeset) + { + channel_handler(channel_post, readset, writeset); + } + + + /* If there is data to send to the connection, enqueue some of it now. */ + void + channel_output_poll(void) + { + Channel *c; + u_int i, len; + + for (i = 0; i < channels_alloc; i++) { + c = channels[i]; + if (c == NULL) + continue; + + /* + * We are only interested in channels that can have buffered + * incoming data. + */ + if (compat13) { + if (c->type != SSH_CHANNEL_OPEN && + c->type != SSH_CHANNEL_INPUT_DRAINING) + continue; + } else { + if (c->type != SSH_CHANNEL_OPEN) + continue; + } + if (compat20 && + (c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD))) { + /* XXX is this true? */ + debug3("channel %d: will not send data after close", c->self); + continue; + } + + /* Get the amount of buffered data for this channel. */ + if ((c->istate == CHAN_INPUT_OPEN || + c->istate == CHAN_INPUT_WAIT_DRAIN) && + (len = buffer_len(&c->input)) > 0) { + if (c->datagram) { + if (len > 0) { + u_char *data; + u_int dlen; + + data = buffer_get_string(&c->input, + &dlen); + packet_start(SSH2_MSG_CHANNEL_DATA); + packet_put_int(c->remote_id); + packet_put_string(data, dlen); + packet_send(); + c->remote_window -= dlen + 4; + xfree(data); + } + continue; + } + /* + * Send some data for the other side over the secure + * connection. + */ + if (compat20) { + if (len > c->remote_window) + len = c->remote_window; + if (len > c->remote_maxpacket) + len = c->remote_maxpacket; + } else { + if (packet_is_interactive()) { + if (len > 1024) + len = 512; + } else { + /* Keep the packets at reasonable size. */ + if (len > packet_get_maxsize()/2) + len = packet_get_maxsize()/2; + } + } + if (len > 0) { + packet_start(compat20 ? + SSH2_MSG_CHANNEL_DATA : SSH_MSG_CHANNEL_DATA); + packet_put_int(c->remote_id); + packet_put_string(buffer_ptr(&c->input), len); + packet_send(); + buffer_consume(&c->input, len); + c->remote_window -= len; + } + } else if (c->istate == CHAN_INPUT_WAIT_DRAIN) { + if (compat13) + fatal("cannot happen: istate == INPUT_WAIT_DRAIN for proto 1.3"); + /* + * input-buffer is empty and read-socket shutdown: + * tell peer, that we will not send more data: send IEOF. + * hack for extended data: delay EOF if EFD still in use. + */ + if (CHANNEL_EFD_INPUT_ACTIVE(c)) + debug2("channel %d: ibuf_empty delayed efd %d/(%d)", + c->self, c->efd, buffer_len(&c->extended)); + else + chan_ibuf_empty(c); + } + /* Send extended data, i.e. stderr */ + if (compat20 && + !(c->flags & CHAN_EOF_SENT) && + c->remote_window > 0 && + (len = buffer_len(&c->extended)) > 0 && + c->extended_usage == CHAN_EXTENDED_READ) { + debug2("channel %d: rwin %u elen %u euse %d", + c->self, c->remote_window, buffer_len(&c->extended), + c->extended_usage); + if (len > c->remote_window) + len = c->remote_window; + if (len > c->remote_maxpacket) + len = c->remote_maxpacket; + packet_start(SSH2_MSG_CHANNEL_EXTENDED_DATA); + packet_put_int(c->remote_id); + packet_put_int(SSH2_EXTENDED_DATA_STDERR); + packet_put_string(buffer_ptr(&c->extended), len); + packet_send(); + buffer_consume(&c->extended, len); + c->remote_window -= len; + debug2("channel %d: sent ext data %d", c->self, len); + } + } + } + + + /* -- protocol input */ + + /* ARGSUSED */ + void + channel_input_data(int type, u_int32_t seq, void *ctxt) + { + int id; + char *data; + u_int data_len; + Channel *c; + + /* Get the channel number and verify it. */ + id = packet_get_int(); + c = channel_lookup(id); + if (c == NULL) + packet_disconnect("Received data for nonexistent channel %d.", id); + + /* Ignore any data for non-open channels (might happen on close) */ + if (c->type != SSH_CHANNEL_OPEN && + c->type != SSH_CHANNEL_X11_OPEN) + return; + + /* Get the data. */ + data = packet_get_string(&data_len); + + /* + * Ignore data for protocol > 1.3 if output end is no longer open. + * For protocol 2 the sending side is reducing its window as it sends + * data, so we must 'fake' consumption of the data in order to ensure + * that window updates are sent back. Otherwise the connection might + * deadlock. + */ + if (!compat13 && c->ostate != CHAN_OUTPUT_OPEN) { + if (compat20) { + c->local_window -= data_len; + c->local_consumed += data_len; + } + xfree(data); + return; + } + + if (compat20) { + if (data_len > c->local_maxpacket) { + logit("channel %d: rcvd big packet %d, maxpack %d", + c->self, data_len, c->local_maxpacket); + } + if (data_len > c->local_window) { + logit("channel %d: rcvd too much data %d, win %d", + c->self, data_len, c->local_window); + xfree(data); + return; + } + c->local_window -= data_len; + } + packet_check_eom(); + if (c->datagram) + buffer_put_string(&c->output, data, data_len); + else + buffer_append(&c->output, data, data_len); + xfree(data); + } + + /* ARGSUSED */ + void + channel_input_extended_data(int type, u_int32_t seq, void *ctxt) + { + int id; + char *data; + u_int data_len, tcode; + Channel *c; + + /* Get the channel number and verify it. */ + id = packet_get_int(); + c = channel_lookup(id); + + if (c == NULL) + packet_disconnect("Received extended_data for bad channel %d.", id); + if (c->type != SSH_CHANNEL_OPEN) { + logit("channel %d: ext data for non open", id); + return; + } + if (c->flags & CHAN_EOF_RCVD) { + if (datafellows & SSH_BUG_EXTEOF) + debug("channel %d: accepting ext data after eof", id); + else + packet_disconnect("Received extended_data after EOF " + "on channel %d.", id); + } + tcode = packet_get_int(); + if (c->efd == -1 || + c->extended_usage != CHAN_EXTENDED_WRITE || + tcode != SSH2_EXTENDED_DATA_STDERR) { + logit("channel %d: bad ext data", c->self); + return; + } + data = packet_get_string(&data_len); + packet_check_eom(); + if (data_len > c->local_window) { + logit("channel %d: rcvd too much extended_data %d, win %d", + c->self, data_len, c->local_window); + xfree(data); + return; + } + debug2("channel %d: rcvd ext data %d", c->self, data_len); + c->local_window -= data_len; + buffer_append(&c->extended, data, data_len); + xfree(data); + } + + /* ARGSUSED */ + void + channel_input_ieof(int type, u_int32_t seq, void *ctxt) + { + int id; + Channel *c; + + id = packet_get_int(); + packet_check_eom(); + c = channel_lookup(id); + if (c == NULL) + packet_disconnect("Received ieof for nonexistent channel %d.", id); + chan_rcvd_ieof(c); + + /* XXX force input close */ + if (c->force_drain && c->istate == CHAN_INPUT_OPEN) { + debug("channel %d: FORCE input drain", c->self); + c->istate = CHAN_INPUT_WAIT_DRAIN; + if (buffer_len(&c->input) == 0) + chan_ibuf_empty(c); + } + + } + + /* ARGSUSED */ + void + channel_input_close(int type, u_int32_t seq, void *ctxt) + { + int id; + Channel *c; + + id = packet_get_int(); + packet_check_eom(); + c = channel_lookup(id); + if (c == NULL) + packet_disconnect("Received close for nonexistent channel %d.", id); + + /* + * Send a confirmation that we have closed the channel and no more + * data is coming for it. + */ + packet_start(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION); + packet_put_int(c->remote_id); + packet_send(); + + /* + * If the channel is in closed state, we have sent a close request, + * and the other side will eventually respond with a confirmation. + * Thus, we cannot free the channel here, because then there would be + * no-one to receive the confirmation. The channel gets freed when + * the confirmation arrives. + */ + if (c->type != SSH_CHANNEL_CLOSED) { + /* + * Not a closed channel - mark it as draining, which will + * cause it to be freed later. + */ + buffer_clear(&c->input); + c->type = SSH_CHANNEL_OUTPUT_DRAINING; + } + } + + /* proto version 1.5 overloads CLOSE_CONFIRMATION with OCLOSE */ + /* ARGSUSED */ + void + channel_input_oclose(int type, u_int32_t seq, void *ctxt) + { + int id = packet_get_int(); + Channel *c = channel_lookup(id); + + packet_check_eom(); + if (c == NULL) + packet_disconnect("Received oclose for nonexistent channel %d.", id); + chan_rcvd_oclose(c); + } + + /* ARGSUSED */ + void + channel_input_close_confirmation(int type, u_int32_t seq, void *ctxt) + { + int id = packet_get_int(); + Channel *c = channel_lookup(id); + + packet_check_eom(); + if (c == NULL) + packet_disconnect("Received close confirmation for " + "out-of-range channel %d.", id); + if (c->type != SSH_CHANNEL_CLOSED) + packet_disconnect("Received close confirmation for " + "non-closed channel %d (type %d).", id, c->type); + channel_free(c); + } + + /* ARGSUSED */ + void + channel_input_open_confirmation(int type, u_int32_t seq, void *ctxt) + { + int id, remote_id; + Channel *c; + + id = packet_get_int(); + c = channel_lookup(id); + + if (c==NULL || c->type != SSH_CHANNEL_OPENING) + packet_disconnect("Received open confirmation for " + "non-opening channel %d.", id); + remote_id = packet_get_int(); + /* Record the remote channel number and mark that the channel is now open. */ + c->remote_id = remote_id; + c->type = SSH_CHANNEL_OPEN; + + if (compat20) { + c->remote_window = packet_get_int(); + c->remote_maxpacket = packet_get_int(); + if (c->confirm) { + debug2("callback start"); + c->confirm(c->self, c->confirm_ctx); + debug2("callback done"); + } + debug2("channel %d: open confirm rwindow %u rmax %u", c->self, + c->remote_window, c->remote_maxpacket); + } + packet_check_eom(); + } + + static char * + reason2txt(int reason) + { + switch (reason) { + case SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED: + return "administratively prohibited"; + case SSH2_OPEN_CONNECT_FAILED: + return "connect failed"; + case SSH2_OPEN_UNKNOWN_CHANNEL_TYPE: + return "unknown channel type"; + case SSH2_OPEN_RESOURCE_SHORTAGE: + return "resource shortage"; + } + return "unknown reason"; + } + + /* ARGSUSED */ + void + channel_input_open_failure(int type, u_int32_t seq, void *ctxt) + { + int id, reason; + char *msg = NULL, *lang = NULL; + Channel *c; + + id = packet_get_int(); + c = channel_lookup(id); + + if (c==NULL || c->type != SSH_CHANNEL_OPENING) + packet_disconnect("Received open failure for " + "non-opening channel %d.", id); + if (compat20) { + reason = packet_get_int(); + if (!(datafellows & SSH_BUG_OPENFAILURE)) { + msg = packet_get_string(NULL); + lang = packet_get_string(NULL); + } + logit("channel %d: open failed: %s%s%s", id, + reason2txt(reason), msg ? ": ": "", msg ? msg : ""); + if (msg != NULL) + xfree(msg); + if (lang != NULL) + xfree(lang); + } + packet_check_eom(); + /* Free the channel. This will also close the socket. */ + channel_free(c); + } + + /* ARGSUSED */ + void + channel_input_window_adjust(int type, u_int32_t seq, void *ctxt) + { + Channel *c; + int id; + u_int adjust; + + if (!compat20) + return; + + /* Get the channel number and verify it. */ + id = packet_get_int(); + c = channel_lookup(id); + + if (c == NULL) { + logit("Received window adjust for non-open channel %d.", id); + return; + } + adjust = packet_get_int(); + packet_check_eom(); + debug2("channel %d: rcvd adjust %u", id, adjust); + c->remote_window += adjust; + } + + /* ARGSUSED */ + void + channel_input_port_open(int type, u_int32_t seq, void *ctxt) + { + Channel *c = NULL; + u_short host_port; + char *host, *originator_string; + int remote_id, sock = -1; + + remote_id = packet_get_int(); + host = packet_get_string(NULL); + host_port = packet_get_int(); + + if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) { + originator_string = packet_get_string(NULL); + } else { + originator_string = xstrdup("unknown (remote did not supply name)"); + } + packet_check_eom(); + sock = channel_connect_to(host, host_port); + if (sock != -1) { + c = channel_new("connected socket", + SSH_CHANNEL_CONNECTING, sock, sock, -1, 0, 0, 0, + originator_string, 1); + c->remote_id = remote_id; + } + xfree(originator_string); + if (c == NULL) { + packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(remote_id); + packet_send(); + } + xfree(host); + } + + + /* -- tcp forwarding */ + + void + channel_set_af(int af) + { + IPv4or6 = af; + } + + static int + channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_port, + const char *host_to_connect, u_short port_to_connect, int gateway_ports) + { + Channel *c; + int sock, r, success = 0, wildcard = 0, is_client; + struct addrinfo hints, *ai, *aitop; + const char *host, *addr; + char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + + host = (type == SSH_CHANNEL_RPORT_LISTENER) ? + listen_addr : host_to_connect; + is_client = (type == SSH_CHANNEL_PORT_LISTENER); + + if (host == NULL) { + error("No forward host name."); + return 0; + } + if (strlen(host) > SSH_CHANNEL_PATH_LEN - 1) { + error("Forward host name too long."); + return 0; + } + + /* + * Determine whether or not a port forward listens to loopback, + * specified address or wildcard. On the client, a specified bind + * address will always override gateway_ports. On the server, a + * gateway_ports of 1 (``yes'') will override the client's + * specification and force a wildcard bind, whereas a value of 2 + * (``clientspecified'') will bind to whatever address the client + * asked for. + * + * Special-case listen_addrs are: + * + * "0.0.0.0" -> wildcard v4/v6 if SSH_OLD_FORWARD_ADDR + * "" (empty string), "*" -> wildcard v4/v6 + * "localhost" -> loopback v4/v6 + */ + addr = NULL; + if (listen_addr == NULL) { + /* No address specified: default to gateway_ports setting */ + if (gateway_ports) + wildcard = 1; + } else if (gateway_ports || is_client) { + if (((datafellows & SSH_OLD_FORWARD_ADDR) && + strcmp(listen_addr, "0.0.0.0") == 0) || + *listen_addr == '\0' || strcmp(listen_addr, "*") == 0 || + (!is_client && gateway_ports == 1)) + wildcard = 1; + else if (strcmp(listen_addr, "localhost") != 0) + addr = listen_addr; + } + + debug3("channel_setup_fwd_listener: type %d wildcard %d addr %s", + type, wildcard, (addr == NULL) ? "NULL" : addr); + + /* + * getaddrinfo returns a loopback address if the hostname is + * set to NULL and hints.ai_flags is not AI_PASSIVE + */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_flags = wildcard ? AI_PASSIVE : 0; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof strport, "%d", listen_port); + if ((r = getaddrinfo(addr, strport, &hints, &aitop)) != 0) { + if (addr == NULL) { + /* This really shouldn't happen */ + packet_disconnect("getaddrinfo: fatal error: %s", + gai_strerror(r)); + } else { + error("channel_setup_fwd_listener: " + "getaddrinfo(%.64s): %s", addr, gai_strerror(r)); + } + return 0; + } + + for (ai = aitop; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), + strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { + error("channel_setup_fwd_listener: getnameinfo failed"); + continue; + } + /* Create a port to listen for the host. */ + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) { + /* this is no error since kernel may not support ipv6 */ + verbose("socket: %.100s", strerror(errno)); + continue; + } + + channel_set_reuseaddr(sock); + + debug("Local forwarding listening on %s port %s.", ntop, strport); + + /* Bind the socket to the address. */ + if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { + /* address can be in use ipv6 address is already bound */ + if (!ai->ai_next) + error("bind: %.100s", strerror(errno)); + else + verbose("bind: %.100s", strerror(errno)); + + close(sock); + continue; + } + /* Start listening for connections on the socket. */ + if (listen(sock, SSH_LISTEN_BACKLOG) < 0) { + error("listen: %.100s", strerror(errno)); + close(sock); + continue; + } + /* Allocate a channel number for the socket. */ + c = channel_new("port listener", type, sock, sock, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, + 0, "port listener", 1); + strlcpy(c->path, host, sizeof(c->path)); + c->host_port = port_to_connect; + c->listening_port = listen_port; + success = 1; + } + if (success == 0) + error("channel_setup_fwd_listener: cannot listen to port: %d", + listen_port); + freeaddrinfo(aitop); + return success; + } + + int + channel_cancel_rport_listener(const char *host, u_short port) + { + u_int i; + int found = 0; + + for (i = 0; i < channels_alloc; i++) { + Channel *c = channels[i]; + + if (c != NULL && c->type == SSH_CHANNEL_RPORT_LISTENER && + strncmp(c->path, host, sizeof(c->path)) == 0 && + c->listening_port == port) { + debug2("%s: close channel %d", __func__, i); + channel_free(c); + found = 1; + } + } + + return (found); + } + + /* protocol local port fwd, used by ssh (and sshd in v1) */ + int + channel_setup_local_fwd_listener(const char *listen_host, u_short listen_port, + const char *host_to_connect, u_short port_to_connect, int gateway_ports) + { + return channel_setup_fwd_listener(SSH_CHANNEL_PORT_LISTENER, + listen_host, listen_port, host_to_connect, port_to_connect, + gateway_ports); + } + + /* protocol v2 remote port fwd, used by sshd */ + int + channel_setup_remote_fwd_listener(const char *listen_address, + u_short listen_port, int gateway_ports) + { + return channel_setup_fwd_listener(SSH_CHANNEL_RPORT_LISTENER, + listen_address, listen_port, NULL, 0, gateway_ports); + } + + /* + * Initiate forwarding of connections to port "port" on remote host through + * the secure channel to host:port from local side. + */ + + int + channel_request_remote_forwarding(const char *listen_host, u_short listen_port, + const char *host_to_connect, u_short port_to_connect) + { + int type, success = 0; + + /* Record locally that connection to this host/port is permitted. */ + if (num_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION) + fatal("channel_request_remote_forwarding: too many forwards"); + + /* Send the forward request to the remote side. */ + if (compat20) { + const char *address_to_bind; + if (listen_host == NULL) { + if (datafellows & SSH_BUG_RFWD_ADDR) + address_to_bind = "127.0.0.1"; + else + address_to_bind = "localhost"; + } else if (*listen_host == '\0' || + strcmp(listen_host, "*") == 0) { + if (datafellows & SSH_BUG_RFWD_ADDR) + address_to_bind = "0.0.0.0"; + else + address_to_bind = ""; + } else + address_to_bind = listen_host; + + packet_start(SSH2_MSG_GLOBAL_REQUEST); + packet_put_cstring("tcpip-forward"); + packet_put_char(1); /* boolean: want reply */ + packet_put_cstring(address_to_bind); + packet_put_int(listen_port); + packet_send(); + packet_write_wait(); + /* Assume that server accepts the request */ + success = 1; + } else { + packet_start(SSH_CMSG_PORT_FORWARD_REQUEST); + packet_put_int(listen_port); + packet_put_cstring(host_to_connect); + packet_put_int(port_to_connect); + packet_send(); + packet_write_wait(); + + /* Wait for response from the remote side. */ + type = packet_read(); + switch (type) { + case SSH_SMSG_SUCCESS: + success = 1; + break; + case SSH_SMSG_FAILURE: + break; + default: + /* Unknown packet */ + packet_disconnect("Protocol error for port forward request:" + "received packet type %d.", type); + } + } + if (success) { + permitted_opens[num_permitted_opens].host_to_connect = xstrdup(host_to_connect); + permitted_opens[num_permitted_opens].port_to_connect = port_to_connect; + permitted_opens[num_permitted_opens].listen_port = listen_port; + num_permitted_opens++; + } + return (success ? 0 : -1); + } + + /* + * Request cancellation of remote forwarding of connection host:port from + * local side. + */ + void + channel_request_rforward_cancel(const char *host, u_short port) + { + int i; + + if (!compat20) + return; + + for (i = 0; i < num_permitted_opens; i++) { + if (permitted_opens[i].host_to_connect != NULL && + permitted_opens[i].listen_port == port) + break; + } + if (i >= num_permitted_opens) { + debug("%s: requested forward not found", __func__); + return; + } + packet_start(SSH2_MSG_GLOBAL_REQUEST); + packet_put_cstring("cancel-tcpip-forward"); + packet_put_char(0); + packet_put_cstring(host == NULL ? "" : host); + packet_put_int(port); + packet_send(); + + permitted_opens[i].listen_port = 0; + permitted_opens[i].port_to_connect = 0; + xfree(permitted_opens[i].host_to_connect); + permitted_opens[i].host_to_connect = NULL; + } + + /* + * This is called after receiving CHANNEL_FORWARDING_REQUEST. This initates + * listening for the port, and sends back a success reply (or disconnect + * message if there was an error). + */ + int + channel_input_port_forward_request(int is_root, int gateway_ports) + { + u_short port, host_port; + int success = 0; + char *hostname; + + /* Get arguments from the packet. */ + port = packet_get_int(); + hostname = packet_get_string(NULL); + host_port = packet_get_int(); + + #ifndef HAVE_CYGWIN + /* + * Check that an unprivileged user is not trying to forward a + * privileged port. + */ + if (port < IPPORT_RESERVED && !is_root) + packet_disconnect( + "Requested forwarding of port %d but user is not root.", + port); + if (host_port == 0) + packet_disconnect("Dynamic forwarding denied."); + #endif + + /* Initiate forwarding */ + success = channel_setup_local_fwd_listener(NULL, port, hostname, + host_port, gateway_ports); + + /* Free the argument string. */ + xfree(hostname); + + return (success ? 0 : -1); + } + + /* + * Permits opening to any host/port if permitted_opens[] is empty. This is + * usually called by the server, because the user could connect to any port + * anyway, and the server has no way to know but to trust the client anyway. + */ + void + channel_permit_all_opens(void) + { + if (num_permitted_opens == 0) + all_opens_permitted = 1; + } + + void + channel_add_permitted_opens(char *host, int port) + { + if (num_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION) + fatal("channel_add_permitted_opens: too many forwards"); + debug("allow port forwarding to host %s port %d", host, port); + + permitted_opens[num_permitted_opens].host_to_connect = xstrdup(host); + permitted_opens[num_permitted_opens].port_to_connect = port; + num_permitted_opens++; + + all_opens_permitted = 0; + } + + int + channel_add_adm_permitted_opens(char *host, int port) + { + if (num_adm_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION) + fatal("channel_add_adm_permitted_opens: too many forwards"); + debug("config allows port forwarding to host %s port %d", host, port); + + permitted_adm_opens[num_adm_permitted_opens].host_to_connect + = xstrdup(host); + permitted_adm_opens[num_adm_permitted_opens].port_to_connect = port; + return ++num_adm_permitted_opens; + } + + void + channel_clear_permitted_opens(void) + { + int i; + + for (i = 0; i < num_permitted_opens; i++) + if (permitted_opens[i].host_to_connect != NULL) + xfree(permitted_opens[i].host_to_connect); + num_permitted_opens = 0; + } + + void + channel_clear_adm_permitted_opens(void) + { + int i; + + for (i = 0; i < num_adm_permitted_opens; i++) + if (permitted_adm_opens[i].host_to_connect != NULL) + xfree(permitted_adm_opens[i].host_to_connect); + num_adm_permitted_opens = 0; + } + + /* return socket to remote host, port */ + static int + connect_to(const char *host, u_short port) + { + struct addrinfo hints, *ai, *aitop; + char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + int gaierr; + int sock = -1; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof strport, "%d", port); + if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) { + error("connect_to %.100s: unknown host (%s)", host, + gai_strerror(gaierr)); + return -1; + } + for (ai = aitop; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), + strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { + error("connect_to: getnameinfo failed"); + continue; + } + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) { + if (ai->ai_next == NULL) + error("socket: %.100s", strerror(errno)); + else + verbose("socket: %.100s", strerror(errno)); + continue; + } + if (set_nonblock(sock) == -1) + fatal("%s: set_nonblock(%d)", __func__, sock); + if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0 && + errno != EINPROGRESS) { + error("connect_to %.100s port %s: %.100s", ntop, strport, + strerror(errno)); + close(sock); + continue; /* fail -- try next */ + } + break; /* success */ + + } + freeaddrinfo(aitop); + if (!ai) { + error("connect_to %.100s port %d: failed.", host, port); + return -1; + } + /* success */ + set_nodelay(sock); + return sock; + } + + int + channel_connect_by_listen_address(u_short listen_port) + { + int i; + + for (i = 0; i < num_permitted_opens; i++) + if (permitted_opens[i].host_to_connect != NULL && + permitted_opens[i].listen_port == listen_port) + return connect_to( + permitted_opens[i].host_to_connect, + permitted_opens[i].port_to_connect); + error("WARNING: Server requests forwarding for unknown listen_port %d", + listen_port); + return -1; + } + + /* Check if connecting to that port is permitted and connect. */ + int + channel_connect_to(const char *host, u_short port) + { + int i, permit, permit_adm = 1; + + permit = all_opens_permitted; + if (!permit) { + for (i = 0; i < num_permitted_opens; i++) + if (permitted_opens[i].host_to_connect != NULL && + permitted_opens[i].port_to_connect == port && + strcmp(permitted_opens[i].host_to_connect, host) == 0) + permit = 1; + } + + if (num_adm_permitted_opens > 0) { + permit_adm = 0; + for (i = 0; i < num_adm_permitted_opens; i++) + if (permitted_adm_opens[i].host_to_connect != NULL && + permitted_adm_opens[i].port_to_connect == port && + strcmp(permitted_adm_opens[i].host_to_connect, host) + == 0) + permit_adm = 1; + } + + if (!permit || !permit_adm) { + logit("Received request to connect to host %.100s port %d, " + "but the request was denied.", host, port); + return -1; + } + return connect_to(host, port); + } + + void + channel_send_window_changes(void) + { + u_int i; + struct winsize ws; + + for (i = 0; i < channels_alloc; i++) { + if (channels[i] == NULL || !channels[i]->client_tty || + channels[i]->type != SSH_CHANNEL_OPEN) + continue; + if (ioctl(channels[i]->rfd, TIOCGWINSZ, &ws) < 0) + continue; + channel_request_start(i, "window-change", 0); + packet_put_int((u_int)ws.ws_col); + packet_put_int((u_int)ws.ws_row); + packet_put_int((u_int)ws.ws_xpixel); + packet_put_int((u_int)ws.ws_ypixel); + packet_send(); + } + } + + /* -- X11 forwarding */ + + /* + * Creates an internet domain socket for listening for X11 connections. + * Returns 0 and a suitable display number for the DISPLAY variable + * stored in display_numberp , or -1 if an error occurs. + */ + int + x11_create_display_inet(int x11_display_offset, int x11_use_localhost, + int single_connection, u_int *display_numberp, int **chanids) + { + Channel *nc = NULL; + int display_number, sock; + u_short port; + struct addrinfo hints, *ai, *aitop; + char strport[NI_MAXSERV]; + int gaierr, n, num_socks = 0, socks[NUM_SOCKS]; + + if (chanids == NULL) + return -1; + + for (display_number = x11_display_offset; + display_number < MAX_DISPLAYS; + display_number++) { + port = 6000 + display_number; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_flags = x11_use_localhost ? 0: AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof strport, "%d", port); + if ((gaierr = getaddrinfo(NULL, strport, &hints, &aitop)) != 0) { + error("getaddrinfo: %.100s", gai_strerror(gaierr)); + return -1; + } + for (ai = aitop; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + sock = socket(ai->ai_family, ai->ai_socktype, + ai->ai_protocol); + if (sock < 0) { + if ((errno != EINVAL) && (errno != EAFNOSUPPORT)) { + error("socket: %.100s", strerror(errno)); + freeaddrinfo(aitop); + return -1; + } else { + debug("x11_create_display_inet: Socket family %d not supported", + ai->ai_family); + continue; + } + } + #ifdef IPV6_V6ONLY + if (ai->ai_family == AF_INET6) { + int on = 1; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) + error("setsockopt IPV6_V6ONLY: %.100s", strerror(errno)); + } + #endif + channel_set_reuseaddr(sock); + if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { + debug2("bind port %d: %.100s", port, strerror(errno)); + close(sock); + + if (ai->ai_next) + continue; + + for (n = 0; n < num_socks; n++) { + close(socks[n]); + } + num_socks = 0; + break; + } + socks[num_socks++] = sock; + #ifndef DONT_TRY_OTHER_AF + if (num_socks == NUM_SOCKS) + break; + #else + if (x11_use_localhost) { + if (num_socks == NUM_SOCKS) + break; + } else { + break; + } + #endif + } + freeaddrinfo(aitop); + if (num_socks > 0) + break; + } + if (display_number >= MAX_DISPLAYS) { + error("Failed to allocate internet-domain X11 display socket."); + return -1; + } + /* Start listening for connections on the socket. */ + for (n = 0; n < num_socks; n++) { + sock = socks[n]; + if (listen(sock, SSH_LISTEN_BACKLOG) < 0) { + error("listen: %.100s", strerror(errno)); + close(sock); + return -1; + } + } + + /* Allocate a channel for each socket. */ + *chanids = xcalloc(num_socks + 1, sizeof(**chanids)); + for (n = 0; n < num_socks; n++) { + sock = socks[n]; + nc = channel_new("x11 listener", + SSH_CHANNEL_X11_LISTENER, sock, sock, -1, + CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, + 0, "X11 inet listener", 1); + nc->single_connection = single_connection; + (*chanids)[n] = nc->self; + } + (*chanids)[n] = -1; + + /* Return the display number for the DISPLAY environment variable. */ + *display_numberp = display_number; + return (0); + } + + static int + connect_local_xsocket(u_int dnr) + { + int sock; + struct sockaddr_un addr; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) + error("socket: %.100s", strerror(errno)); + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof addr.sun_path, _PATH_UNIX_X, dnr); + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0) + return sock; + close(sock); + error("connect %.100s: %.100s", addr.sun_path, strerror(errno)); + return -1; + } + + int + x11_connect_display(void) + { + u_int display_number; + const char *display; + char buf[1024], *cp; + struct addrinfo hints, *ai, *aitop; + char strport[NI_MAXSERV]; + int gaierr, sock = 0; + + /* Try to open a socket for the local X server. */ + display = getenv("DISPLAY"); + if (!display) { + error("DISPLAY not set."); + return -1; + } + /* + * Now we decode the value of the DISPLAY variable and make a + * connection to the real X server. + */ + + /* + * Check if it is a unix domain socket. Unix domain displays are in + * one of the following formats: unix:d[.s], :d[.s], ::d[.s] + */ + if (strncmp(display, "unix:", 5) == 0 || + display[0] == ':') { + /* Connect to the unix domain socket. */ + if (sscanf(strrchr(display, ':') + 1, "%u", &display_number) != 1) { + error("Could not parse display number from DISPLAY: %.100s", + display); + return -1; + } + /* Create a socket. */ + sock = connect_local_xsocket(display_number); + if (sock < 0) + return -1; + + /* OK, we now have a connection to the display. */ + return sock; + } + /* + * Connect to an inet socket. The DISPLAY value is supposedly + * hostname:d[.s], where hostname may also be numeric IP address. + */ + strlcpy(buf, display, sizeof(buf)); + cp = strchr(buf, ':'); + if (!cp) { + error("Could not find ':' in DISPLAY: %.100s", display); + return -1; + } + *cp = 0; + /* buf now contains the host name. But first we parse the display number. */ + if (sscanf(cp + 1, "%u", &display_number) != 1) { + error("Could not parse display number from DISPLAY: %.100s", + display); + return -1; + } + + /* Look up the host address */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof strport, "%u", 6000 + display_number); + if ((gaierr = getaddrinfo(buf, strport, &hints, &aitop)) != 0) { + error("%.100s: unknown host. (%s)", buf, gai_strerror(gaierr)); + return -1; + } + for (ai = aitop; ai; ai = ai->ai_next) { + /* Create a socket. */ + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) { + debug2("socket: %.100s", strerror(errno)); + continue; + } + /* Connect it to the display. */ + if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0) { + debug2("connect %.100s port %u: %.100s", buf, + 6000 + display_number, strerror(errno)); + close(sock); + continue; + } + /* Success */ + break; + } + freeaddrinfo(aitop); + if (!ai) { + error("connect %.100s port %u: %.100s", buf, 6000 + display_number, + strerror(errno)); + return -1; + } + set_nodelay(sock); + return sock; + } + + /* + * This is called when SSH_SMSG_X11_OPEN is received. The packet contains + * the remote channel number. We should do whatever we want, and respond + * with either SSH_MSG_OPEN_CONFIRMATION or SSH_MSG_OPEN_FAILURE. + */ + + /* ARGSUSED */ + void + x11_input_open(int type, u_int32_t seq, void *ctxt) + { + Channel *c = NULL; + int remote_id, sock = 0; + char *remote_host; + + debug("Received X11 open request."); + + remote_id = packet_get_int(); + + if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) { + remote_host = packet_get_string(NULL); + } else { + remote_host = xstrdup("unknown (remote did not supply name)"); + } + packet_check_eom(); + + /* Obtain a connection to the real X display. */ + sock = x11_connect_display(); + if (sock != -1) { + /* Allocate a channel for this connection. */ + c = channel_new("connected x11 socket", + SSH_CHANNEL_X11_OPEN, sock, sock, -1, 0, 0, 0, + remote_host, 1); + c->remote_id = remote_id; + c->force_drain = 1; + } + xfree(remote_host); + if (c == NULL) { + /* Send refusal to the remote host. */ + packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(remote_id); + } else { + /* Send a confirmation to the remote host. */ + packet_start(SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + packet_put_int(remote_id); + packet_put_int(c->self); + } + packet_send(); + } + + /* dummy protocol handler that denies SSH-1 requests (agent/x11) */ + /* ARGSUSED */ + void + deny_input_open(int type, u_int32_t seq, void *ctxt) + { + int rchan = packet_get_int(); + + switch (type) { + case SSH_SMSG_AGENT_OPEN: + error("Warning: ssh server tried agent forwarding."); + break; + case SSH_SMSG_X11_OPEN: + error("Warning: ssh server tried X11 forwarding."); + break; + default: + error("deny_input_open: type %d", type); + break; + } + error("Warning: this is probably a break-in attempt by a malicious server."); + packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(rchan); + packet_send(); + } + + /* + * Requests forwarding of X11 connections, generates fake authentication + * data, and enables authentication spoofing. + * This should be called in the client only. + */ + void + x11_request_forwarding_with_spoofing(int client_session_id, const char *disp, + const char *proto, const char *data) + { + u_int data_len = (u_int) strlen(data) / 2; + u_int i, value; + char *new_data; + int screen_number; + const char *cp; + u_int32_t rnd = 0; + + if (x11_saved_display == NULL) + x11_saved_display = xstrdup(disp); + else if (strcmp(disp, x11_saved_display) != 0) { + error("x11_request_forwarding_with_spoofing: different " + "$DISPLAY already forwarded"); + return; + } + + cp = strchr(disp, ':'); + if (cp) + cp = strchr(cp, '.'); + if (cp) + screen_number = (u_int)strtonum(cp + 1, 0, 400, NULL); + else + screen_number = 0; + + if (x11_saved_proto == NULL) { + /* Save protocol name. */ + x11_saved_proto = xstrdup(proto); + /* + * Extract real authentication data and generate fake data + * of the same length. + */ + x11_saved_data = xmalloc(data_len); + x11_fake_data = xmalloc(data_len); + for (i = 0; i < data_len; i++) { + if (sscanf(data + 2 * i, "%2x", &value) != 1) + fatal("x11_request_forwarding: bad " + "authentication data: %.100s", data); + if (i % 4 == 0) + rnd = arc4random(); + x11_saved_data[i] = value; + x11_fake_data[i] = rnd & 0xff; + rnd >>= 8; + } + x11_saved_data_len = data_len; + x11_fake_data_len = data_len; + } + + /* Convert the fake data into hex. */ + new_data = tohex(x11_fake_data, data_len); + + /* Send the request packet. */ + if (compat20) { + channel_request_start(client_session_id, "x11-req", 0); + packet_put_char(0); /* XXX bool single connection */ + } else { + packet_start(SSH_CMSG_X11_REQUEST_FORWARDING); + } + packet_put_cstring(proto); + packet_put_cstring(new_data); + packet_put_int(screen_number); + packet_send(); + packet_write_wait(); + xfree(new_data); + } + + + /* -- agent forwarding */ + + /* Sends a message to the server to request authentication fd forwarding. */ + + void + auth_request_forwarding(void) + { + packet_start(SSH_CMSG_AGENT_REQUEST_FORWARDING); + packet_send(); + packet_write_wait(); + } diff -crN openssh-4.7p1/channels.c.rej openssh-4.7p1-logging/channels.c.rej *** openssh-4.7p1/channels.c.rej Thu Jan 1 02:00:00 1970 --- openssh-4.7p1-logging/channels.c.rej Tue Jan 22 18:10:47 2008 *************** *** 0 **** --- 1,16 ---- + *************** + *** 55,60 **** + #include "authfd.h" + #include "pathnames.h" + #include "bufaux.h" + + /* -- channel core */ + + --- 55,61 ---- + #include "authfd.h" + #include "pathnames.h" + #include "bufaux.h" + + #include "script.h" + + /* -- channel core */ + diff -crN openssh-4.7p1/config.status openssh-4.7p1-logging/config.status *** openssh-4.7p1/config.status Wed Jan 23 15:36:09 2008 --- openssh-4.7p1-logging/config.status Wed Jan 23 15:30:47 2008 *************** *** 337,343 **** This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ! ac_pwd='/home/angelos/openssh-4.7p1' srcdir='.' INSTALL='/usr/bin/install -c' # If no file are specified by the user, then we need to provide default --- 337,343 ---- This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ! ac_pwd='/home/angelos/openssh-4.3p2' srcdir='.' INSTALL='/usr/bin/install -c' # If no file are specified by the user, then we need to provide default diff -crN openssh-4.7p1/replay openssh-4.7p1-logging/replay *** openssh-4.7p1/replay Thu Jan 1 02:00:00 1970 --- openssh-4.7p1-logging/replay Tue Jan 22 18:10:47 2008 *************** *** 0 **** --- 1,66 ---- + #!/usr/bin/perl -w + # + # Script to replay openssh log files. + # + # Based upon "replay" script from Joey Hess + # ( See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=68556 ) + # + use Getopt::Long; + use Time::HiRes qw(usleep); + use strict; + $|=1; + + my $speed = 1.0; + GetOptions ('speed:f' => \$speed); + if ($speed <= 0) { + die "replay speed must be positive number"; + } + if ($#ARGV != 0) { + die "Usage: replay [--speed number] file" ; + } + + + my $timing=$ARGV[0]; + my $typescript=$ARGV[0]; + $timing=~s/.typescript$/.timing/; + $typescript=~s/.timing$/.typescript/; + + open (TIMING, $timing) + or die "cannot read timing info ", $timing , ": $!"; + open (TYPESCRIPT, $typescript) + or die "cannot read typescript ", $typescript , ": $!"; + + # print starting timestamp line ("Script started on ...") + my $header=; + print $header; + + my $block; + my $oldblock=''; + my $timestamp; + my $oldtimestamp=0; + my $blocksize; + my $delay; + + while () { + ($timestamp, $blocksize)=split ' ', $_, 2; + if ($oldtimestamp != 0) { + $delay=$timestamp-$oldtimestamp; + } + else { + $delay=0; + } + read(TYPESCRIPT, $block, $blocksize) + or die "read failure on typescript: $!"; + print $block; + # Sleep, unless the delay is really tiny. Really tiny delays cannot + # be accurately done, because the system calls in this loop will + # have more overhead. The 0.0001 is arbitrary, but works fairly well. + if ($delay / $speed > 0.0001) { + usleep( ($delay / $speed - 0.0001) * 1000000); + } + + $oldtimestamp=$timestamp; + } + # print remainder of file (Script ended on ...) + print ; + #not truncated diff -crN openssh-4.7p1/script.c openssh-4.7p1-logging/script.c *** openssh-4.7p1/script.c Thu Jan 1 02:00:00 1970 --- openssh-4.7p1-logging/script.c Thu Jan 24 11:27:20 2008 *************** *** 0 **** --- 1,118 ---- + #include "includes.h" + #include "monitor.h" + #include "log.h" + #include "script.h" + #include "errno.h" + #include + #include + #include + #include + #include + #include + + + + + #include "audit.h" + #include "log.h" + #include "key.h" + #include "hostfile.h" + #include "auth.h" + + /* + * Care must be taken when using this since it WILL NOT be initialized when + * audit_connection_from() is called and MAY NOT be initialized when + * audit_event(CONNECTION_ABANDON) is called. Test for NULL before using. + */ + extern Authctxt *the_authctxt; + + + /* + * Log tty output in script format with additional timing information. + * Log file timing format: + * ss.hh cc + * Where: + * ss: seconds since Jan 1, 1970. + * hh: hundredths of seconds + * cc: character count + * Inspired by http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=68556 + */ + + #define SCRIPTDIR "/var/log/ssh_audit" + + + static long int oldseconds = 0, oldhundredthsofseconds = 0, charcount = 0; + + /* + * Log string 'buf' of length 'len' + */ + static FILE *timingfd=NULL; + static FILE *scriptfd=NULL; + static char scriptfname[FILENAME_MAX + 1]; + static char timingfname[FILENAME_MAX + 1]; + static int init=0; + + void + script(char *buf, int len) + { + struct timeval tv; + struct stat st; + + long int seconds; + long int hundredthsofseconds; + time_t t; + char buffer[FILENAME_MAX + 1]; + + + /* static initializations + files remain open and are closed by the OS on exit */ + + + if ( init == 0 ) { + init=1; + time(&t); + strftime(buffer, sizeof(buffer), "%Y_%m_%d_%H:%M:%S", localtime(&t)); + + snprintf(timingfname, sizeof(timingfname), "%s/%s_%s.%d.timing",SCRIPTDIR, buffer,the_authctxt->user,getpid()); + debug3("trying to open '%s' ", timingfname); + timingfd = fopen(timingfname, "a"); + + snprintf(scriptfname, sizeof(scriptfname), "%s/%s_%s.%d.typescript",SCRIPTDIR, buffer,the_authctxt->user, getpid()); + debug3("trying to open '%s' ", scriptfname); + scriptfd=fopen(scriptfname, "a"); + } + + + if ((buf != NULL) && (len > 0)) { + /* + * .timing file: timestamp and character count + */ + if ( timingfd != NULL) { + if ((gettimeofday(&tv, NULL)) == -1) { + fatal("%s", strerror(errno)); + } + seconds = tv.tv_sec; + hundredthsofseconds = tv.tv_usec / 10000; + if ((seconds == oldseconds) + && (hundredthsofseconds == oldhundredthsofseconds)) { + charcount += len; + } else { + if (charcount != 0) { + fprintf(timingfd, "%li.%02li %li\n", oldseconds, + oldhundredthsofseconds, charcount); + } + oldseconds = seconds; + oldhundredthsofseconds = hundredthsofseconds; + charcount = len; + } + fflush(timingfd); + } + + if (scriptfd != NULL) { + fwrite(buf, 1, len, scriptfd); + fflush(scriptfd); + } + } + + } + diff -crN openssh-4.7p1/script.h openssh-4.7p1-logging/script.h *** openssh-4.7p1/script.h Thu Jan 1 02:00:00 1970 --- openssh-4.7p1-logging/script.h Wed Jan 23 17:09:01 2008 *************** *** 0 **** --- 1,9 ---- + + #ifndef SCRIPT_H + #define SCRIPT_H + + void script_open(char *); + void script(char *, int); + void script_close(); + + #endif diff -crN openssh-4.7p1/serverloop.c openssh-4.7p1-logging/serverloop.c *** openssh-4.7p1/serverloop.c Mon Jan 29 01:16:28 2007 --- openssh-4.7p1-logging/serverloop.c Wed Jan 23 18:09:49 2008 *************** *** 77,82 **** --- 77,83 ---- #include "auth-options.h" #include "serverloop.h" #include "misc.h" + #include "script.h" extern ServerOptions options; *************** *** 428,433 **** --- 429,435 ---- #endif fdout_eof = 1; } else { + script(buf, len); buffer_append(&stdout_buffer, buf, len); fdout_bytes += len; } *************** *** 447,452 **** --- 449,455 ---- #endif fderr_eof = 1; } else { + script(buf, len); buffer_append(&stderr_buffer, buf, len); } } *************** *** 742,747 **** --- 745,751 ---- channel_free_all(); + /* We no longer want our SIGCHLD handler to be called. */ mysignal(SIGCHLD, SIG_DFL); *************** *** 874,879 **** --- 878,884 ---- /* free all channels, no more reads and writes */ channel_free_all(); + /* free remaining sessions, e.g. remove wtmp entries */ session_destroy_all(NULL); } diff -crN openssh-4.7p1/serverloop.c.orig openssh-4.7p1-logging/serverloop.c.orig *** openssh-4.7p1/serverloop.c.orig Thu Jan 1 02:00:00 1970 --- openssh-4.7p1-logging/serverloop.c.orig Mon Jan 29 01:16:28 2007 *************** *** 0 **** --- 1,1231 ---- + /* $OpenBSD: serverloop.c,v 1.145 2006/10/11 12:38:03 markus Exp $ */ + /* + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * Server main loop for handling the interactive session. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + * + * SSH2 support by Markus Friedl. + * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + #include "includes.h" + + #include + #include + #include + #include + #ifdef HAVE_SYS_TIME_H + # include + #endif + + #include + + #include + #include + #include + #include + #include + #include + #include + #include + + #include "xmalloc.h" + #include "packet.h" + #include "buffer.h" + #include "log.h" + #include "servconf.h" + #include "canohost.h" + #include "sshpty.h" + #include "channels.h" + #include "compat.h" + #include "ssh1.h" + #include "ssh2.h" + #include "key.h" + #include "cipher.h" + #include "kex.h" + #include "hostfile.h" + #include "auth.h" + #include "session.h" + #include "dispatch.h" + #include "auth-options.h" + #include "serverloop.h" + #include "misc.h" + + extern ServerOptions options; + + /* XXX */ + extern Kex *xxx_kex; + extern Authctxt *the_authctxt; + extern int use_privsep; + + static Buffer stdin_buffer; /* Buffer for stdin data. */ + static Buffer stdout_buffer; /* Buffer for stdout data. */ + static Buffer stderr_buffer; /* Buffer for stderr data. */ + static int fdin; /* Descriptor for stdin (for writing) */ + static int fdout; /* Descriptor for stdout (for reading); + May be same number as fdin. */ + static int fderr; /* Descriptor for stderr. May be -1. */ + static long stdin_bytes = 0; /* Number of bytes written to stdin. */ + static long stdout_bytes = 0; /* Number of stdout bytes sent to client. */ + static long stderr_bytes = 0; /* Number of stderr bytes sent to client. */ + static long fdout_bytes = 0; /* Number of stdout bytes read from program. */ + static int stdin_eof = 0; /* EOF message received from client. */ + static int fdout_eof = 0; /* EOF encountered reading from fdout. */ + static int fderr_eof = 0; /* EOF encountered readung from fderr. */ + static int fdin_is_tty = 0; /* fdin points to a tty. */ + static int connection_in; /* Connection to client (input). */ + static int connection_out; /* Connection to client (output). */ + static int connection_closed = 0; /* Connection to client closed. */ + static u_int buffer_high; /* "Soft" max buffer size. */ + static int client_alive_timeouts = 0; + + /* + * This SIGCHLD kludge is used to detect when the child exits. The server + * will exit after that, as soon as forwarded connections have terminated. + */ + + static volatile sig_atomic_t child_terminated = 0; /* The child has terminated. */ + + /* Cleanup on signals (!use_privsep case only) */ + static volatile sig_atomic_t received_sigterm = 0; + + /* prototypes */ + static void server_init_dispatch(void); + + /* + * we write to this pipe if a SIGCHLD is caught in order to avoid + * the race between select() and child_terminated + */ + static int notify_pipe[2]; + static void + notify_setup(void) + { + if (pipe(notify_pipe) < 0) { + error("pipe(notify_pipe) failed %s", strerror(errno)); + } else if ((fcntl(notify_pipe[0], F_SETFD, 1) == -1) || + (fcntl(notify_pipe[1], F_SETFD, 1) == -1)) { + error("fcntl(notify_pipe, F_SETFD) failed %s", strerror(errno)); + close(notify_pipe[0]); + close(notify_pipe[1]); + } else { + set_nonblock(notify_pipe[0]); + set_nonblock(notify_pipe[1]); + return; + } + notify_pipe[0] = -1; /* read end */ + notify_pipe[1] = -1; /* write end */ + } + static void + notify_parent(void) + { + if (notify_pipe[1] != -1) + write(notify_pipe[1], "", 1); + } + static void + notify_prepare(fd_set *readset) + { + if (notify_pipe[0] != -1) + FD_SET(notify_pipe[0], readset); + } + static void + notify_done(fd_set *readset) + { + char c; + + if (notify_pipe[0] != -1 && FD_ISSET(notify_pipe[0], readset)) + while (read(notify_pipe[0], &c, 1) != -1) + debug2("notify_done: reading"); + } + + /*ARGSUSED*/ + static void + sigchld_handler(int sig) + { + int save_errno = errno; + child_terminated = 1; + #ifndef _UNICOS + mysignal(SIGCHLD, sigchld_handler); + #endif + notify_parent(); + errno = save_errno; + } + + /*ARGSUSED*/ + static void + sigterm_handler(int sig) + { + received_sigterm = sig; + } + + /* + * Make packets from buffered stderr data, and buffer it for sending + * to the client. + */ + static void + make_packets_from_stderr_data(void) + { + u_int len; + + /* Send buffered stderr data to the client. */ + while (buffer_len(&stderr_buffer) > 0 && + packet_not_very_much_data_to_write()) { + len = buffer_len(&stderr_buffer); + if (packet_is_interactive()) { + if (len > 512) + len = 512; + } else { + /* Keep the packets at reasonable size. */ + if (len > packet_get_maxsize()) + len = packet_get_maxsize(); + } + packet_start(SSH_SMSG_STDERR_DATA); + packet_put_string(buffer_ptr(&stderr_buffer), len); + packet_send(); + buffer_consume(&stderr_buffer, len); + stderr_bytes += len; + } + } + + /* + * Make packets from buffered stdout data, and buffer it for sending to the + * client. + */ + static void + make_packets_from_stdout_data(void) + { + u_int len; + + /* Send buffered stdout data to the client. */ + while (buffer_len(&stdout_buffer) > 0 && + packet_not_very_much_data_to_write()) { + len = buffer_len(&stdout_buffer); + if (packet_is_interactive()) { + if (len > 512) + len = 512; + } else { + /* Keep the packets at reasonable size. */ + if (len > packet_get_maxsize()) + len = packet_get_maxsize(); + } + packet_start(SSH_SMSG_STDOUT_DATA); + packet_put_string(buffer_ptr(&stdout_buffer), len); + packet_send(); + buffer_consume(&stdout_buffer, len); + stdout_bytes += len; + } + } + + static void + client_alive_check(void) + { + int channel_id; + + /* timeout, check to see how many we have had */ + if (++client_alive_timeouts > options.client_alive_count_max) { + logit("Timeout, client not responding."); + cleanup_exit(255); + } + + /* + * send a bogus global/channel request with "wantreply", + * we should get back a failure + */ + if ((channel_id = channel_find_open()) == -1) { + packet_start(SSH2_MSG_GLOBAL_REQUEST); + packet_put_cstring("keepalive@openssh.com"); + packet_put_char(1); /* boolean: want reply */ + } else { + channel_request_start(channel_id, "keepalive@openssh.com", 1); + } + packet_send(); + } + + /* + * Sleep in select() until we can do something. This will initialize the + * select masks. Upon return, the masks will indicate which descriptors + * have data or can accept data. Optionally, a maximum time can be specified + * for the duration of the wait (0 = infinite). + */ + static void + wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, int *maxfdp, + u_int *nallocp, u_int max_time_milliseconds) + { + struct timeval tv, *tvp; + int ret; + int client_alive_scheduled = 0; + int program_alive_scheduled = 0; + + /* + * if using client_alive, set the max timeout accordingly, + * and indicate that this particular timeout was for client + * alive by setting the client_alive_scheduled flag. + * + * this could be randomized somewhat to make traffic + * analysis more difficult, but we're not doing it yet. + */ + if (compat20 && + max_time_milliseconds == 0 && options.client_alive_interval) { + client_alive_scheduled = 1; + max_time_milliseconds = options.client_alive_interval * 1000; + } + + /* Allocate and update select() masks for channel descriptors. */ + channel_prepare_select(readsetp, writesetp, maxfdp, nallocp, 0); + + if (compat20) { + #if 0 + /* wrong: bad condition XXX */ + if (channel_not_very_much_buffered_data()) + #endif + FD_SET(connection_in, *readsetp); + } else { + /* + * Read packets from the client unless we have too much + * buffered stdin or channel data. + */ + if (buffer_len(&stdin_buffer) < buffer_high && + channel_not_very_much_buffered_data()) + FD_SET(connection_in, *readsetp); + /* + * If there is not too much data already buffered going to + * the client, try to get some more data from the program. + */ + if (packet_not_very_much_data_to_write()) { + program_alive_scheduled = child_terminated; + if (!fdout_eof) + FD_SET(fdout, *readsetp); + if (!fderr_eof) + FD_SET(fderr, *readsetp); + } + /* + * If we have buffered data, try to write some of that data + * to the program. + */ + if (fdin != -1 && buffer_len(&stdin_buffer) > 0) + FD_SET(fdin, *writesetp); + } + notify_prepare(*readsetp); + + /* + * If we have buffered packet data going to the client, mark that + * descriptor. + */ + if (packet_have_data_to_write()) + FD_SET(connection_out, *writesetp); + + /* + * If child has terminated and there is enough buffer space to read + * from it, then read as much as is available and exit. + */ + if (child_terminated && packet_not_very_much_data_to_write()) + if (max_time_milliseconds == 0 || client_alive_scheduled) + max_time_milliseconds = 100; + + if (max_time_milliseconds == 0) + tvp = NULL; + else { + tv.tv_sec = max_time_milliseconds / 1000; + tv.tv_usec = 1000 * (max_time_milliseconds % 1000); + tvp = &tv; + } + + /* Wait for something to happen, or the timeout to expire. */ + ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp); + + if (ret == -1) { + memset(*readsetp, 0, *nallocp); + memset(*writesetp, 0, *nallocp); + if (errno != EINTR) + error("select: %.100s", strerror(errno)); + } else { + if (ret == 0 && client_alive_scheduled) + client_alive_check(); + if (!compat20 && program_alive_scheduled && fdin_is_tty) { + if (!fdout_eof) + FD_SET(fdout, *readsetp); + if (!fderr_eof) + FD_SET(fderr, *readsetp); + } + } + + notify_done(*readsetp); + } + + /* + * Processes input from the client and the program. Input data is stored + * in buffers and processed later. + */ + static void + process_input(fd_set *readset) + { + int len; + char buf[16384]; + + /* Read and buffer any input data from the client. */ + if (FD_ISSET(connection_in, readset)) { + len = read(connection_in, buf, sizeof(buf)); + if (len == 0) { + verbose("Connection closed by %.100s", + get_remote_ipaddr()); + connection_closed = 1; + if (compat20) + return; + cleanup_exit(255); + } else if (len < 0) { + if (errno != EINTR && errno != EAGAIN) { + verbose("Read error from remote host " + "%.100s: %.100s", + get_remote_ipaddr(), strerror(errno)); + cleanup_exit(255); + } + } else { + /* Buffer any received data. */ + packet_process_incoming(buf, len); + } + } + if (compat20) + return; + + /* Read and buffer any available stdout data from the program. */ + if (!fdout_eof && FD_ISSET(fdout, readset)) { + errno = 0; + len = read(fdout, buf, sizeof(buf)); + if (len < 0 && (errno == EINTR || + (errno == EAGAIN && !child_terminated))) { + /* do nothing */ + #ifndef PTY_ZEROREAD + } else if (len <= 0) { + #else + } else if ((!isatty(fdout) && len <= 0) || + (isatty(fdout) && (len < 0 || (len == 0 && errno != 0)))) { + #endif + fdout_eof = 1; + } else { + buffer_append(&stdout_buffer, buf, len); + fdout_bytes += len; + } + } + /* Read and buffer any available stderr data from the program. */ + if (!fderr_eof && FD_ISSET(fderr, readset)) { + errno = 0; + len = read(fderr, buf, sizeof(buf)); + if (len < 0 && (errno == EINTR || + (errno == EAGAIN && !child_terminated))) { + /* do nothing */ + #ifndef PTY_ZEROREAD + } else if (len <= 0) { + #else + } else if ((!isatty(fderr) && len <= 0) || + (isatty(fderr) && (len < 0 || (len == 0 && errno != 0)))) { + #endif + fderr_eof = 1; + } else { + buffer_append(&stderr_buffer, buf, len); + } + } + } + + /* + * Sends data from internal buffers to client program stdin. + */ + static void + process_output(fd_set *writeset) + { + struct termios tio; + u_char *data; + u_int dlen; + int len; + + /* Write buffered data to program stdin. */ + if (!compat20 && fdin != -1 && FD_ISSET(fdin, writeset)) { + data = buffer_ptr(&stdin_buffer); + dlen = buffer_len(&stdin_buffer); + len = write(fdin, data, dlen); + if (len < 0 && (errno == EINTR || errno == EAGAIN)) { + /* do nothing */ + } else if (len <= 0) { + if (fdin != fdout) + close(fdin); + else + shutdown(fdin, SHUT_WR); /* We will no longer send. */ + fdin = -1; + } else { + /* Successful write. */ + if (fdin_is_tty && dlen >= 1 && data[0] != '\r' && + tcgetattr(fdin, &tio) == 0 && + !(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { + /* + * Simulate echo to reduce the impact of + * traffic analysis + */ + packet_send_ignore(len); + packet_send(); + } + /* Consume the data from the buffer. */ + buffer_consume(&stdin_buffer, len); + /* Update the count of bytes written to the program. */ + stdin_bytes += len; + } + } + /* Send any buffered packet data to the client. */ + if (FD_ISSET(connection_out, writeset)) + packet_write_poll(); + } + + /* + * Wait until all buffered output has been sent to the client. + * This is used when the program terminates. + */ + static void + drain_output(void) + { + /* Send any buffered stdout data to the client. */ + if (buffer_len(&stdout_buffer) > 0) { + packet_start(SSH_SMSG_STDOUT_DATA); + packet_put_string(buffer_ptr(&stdout_buffer), + buffer_len(&stdout_buffer)); + packet_send(); + /* Update the count of sent bytes. */ + stdout_bytes += buffer_len(&stdout_buffer); + } + /* Send any buffered stderr data to the client. */ + if (buffer_len(&stderr_buffer) > 0) { + packet_start(SSH_SMSG_STDERR_DATA); + packet_put_string(buffer_ptr(&stderr_buffer), + buffer_len(&stderr_buffer)); + packet_send(); + /* Update the count of sent bytes. */ + stderr_bytes += buffer_len(&stderr_buffer); + } + /* Wait until all buffered data has been written to the client. */ + packet_write_wait(); + } + + static void + process_buffered_input_packets(void) + { + dispatch_run(DISPATCH_NONBLOCK, NULL, compat20 ? xxx_kex : NULL); + } + + /* + * Performs the interactive session. This handles data transmission between + * the client and the program. Note that the notion of stdin, stdout, and + * stderr in this function is sort of reversed: this function writes to + * stdin (of the child program), and reads from stdout and stderr (of the + * child program). + */ + void + server_loop(pid_t pid, int fdin_arg, int fdout_arg, int fderr_arg) + { + fd_set *readset = NULL, *writeset = NULL; + int max_fd = 0; + u_int nalloc = 0; + int wait_status; /* Status returned by wait(). */ + pid_t wait_pid; /* pid returned by wait(). */ + int waiting_termination = 0; /* Have displayed waiting close message. */ + u_int max_time_milliseconds; + u_int previous_stdout_buffer_bytes; + u_int stdout_buffer_bytes; + int type; + + debug("Entering interactive session."); + + /* Initialize the SIGCHLD kludge. */ + child_terminated = 0; + mysignal(SIGCHLD, sigchld_handler); + + if (!use_privsep) { + signal(SIGTERM, sigterm_handler); + signal(SIGINT, sigterm_handler); + signal(SIGQUIT, sigterm_handler); + } + + /* Initialize our global variables. */ + fdin = fdin_arg; + fdout = fdout_arg; + fderr = fderr_arg; + + /* nonblocking IO */ + set_nonblock(fdin); + set_nonblock(fdout); + /* we don't have stderr for interactive terminal sessions, see below */ + if (fderr != -1) + set_nonblock(fderr); + + if (!(datafellows & SSH_BUG_IGNOREMSG) && isatty(fdin)) + fdin_is_tty = 1; + + connection_in = packet_get_connection_in(); + connection_out = packet_get_connection_out(); + + notify_setup(); + + previous_stdout_buffer_bytes = 0; + + /* Set approximate I/O buffer size. */ + if (packet_is_interactive()) + buffer_high = 4096; + else + buffer_high = 64 * 1024; + + #if 0 + /* Initialize max_fd to the maximum of the known file descriptors. */ + max_fd = MAX(connection_in, connection_out); + max_fd = MAX(max_fd, fdin); + max_fd = MAX(max_fd, fdout); + if (fderr != -1) + max_fd = MAX(max_fd, fderr); + #endif + + /* Initialize Initialize buffers. */ + buffer_init(&stdin_buffer); + buffer_init(&stdout_buffer); + buffer_init(&stderr_buffer); + + /* + * If we have no separate fderr (which is the case when we have a pty + * - there we cannot make difference between data sent to stdout and + * stderr), indicate that we have seen an EOF from stderr. This way + * we don't need to check the descriptor everywhere. + */ + if (fderr == -1) + fderr_eof = 1; + + server_init_dispatch(); + + /* Main loop of the server for the interactive session mode. */ + for (;;) { + + /* Process buffered packets from the client. */ + process_buffered_input_packets(); + + /* + * If we have received eof, and there is no more pending + * input data, cause a real eof by closing fdin. + */ + if (stdin_eof && fdin != -1 && buffer_len(&stdin_buffer) == 0) { + if (fdin != fdout) + close(fdin); + else + shutdown(fdin, SHUT_WR); /* We will no longer send. */ + fdin = -1; + } + /* Make packets from buffered stderr data to send to the client. */ + make_packets_from_stderr_data(); + + /* + * Make packets from buffered stdout data to send to the + * client. If there is very little to send, this arranges to + * not send them now, but to wait a short while to see if we + * are getting more data. This is necessary, as some systems + * wake up readers from a pty after each separate character. + */ + max_time_milliseconds = 0; + stdout_buffer_bytes = buffer_len(&stdout_buffer); + if (stdout_buffer_bytes != 0 && stdout_buffer_bytes < 256 && + stdout_buffer_bytes != previous_stdout_buffer_bytes) { + /* try again after a while */ + max_time_milliseconds = 10; + } else { + /* Send it now. */ + make_packets_from_stdout_data(); + } + previous_stdout_buffer_bytes = buffer_len(&stdout_buffer); + + /* Send channel data to the client. */ + if (packet_not_very_much_data_to_write()) + channel_output_poll(); + + /* + * Bail out of the loop if the program has closed its output + * descriptors, and we have no more data to send to the + * client, and there is no pending buffered data. + */ + if (fdout_eof && fderr_eof && !packet_have_data_to_write() && + buffer_len(&stdout_buffer) == 0 && buffer_len(&stderr_buffer) == 0) { + if (!channel_still_open()) + break; + if (!waiting_termination) { + const char *s = "Waiting for forwarded connections to terminate...\r\n"; + char *cp; + waiting_termination = 1; + buffer_append(&stderr_buffer, s, strlen(s)); + + /* Display list of open channels. */ + cp = channel_open_message(); + buffer_append(&stderr_buffer, cp, strlen(cp)); + xfree(cp); + } + } + max_fd = MAX(connection_in, connection_out); + max_fd = MAX(max_fd, fdin); + max_fd = MAX(max_fd, fdout); + max_fd = MAX(max_fd, fderr); + max_fd = MAX(max_fd, notify_pipe[0]); + + /* Sleep in select() until we can do something. */ + wait_until_can_do_something(&readset, &writeset, &max_fd, + &nalloc, max_time_milliseconds); + + if (received_sigterm) { + logit("Exiting on signal %d", received_sigterm); + /* Clean up sessions, utmp, etc. */ + cleanup_exit(255); + } + + /* Process any channel events. */ + channel_after_select(readset, writeset); + + /* Process input from the client and from program stdout/stderr. */ + process_input(readset); + + /* Process output to the client and to program stdin. */ + process_output(writeset); + } + if (readset) + xfree(readset); + if (writeset) + xfree(writeset); + + /* Cleanup and termination code. */ + + /* Wait until all output has been sent to the client. */ + drain_output(); + + debug("End of interactive session; stdin %ld, stdout (read %ld, sent %ld), stderr %ld bytes.", + stdin_bytes, fdout_bytes, stdout_bytes, stderr_bytes); + + /* Free and clear the buffers. */ + buffer_free(&stdin_buffer); + buffer_free(&stdout_buffer); + buffer_free(&stderr_buffer); + + /* Close the file descriptors. */ + if (fdout != -1) + close(fdout); + fdout = -1; + fdout_eof = 1; + if (fderr != -1) + close(fderr); + fderr = -1; + fderr_eof = 1; + if (fdin != -1) + close(fdin); + fdin = -1; + + channel_free_all(); + + /* We no longer want our SIGCHLD handler to be called. */ + mysignal(SIGCHLD, SIG_DFL); + + while ((wait_pid = waitpid(-1, &wait_status, 0)) < 0) + if (errno != EINTR) + packet_disconnect("wait: %.100s", strerror(errno)); + if (wait_pid != pid) + error("Strange, wait returned pid %ld, expected %ld", + (long)wait_pid, (long)pid); + + /* Check if it exited normally. */ + if (WIFEXITED(wait_status)) { + /* Yes, normal exit. Get exit status and send it to the client. */ + debug("Command exited with status %d.", WEXITSTATUS(wait_status)); + packet_start(SSH_SMSG_EXITSTATUS); + packet_put_int(WEXITSTATUS(wait_status)); + packet_send(); + packet_write_wait(); + + /* + * Wait for exit confirmation. Note that there might be + * other packets coming before it; however, the program has + * already died so we just ignore them. The client is + * supposed to respond with the confirmation when it receives + * the exit status. + */ + do { + type = packet_read(); + } + while (type != SSH_CMSG_EXIT_CONFIRMATION); + + debug("Received exit confirmation."); + return; + } + /* Check if the program terminated due to a signal. */ + if (WIFSIGNALED(wait_status)) + packet_disconnect("Command terminated on signal %d.", + WTERMSIG(wait_status)); + + /* Some weird exit cause. Just exit. */ + packet_disconnect("wait returned status %04x.", wait_status); + /* NOTREACHED */ + } + + static void + collect_children(void) + { + pid_t pid; + sigset_t oset, nset; + int status; + + /* block SIGCHLD while we check for dead children */ + sigemptyset(&nset); + sigaddset(&nset, SIGCHLD); + sigprocmask(SIG_BLOCK, &nset, &oset); + if (child_terminated) { + debug("Received SIGCHLD."); + while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || + (pid < 0 && errno == EINTR)) + if (pid > 0) + session_close_by_pid(pid, status); + child_terminated = 0; + } + sigprocmask(SIG_SETMASK, &oset, NULL); + } + + void + server_loop2(Authctxt *authctxt) + { + fd_set *readset = NULL, *writeset = NULL; + int rekeying = 0, max_fd, nalloc = 0; + + debug("Entering interactive session for SSH2."); + + mysignal(SIGCHLD, sigchld_handler); + child_terminated = 0; + connection_in = packet_get_connection_in(); + connection_out = packet_get_connection_out(); + + if (!use_privsep) { + signal(SIGTERM, sigterm_handler); + signal(SIGINT, sigterm_handler); + signal(SIGQUIT, sigterm_handler); + } + + notify_setup(); + + max_fd = MAX(connection_in, connection_out); + max_fd = MAX(max_fd, notify_pipe[0]); + + server_init_dispatch(); + + for (;;) { + process_buffered_input_packets(); + + rekeying = (xxx_kex != NULL && !xxx_kex->done); + + if (!rekeying && packet_not_very_much_data_to_write()) + channel_output_poll(); + wait_until_can_do_something(&readset, &writeset, &max_fd, + &nalloc, 0); + + if (received_sigterm) { + logit("Exiting on signal %d", received_sigterm); + /* Clean up sessions, utmp, etc. */ + cleanup_exit(255); + } + + collect_children(); + if (!rekeying) { + channel_after_select(readset, writeset); + if (packet_need_rekeying()) { + debug("need rekeying"); + xxx_kex->done = 0; + kex_send_kexinit(xxx_kex); + } + } + process_input(readset); + if (connection_closed) + break; + process_output(writeset); + } + collect_children(); + + if (readset) + xfree(readset); + if (writeset) + xfree(writeset); + + /* free all channels, no more reads and writes */ + channel_free_all(); + + /* free remaining sessions, e.g. remove wtmp entries */ + session_destroy_all(NULL); + } + + static void + server_input_keep_alive(int type, u_int32_t seq, void *ctxt) + { + debug("Got %d/%u for keepalive", type, seq); + /* + * reset timeout, since we got a sane answer from the client. + * even if this was generated by something other than + * the bogus CHANNEL_REQUEST we send for keepalives. + */ + client_alive_timeouts = 0; + } + + static void + server_input_stdin_data(int type, u_int32_t seq, void *ctxt) + { + char *data; + u_int data_len; + + /* Stdin data from the client. Append it to the buffer. */ + /* Ignore any data if the client has closed stdin. */ + if (fdin == -1) + return; + data = packet_get_string(&data_len); + packet_check_eom(); + buffer_append(&stdin_buffer, data, data_len); + memset(data, 0, data_len); + xfree(data); + } + + static void + server_input_eof(int type, u_int32_t seq, void *ctxt) + { + /* + * Eof from the client. The stdin descriptor to the + * program will be closed when all buffered data has + * drained. + */ + debug("EOF received for stdin."); + packet_check_eom(); + stdin_eof = 1; + } + + static void + server_input_window_size(int type, u_int32_t seq, void *ctxt) + { + u_int row = packet_get_int(); + u_int col = packet_get_int(); + u_int xpixel = packet_get_int(); + u_int ypixel = packet_get_int(); + + debug("Window change received."); + packet_check_eom(); + if (fdin != -1) + pty_change_window_size(fdin, row, col, xpixel, ypixel); + } + + static Channel * + server_request_direct_tcpip(void) + { + Channel *c; + int sock; + char *target, *originator; + int target_port, originator_port; + + target = packet_get_string(NULL); + target_port = packet_get_int(); + originator = packet_get_string(NULL); + originator_port = packet_get_int(); + packet_check_eom(); + + debug("server_request_direct_tcpip: originator %s port %d, target %s port %d", + originator, originator_port, target, target_port); + + /* XXX check permission */ + sock = channel_connect_to(target, target_port); + xfree(target); + xfree(originator); + if (sock < 0) + return NULL; + c = channel_new("direct-tcpip", SSH_CHANNEL_CONNECTING, + sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, + CHAN_TCP_PACKET_DEFAULT, 0, "direct-tcpip", 1); + return c; + } + + static Channel * + server_request_tun(void) + { + Channel *c = NULL; + int mode, tun; + int sock; + + mode = packet_get_int(); + switch (mode) { + case SSH_TUNMODE_POINTOPOINT: + case SSH_TUNMODE_ETHERNET: + break; + default: + packet_send_debug("Unsupported tunnel device mode."); + return NULL; + } + if ((options.permit_tun & mode) == 0) { + packet_send_debug("Server has rejected tunnel device " + "forwarding"); + return NULL; + } + + tun = packet_get_int(); + if (forced_tun_device != -1) { + if (tun != SSH_TUNID_ANY && forced_tun_device != tun) + goto done; + tun = forced_tun_device; + } + sock = tun_open(tun, mode); + if (sock < 0) + goto done; + c = channel_new("tun", SSH_CHANNEL_OPEN, sock, sock, -1, + CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); + c->datagram = 1; + #if defined(SSH_TUN_FILTER) + if (mode == SSH_TUNMODE_POINTOPOINT) + channel_register_filter(c->self, sys_tun_infilter, + sys_tun_outfilter); + #endif + + done: + if (c == NULL) + packet_send_debug("Failed to open the tunnel device."); + return c; + } + + static Channel * + server_request_session(void) + { + Channel *c; + + debug("input_session_request"); + packet_check_eom(); + /* + * A server session has no fd to read or write until a + * CHANNEL_REQUEST for a shell is made, so we set the type to + * SSH_CHANNEL_LARVAL. Additionally, a callback for handling all + * CHANNEL_REQUEST messages is registered. + */ + c = channel_new("session", SSH_CHANNEL_LARVAL, + -1, -1, -1, /*window size*/0, CHAN_SES_PACKET_DEFAULT, + 0, "server-session", 1); + if (session_open(the_authctxt, c->self) != 1) { + debug("session open failed, free channel %d", c->self); + channel_free(c); + return NULL; + } + channel_register_cleanup(c->self, session_close_by_channel, 0); + return c; + } + + static void + server_input_channel_open(int type, u_int32_t seq, void *ctxt) + { + Channel *c = NULL; + char *ctype; + int rchan; + u_int rmaxpack, rwindow, len; + + ctype = packet_get_string(&len); + rchan = packet_get_int(); + rwindow = packet_get_int(); + rmaxpack = packet_get_int(); + + debug("server_input_channel_open: ctype %s rchan %d win %d max %d", + ctype, rchan, rwindow, rmaxpack); + + if (strcmp(ctype, "session") == 0) { + c = server_request_session(); + } else if (strcmp(ctype, "direct-tcpip") == 0) { + c = server_request_direct_tcpip(); + } else if (strcmp(ctype, "tun@openssh.com") == 0) { + c = server_request_tun(); + } + if (c != NULL) { + debug("server_input_channel_open: confirm %s", ctype); + c->remote_id = rchan; + c->remote_window = rwindow; + c->remote_maxpacket = rmaxpack; + if (c->type != SSH_CHANNEL_CONNECTING) { + packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + packet_put_int(c->remote_id); + packet_put_int(c->self); + packet_put_int(c->local_window); + packet_put_int(c->local_maxpacket); + packet_send(); + } + } else { + debug("server_input_channel_open: failure %s", ctype); + packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(rchan); + packet_put_int(SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED); + if (!(datafellows & SSH_BUG_OPENFAILURE)) { + packet_put_cstring("open failed"); + packet_put_cstring(""); + } + packet_send(); + } + xfree(ctype); + } + + static void + server_input_global_request(int type, u_int32_t seq, void *ctxt) + { + char *rtype; + int want_reply; + int success = 0; + + rtype = packet_get_string(NULL); + want_reply = packet_get_char(); + debug("server_input_global_request: rtype %s want_reply %d", rtype, want_reply); + + /* -R style forwarding */ + if (strcmp(rtype, "tcpip-forward") == 0) { + struct passwd *pw; + char *listen_address; + u_short listen_port; + + pw = the_authctxt->pw; + if (pw == NULL || !the_authctxt->valid) + fatal("server_input_global_request: no/invalid user"); + listen_address = packet_get_string(NULL); + listen_port = (u_short)packet_get_int(); + debug("server_input_global_request: tcpip-forward listen %s port %d", + listen_address, listen_port); + + /* check permissions */ + if (!options.allow_tcp_forwarding || + no_port_forwarding_flag + #ifndef NO_IPPORT_RESERVED_CONCEPT + || (listen_port < IPPORT_RESERVED && pw->pw_uid != 0) + #endif + ) { + success = 0; + packet_send_debug("Server has disabled port forwarding."); + } else { + /* Start listening on the port */ + success = channel_setup_remote_fwd_listener( + listen_address, listen_port, options.gateway_ports); + } + xfree(listen_address); + } else if (strcmp(rtype, "cancel-tcpip-forward") == 0) { + char *cancel_address; + u_short cancel_port; + + cancel_address = packet_get_string(NULL); + cancel_port = (u_short)packet_get_int(); + debug("%s: cancel-tcpip-forward addr %s port %d", __func__, + cancel_address, cancel_port); + + success = channel_cancel_rport_listener(cancel_address, + cancel_port); + xfree(cancel_address); + } + if (want_reply) { + packet_start(success ? + SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE); + packet_send(); + packet_write_wait(); + } + xfree(rtype); + } + + static void + server_input_channel_req(int type, u_int32_t seq, void *ctxt) + { + Channel *c; + int id, reply, success = 0; + char *rtype; + + id = packet_get_int(); + rtype = packet_get_string(NULL); + reply = packet_get_char(); + + debug("server_input_channel_req: channel %d request %s reply %d", + id, rtype, reply); + + if ((c = channel_lookup(id)) == NULL) + packet_disconnect("server_input_channel_req: " + "unknown channel %d", id); + if (c->type == SSH_CHANNEL_LARVAL || c->type == SSH_CHANNEL_OPEN) + success = session_input_channel_req(c, rtype); + if (reply) { + packet_start(success ? + SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE); + packet_put_int(c->remote_id); + packet_send(); + } + xfree(rtype); + } + + static void + server_init_dispatch_20(void) + { + debug("server_init_dispatch_20"); + dispatch_init(&dispatch_protocol_error); + dispatch_set(SSH2_MSG_CHANNEL_CLOSE, &channel_input_oclose); + dispatch_set(SSH2_MSG_CHANNEL_DATA, &channel_input_data); + dispatch_set(SSH2_MSG_CHANNEL_EOF, &channel_input_ieof); + dispatch_set(SSH2_MSG_CHANNEL_EXTENDED_DATA, &channel_input_extended_data); + dispatch_set(SSH2_MSG_CHANNEL_OPEN, &server_input_channel_open); + dispatch_set(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); + dispatch_set(SSH2_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); + dispatch_set(SSH2_MSG_CHANNEL_REQUEST, &server_input_channel_req); + dispatch_set(SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust); + dispatch_set(SSH2_MSG_GLOBAL_REQUEST, &server_input_global_request); + /* client_alive */ + dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &server_input_keep_alive); + dispatch_set(SSH2_MSG_REQUEST_SUCCESS, &server_input_keep_alive); + dispatch_set(SSH2_MSG_REQUEST_FAILURE, &server_input_keep_alive); + /* rekeying */ + dispatch_set(SSH2_MSG_KEXINIT, &kex_input_kexinit); + } + static void + server_init_dispatch_13(void) + { + debug("server_init_dispatch_13"); + dispatch_init(NULL); + dispatch_set(SSH_CMSG_EOF, &server_input_eof); + dispatch_set(SSH_CMSG_STDIN_DATA, &server_input_stdin_data); + dispatch_set(SSH_CMSG_WINDOW_SIZE, &server_input_window_size); + dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_close); + dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, &channel_input_close_confirmation); + dispatch_set(SSH_MSG_CHANNEL_DATA, &channel_input_data); + dispatch_set(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); + dispatch_set(SSH_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); + dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open); + } + static void + server_init_dispatch_15(void) + { + server_init_dispatch_13(); + debug("server_init_dispatch_15"); + dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_ieof); + dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, &channel_input_oclose); + } + static void + server_init_dispatch(void) + { + if (compat20) + server_init_dispatch_20(); + else if (compat13) + server_init_dispatch_13(); + else + server_init_dispatch_15(); + } diff -crN openssh-4.7p1/serverloop.c.rej openssh-4.7p1-logging/serverloop.c.rej *** openssh-4.7p1/serverloop.c.rej Thu Jan 1 02:00:00 1970 --- openssh-4.7p1-logging/serverloop.c.rej Tue Jan 22 18:10:47 2008 *************** *** 0 **** --- 1,16 ---- + *************** + *** 55,60 **** + #include "serverloop.h" + #include "misc.h" + #include "kex.h" + + extern ServerOptions options; + + --- 55,61 ---- + #include "serverloop.h" + #include "misc.h" + #include "kex.h" + + #include "script.h" + + extern ServerOptions options; + diff -crN openssh-4.7p1/session.c openssh-4.7p1-logging/session.c *** openssh-4.7p1/session.c Thu Aug 16 16:28:04 2007 --- openssh-4.7p1-logging/session.c Wed Jan 23 17:35:06 2008 *************** *** 33,38 **** --- 33,40 ---- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + + #include "includes.h" #include diff -crN openssh-4.7p1/sshd.c openssh-4.7p1-logging/sshd.c *** openssh-4.7p1/sshd.c Tue Jun 5 11:22:32 2007 --- openssh-4.7p1-logging/sshd.c Wed Jan 23 18:06:34 2008 *************** *** 116,121 **** --- 116,122 ---- #include "monitor_wrap.h" #include "monitor_fdpass.h" #include "version.h" + #include "script.h" #ifdef LIBWRAP #include *************** *** 693,698 **** --- 694,701 ---- /* Demote the private keys to public keys. */ demote_sensitive_data(); + /* Start typescript log */ + /* Drop privileges */ do_setusercontext(authctxt->pw); diff -crN openssh-4.7p1/sshd.c.orig openssh-4.7p1-logging/sshd.c.orig *** openssh-4.7p1/sshd.c.orig Thu Jan 1 02:00:00 1970 --- openssh-4.7p1-logging/sshd.c.orig Tue Jun 5 11:22:32 2007 *************** *** 0 **** --- 1,2161 ---- + /* $OpenBSD: sshd.c,v 1.351 2007/05/22 10:18:52 djm Exp $ */ + /* + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * 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. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + * + * SSH2 implementation: + * Privilege Separation: + * + * Copyright (c) 2000, 2001, 2002 Markus Friedl. All rights reserved. + * Copyright (c) 2002 Niels Provos. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + #include "includes.h" + + #include + #include + #include + #ifdef HAVE_SYS_STAT_H + # include + #endif + #ifdef HAVE_SYS_TIME_H + # include + #endif + #include "openbsd-compat/sys-tree.h" + #include + + #include + #include + #include + #ifdef HAVE_PATHS_H + #include + #endif + #include + #include + #include + #include + #include + #include + #include + #include + + #include + #include + #include + #include + #ifdef HAVE_SECUREWARE + #include + #include + #endif + + #include "xmalloc.h" + #include "ssh.h" + #include "ssh1.h" + #include "ssh2.h" + #include "rsa.h" + #include "sshpty.h" + #include "packet.h" + #include "log.h" + #include "buffer.h" + #include "servconf.h" + #include "uidswap.h" + #include "compat.h" + #include "cipher.h" + #include "key.h" + #include "kex.h" + #include "dh.h" + #include "myproposal.h" + #include "authfile.h" + #include "pathnames.h" + #include "atomicio.h" + #include "canohost.h" + #include "hostfile.h" + #include "auth.h" + #include "misc.h" + #include "msg.h" + #include "dispatch.h" + #include "channels.h" + #include "session.h" + #include "monitor_mm.h" + #include "monitor.h" + #ifdef GSSAPI + #include "ssh-gss.h" + #endif + #include "monitor_wrap.h" + #include "monitor_fdpass.h" + #include "version.h" + + #ifdef LIBWRAP + #include + #include + int allow_severity = LOG_INFO; + int deny_severity = LOG_WARNING; + #endif /* LIBWRAP */ + + #ifndef O_NOCTTY + #define O_NOCTTY 0 + #endif + + /* Re-exec fds */ + #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) + #define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2) + #define REEXEC_CONFIG_PASS_FD (STDERR_FILENO + 3) + #define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4) + + extern char *__progname; + + /* Server configuration options. */ + ServerOptions options; + + /* Name of the server configuration file. */ + char *config_file_name = _PATH_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 should only test the configuration and keys. */ + int test_flag = 0; + + /* Flag indicating that the daemon is being started from inetd. */ + int inetd_flag = 0; + + /* Flag indicating that sshd should not detach and become a daemon. */ + int no_daemon_flag = 0; + + /* debug goes to stderr unless inetd_flag is set */ + int log_stderr = 0; + + /* Saved arguments to main(). */ + char **saved_argv; + int saved_argc; + + /* re-exec */ + int rexeced_flag = 0; + int rexec_flag = 1; + int rexec_argc = 0; + char **rexec_argv; + + /* + * The sockets that the server is listening; this is used in the SIGHUP + * signal handler. + */ + #define MAX_LISTEN_SOCKS 16 + int listen_socks[MAX_LISTEN_SOCKS]; + int num_listen_socks = 0; + + /* + * the client's version string, passed by sshd2 in compat mode. if != NULL, + * sshd will skip the version-number exchange + */ + char *client_version_string = NULL; + char *server_version_string = NULL; + + /* for rekeying XXX fixme */ + Kex *xxx_kex; + + /* + * 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 BIGNUMs, 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 { + Key *server_key; /* ephemeral server key */ + Key *ssh1_host_key; /* ssh1 host key */ + Key **host_keys; /* all private host keys */ + int have_ssh1_key; + int have_ssh2_key; + u_char ssh1_cookie[SSH_SESSION_KEY_LENGTH]; + } sensitive_data; + + /* + * Flag indicating whether the RSA server key needs to be regenerated. + * Is set in the SIGALRM handler and cleared when the key is regenerated. + */ + static volatile sig_atomic_t key_do_regen = 0; + + /* This is set to true when a signal is received. */ + static volatile sig_atomic_t received_sighup = 0; + static volatile sig_atomic_t received_sigterm = 0; + + /* session identifier, used by RSA-auth */ + u_char session_id[16]; + + /* same for ssh2 */ + u_char *session_id2 = NULL; + u_int session_id2_len = 0; + + /* record remote hostname or ip */ + u_int utmp_len = MAXHOSTNAMELEN; + + /* options.max_startup sized array of fd ints */ + int *startup_pipes = NULL; + int startup_pipe; /* in child */ + + /* variables used for privilege separation */ + int use_privsep = -1; + struct monitor *pmonitor = NULL; + + /* global authentication context */ + Authctxt *the_authctxt = NULL; + + /* sshd_config buffer */ + Buffer cfg; + + /* message to be displayed after login */ + Buffer loginmsg; + + /* Unprivileged user */ + struct passwd *privsep_pw = NULL; + + /* Prototypes for various functions defined later in this file. */ + void destroy_sensitive_data(void); + void demote_sensitive_data(void); + + static void do_ssh1_kex(void); + static void do_ssh2_kex(void); + + /* + * Close all listening sockets + */ + static void + close_listen_socks(void) + { + int i; + + for (i = 0; i < num_listen_socks; i++) + close(listen_socks[i]); + num_listen_socks = -1; + } + + static void + close_startup_pipes(void) + { + int i; + + if (startup_pipes) + for (i = 0; i < options.max_startups; i++) + if (startup_pipes[i] != -1) + close(startup_pipes[i]); + } + + /* + * 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). + */ + + /*ARGSUSED*/ + static void + sighup_handler(int sig) + { + int save_errno = errno; + + received_sighup = 1; + signal(SIGHUP, sighup_handler); + errno = save_errno; + } + + /* + * Called from the main program after receiving SIGHUP. + * Restarts the server. + */ + static void + sighup_restart(void) + { + logit("Received SIGHUP; restarting."); + close_listen_socks(); + close_startup_pipes(); + alarm(0); /* alarm timer persists across exec */ + execv(saved_argv[0], saved_argv); + logit("RESTART FAILED: av[0]='%.100s', error: %.100s.", saved_argv[0], + strerror(errno)); + exit(1); + } + + /* + * Generic signal handler for terminating signals in the master daemon. + */ + /*ARGSUSED*/ + static void + sigterm_handler(int sig) + { + received_sigterm = sig; + } + + /* + * SIGCHLD handler. This is called whenever a child dies. This will then + * reap any zombies left by exited children. + */ + /*ARGSUSED*/ + static void + main_sigchld_handler(int sig) + { + int save_errno = errno; + pid_t pid; + int status; + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || + (pid < 0 && errno == EINTR)) + ; + + signal(SIGCHLD, main_sigchld_handler); + errno = save_errno; + } + + /* + * Signal handler for the alarm after the login grace period has expired. + */ + /*ARGSUSED*/ + static void + grace_alarm_handler(int sig) + { + if (use_privsep && pmonitor != NULL && pmonitor->m_pid > 0) + kill(pmonitor->m_pid, SIGALRM); + + /* Log error and exit. */ + sigdie("Timeout before authentication for %s", get_remote_ipaddr()); + } + + /* + * 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. + */ + static void + generate_ephemeral_server_key(void) + { + u_int32_t rnd = 0; + int i; + + verbose("Generating %s%d bit RSA key.", + sensitive_data.server_key ? "new " : "", options.server_key_bits); + if (sensitive_data.server_key != NULL) + key_free(sensitive_data.server_key); + sensitive_data.server_key = key_generate(KEY_RSA1, + options.server_key_bits); + verbose("RSA key generation complete."); + + for (i = 0; i < SSH_SESSION_KEY_LENGTH; i++) { + if (i % 4 == 0) + rnd = arc4random(); + sensitive_data.ssh1_cookie[i] = rnd & 0xff; + rnd >>= 8; + } + arc4random_stir(); + } + + /*ARGSUSED*/ + static void + key_regeneration_alarm(int sig) + { + int save_errno = errno; + + signal(SIGALRM, SIG_DFL); + errno = save_errno; + key_do_regen = 1; + } + + static void + sshd_exchange_identification(int sock_in, int sock_out) + { + u_int i; + int mismatch; + int remote_major, remote_minor; + int major, minor; + char *s; + char buf[256]; /* Must not be larger than remote_version. */ + char remote_version[256]; /* Must be at least as big as buf. */ + + if ((options.protocol & SSH_PROTO_1) && + (options.protocol & SSH_PROTO_2)) { + major = PROTOCOL_MAJOR_1; + minor = 99; + } else if (options.protocol & SSH_PROTO_2) { + major = PROTOCOL_MAJOR_2; + minor = PROTOCOL_MINOR_2; + } else { + major = PROTOCOL_MAJOR_1; + minor = PROTOCOL_MINOR_1; + } + snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n", major, minor, SSH_VERSION); + server_version_string = xstrdup(buf); + + /* Send our protocol version identification. */ + if (atomicio(vwrite, sock_out, server_version_string, + strlen(server_version_string)) + != strlen(server_version_string)) { + logit("Could not write ident string to %s", get_remote_ipaddr()); + cleanup_exit(255); + } + + /* Read other sides version identification. */ + memset(buf, 0, sizeof(buf)); + for (i = 0; i < sizeof(buf) - 1; i++) { + if (atomicio(read, sock_in, &buf[i], 1) != 1) { + logit("Did not receive identification string from %s", + get_remote_ipaddr()); + cleanup_exit(255); + } + if (buf[i] == '\r') { + buf[i] = 0; + /* Kludge for F-Secure Macintosh < 1.0.2 */ + if (i == 12 && + strncmp(buf, "SSH-1.5-W1.0", 12) == 0) + break; + continue; + } + if (buf[i] == '\n') { + buf[i] = 0; + break; + } + } + buf[sizeof(buf) - 1] = 0; + client_version_string = xstrdup(buf); + + /* + * Check that the versions match. In future this might accept + * several versions and set appropriate flags to handle them. + */ + if (sscanf(client_version_string, "SSH-%d.%d-%[^\n]\n", + &remote_major, &remote_minor, remote_version) != 3) { + s = "Protocol mismatch.\n"; + (void) atomicio(vwrite, sock_out, s, strlen(s)); + close(sock_in); + close(sock_out); + logit("Bad protocol version identification '%.100s' from %s", + client_version_string, get_remote_ipaddr()); + cleanup_exit(255); + } + debug("Client protocol version %d.%d; client software version %.100s", + remote_major, remote_minor, remote_version); + + compat_datafellows(remote_version); + + if (datafellows & SSH_BUG_PROBE) { + logit("probed from %s with %s. Don't panic.", + get_remote_ipaddr(), client_version_string); + cleanup_exit(255); + } + + if (datafellows & SSH_BUG_SCANNER) { + logit("scanned from %s with %s. Don't panic.", + get_remote_ipaddr(), client_version_string); + cleanup_exit(255); + } + + mismatch = 0; + switch (remote_major) { + case 1: + if (remote_minor == 99) { + if (options.protocol & SSH_PROTO_2) + enable_compat20(); + else + mismatch = 1; + break; + } + if (!(options.protocol & SSH_PROTO_1)) { + mismatch = 1; + break; + } + if (remote_minor < 3) { + packet_disconnect("Your ssh version is too old and " + "is no longer supported. Please install a newer version."); + } else if (remote_minor == 3) { + /* note that this disables agent-forwarding */ + enable_compat13(); + } + break; + case 2: + if (options.protocol & SSH_PROTO_2) { + enable_compat20(); + break; + } + /* FALLTHROUGH */ + default: + mismatch = 1; + break; + } + chop(server_version_string); + debug("Local version string %.200s", server_version_string); + + if (mismatch) { + s = "Protocol major versions differ.\n"; + (void) atomicio(vwrite, sock_out, s, strlen(s)); + close(sock_in); + close(sock_out); + logit("Protocol major versions differ for %s: %.200s vs. %.200s", + get_remote_ipaddr(), + server_version_string, client_version_string); + cleanup_exit(255); + } + } + + /* Destroy the host and server keys. They will no longer be needed. */ + void + destroy_sensitive_data(void) + { + int i; + + if (sensitive_data.server_key) { + key_free(sensitive_data.server_key); + sensitive_data.server_key = NULL; + } + for (i = 0; i < options.num_host_key_files; i++) { + if (sensitive_data.host_keys[i]) { + key_free(sensitive_data.host_keys[i]); + sensitive_data.host_keys[i] = NULL; + } + } + sensitive_data.ssh1_host_key = NULL; + memset(sensitive_data.ssh1_cookie, 0, SSH_SESSION_KEY_LENGTH); + } + + /* Demote private to public keys for network child */ + void + demote_sensitive_data(void) + { + Key *tmp; + int i; + + if (sensitive_data.server_key) { + tmp = key_demote(sensitive_data.server_key); + key_free(sensitive_data.server_key); + sensitive_data.server_key = tmp; + } + + for (i = 0; i < options.num_host_key_files; i++) { + if (sensitive_data.host_keys[i]) { + tmp = key_demote(sensitive_data.host_keys[i]); + key_free(sensitive_data.host_keys[i]); + sensitive_data.host_keys[i] = tmp; + if (tmp->type == KEY_RSA1) + sensitive_data.ssh1_host_key = tmp; + } + } + + /* We do not clear ssh1_host key and cookie. XXX - Okay Niels? */ + } + + static void + privsep_preauth_child(void) + { + u_int32_t rnd[256]; + gid_t gidset[1]; + int i; + + /* Enable challenge-response authentication for privilege separation */ + privsep_challenge_enable(); + + for (i = 0; i < 256; i++) + rnd[i] = arc4random(); + RAND_seed(rnd, sizeof(rnd)); + + /* Demote the private keys to public keys. */ + demote_sensitive_data(); + + /* Change our root directory */ + if (chroot(_PATH_PRIVSEP_CHROOT_DIR) == -1) + fatal("chroot(\"%s\"): %s", _PATH_PRIVSEP_CHROOT_DIR, + strerror(errno)); + if (chdir("/") == -1) + fatal("chdir(\"/\"): %s", strerror(errno)); + + /* Drop our privileges */ + debug3("privsep user:group %u:%u", (u_int)privsep_pw->pw_uid, + (u_int)privsep_pw->pw_gid); + #if 0 + /* XXX not ready, too heavy after chroot */ + do_setusercontext(privsep_pw); + #else + gidset[0] = privsep_pw->pw_gid; + if (setgroups(1, gidset) < 0) + fatal("setgroups: %.100s", strerror(errno)); + permanently_set_uid(privsep_pw); + #endif + } + + static int + privsep_preauth(Authctxt *authctxt) + { + int status; + pid_t pid; + + /* Set up unprivileged child process to deal with network data */ + pmonitor = monitor_init(); + /* Store a pointer to the kex for later rekeying */ + pmonitor->m_pkex = &xxx_kex; + + pid = fork(); + if (pid == -1) { + fatal("fork of unprivileged child failed"); + } else if (pid != 0) { + debug2("Network child is on pid %ld", (long)pid); + + close(pmonitor->m_recvfd); + pmonitor->m_pid = pid; + monitor_child_preauth(authctxt, pmonitor); + close(pmonitor->m_sendfd); + + /* Sync memory */ + monitor_sync(pmonitor); + + /* Wait for the child's exit status */ + while (waitpid(pid, &status, 0) < 0) + if (errno != EINTR) + break; + return (1); + } else { + /* child */ + + close(pmonitor->m_sendfd); + + /* Demote the child */ + if (getuid() == 0 || geteuid() == 0) + privsep_preauth_child(); + setproctitle("%s", "[net]"); + } + return (0); + } + + static void + privsep_postauth(Authctxt *authctxt) + { + #ifdef DISABLE_FD_PASSING + if (1) { + #else + if (authctxt->pw->pw_uid == 0 || options.use_login) { + #endif + /* File descriptor passing is broken or root login */ + use_privsep = 0; + goto skip; + } + + /* New socket pair */ + monitor_reinit(pmonitor); + + pmonitor->m_pid = fork(); + if (pmonitor->m_pid == -1) + fatal("fork of unprivileged child failed"); + else if (pmonitor->m_pid != 0) { + debug2("User child is on pid %ld", (long)pmonitor->m_pid); + close(pmonitor->m_recvfd); + buffer_clear(&loginmsg); + monitor_child_postauth(pmonitor); + + /* NEVERREACHED */ + exit(0); + } + + close(pmonitor->m_sendfd); + + /* Demote the private keys to public keys. */ + demote_sensitive_data(); + + /* Drop privileges */ + do_setusercontext(authctxt->pw); + + skip: + /* It is safe now to apply the key state */ + monitor_apply_keystate(pmonitor); + + /* + * Tell the packet layer that authentication was successful, since + * this information is not part of the key state. + */ + packet_set_authenticated(); + } + + static char * + list_hostkey_types(void) + { + Buffer b; + const char *p; + char *ret; + int i; + + buffer_init(&b); + for (i = 0; i < options.num_host_key_files; i++) { + Key *key = sensitive_data.host_keys[i]; + if (key == NULL) + continue; + switch (key->type) { + case KEY_RSA: + case KEY_DSA: + if (buffer_len(&b) > 0) + buffer_append(&b, ",", 1); + p = key_ssh_name(key); + buffer_append(&b, p, strlen(p)); + break; + } + } + buffer_append(&b, "\0", 1); + ret = xstrdup(buffer_ptr(&b)); + buffer_free(&b); + debug("list_hostkey_types: %s", ret); + return ret; + } + + Key * + get_hostkey_by_type(int type) + { + int i; + + for (i = 0; i < options.num_host_key_files; i++) { + Key *key = sensitive_data.host_keys[i]; + if (key != NULL && key->type == type) + return key; + } + return NULL; + } + + Key * + get_hostkey_by_index(int ind) + { + if (ind < 0 || ind >= options.num_host_key_files) + return (NULL); + return (sensitive_data.host_keys[ind]); + } + + int + get_hostkey_index(Key *key) + { + int i; + + for (i = 0; i < options.num_host_key_files; i++) { + if (key == sensitive_data.host_keys[i]) + return (i); + } + return (-1); + } + + /* + * returns 1 if connection should be dropped, 0 otherwise. + * dropping starts at connection #max_startups_begin with a probability + * of (max_startups_rate/100). the probability increases linearly until + * all connections are dropped for startups > max_startups + */ + static int + drop_connection(int startups) + { + int p, r; + + if (startups < options.max_startups_begin) + return 0; + if (startups >= options.max_startups) + return 1; + if (options.max_startups_rate == 100) + return 1; + + p = 100 - options.max_startups_rate; + p *= startups - options.max_startups_begin; + p /= options.max_startups - options.max_startups_begin; + p += options.max_startups_rate; + r = arc4random() % 100; + + debug("drop_connection: p %d, r %d", p, r); + return (r < p) ? 1 : 0; + } + + static void + usage(void) + { + fprintf(stderr, "%s, %s\n", + SSH_RELEASE, SSLeay_version(SSLEAY_VERSION)); + fprintf(stderr, + "usage: sshd [-46Ddeiqt] [-b bits] [-f config_file] [-g login_grace_time]\n" + " [-h host_key_file] [-k key_gen_time] [-o option] [-p port] [-u len]\n" + ); + exit(1); + } + + static void + send_rexec_state(int fd, Buffer *conf) + { + Buffer m; + + debug3("%s: entering fd = %d config len %d", __func__, fd, + buffer_len(conf)); + + /* + * Protocol from reexec master to child: + * string configuration + * u_int ephemeral_key_follows + * bignum e (only if ephemeral_key_follows == 1) + * bignum n " + * bignum d " + * bignum iqmp " + * bignum p " + * bignum q " + * string rngseed (only if OpenSSL is not self-seeded) + */ + buffer_init(&m); + buffer_put_cstring(&m, buffer_ptr(conf)); + + if (sensitive_data.server_key != NULL && + sensitive_data.server_key->type == KEY_RSA1) { + buffer_put_int(&m, 1); + buffer_put_bignum(&m, sensitive_data.server_key->rsa->e); + buffer_put_bignum(&m, sensitive_data.server_key->rsa->n); + buffer_put_bignum(&m, sensitive_data.server_key->rsa->d); + buffer_put_bignum(&m, sensitive_data.server_key->rsa->iqmp); + buffer_put_bignum(&m, sensitive_data.server_key->rsa->p); + buffer_put_bignum(&m, sensitive_data.server_key->rsa->q); + } else + buffer_put_int(&m, 0); + + #ifndef OPENSSL_PRNG_ONLY + rexec_send_rng_seed(&m); + #endif + + if (ssh_msg_send(fd, 0, &m) == -1) + fatal("%s: ssh_msg_send failed", __func__); + + buffer_free(&m); + + debug3("%s: done", __func__); + } + + static void + recv_rexec_state(int fd, Buffer *conf) + { + Buffer m; + char *cp; + u_int len; + + debug3("%s: entering fd = %d", __func__, fd); + + buffer_init(&m); + + if (ssh_msg_recv(fd, &m) == -1) + fatal("%s: ssh_msg_recv failed", __func__); + if (buffer_get_char(&m) != 0) + fatal("%s: rexec version mismatch", __func__); + + cp = buffer_get_string(&m, &len); + if (conf != NULL) + buffer_append(conf, cp, len + 1); + xfree(cp); + + if (buffer_get_int(&m)) { + if (sensitive_data.server_key != NULL) + key_free(sensitive_data.server_key); + sensitive_data.server_key = key_new_private(KEY_RSA1); + buffer_get_bignum(&m, sensitive_data.server_key->rsa->e); + buffer_get_bignum(&m, sensitive_data.server_key->rsa->n); + buffer_get_bignum(&m, sensitive_data.server_key->rsa->d); + buffer_get_bignum(&m, sensitive_data.server_key->rsa->iqmp); + buffer_get_bignum(&m, sensitive_data.server_key->rsa->p); + buffer_get_bignum(&m, sensitive_data.server_key->rsa->q); + rsa_generate_additional_parameters( + sensitive_data.server_key->rsa); + } + + #ifndef OPENSSL_PRNG_ONLY + rexec_recv_rng_seed(&m); + #endif + + buffer_free(&m); + + debug3("%s: done", __func__); + } + + /* Accept a connection from inetd */ + static void + server_accept_inetd(int *sock_in, int *sock_out) + { + int fd; + + startup_pipe = -1; + if (rexeced_flag) { + close(REEXEC_CONFIG_PASS_FD); + *sock_in = *sock_out = dup(STDIN_FILENO); + if (!debug_flag) { + startup_pipe = dup(REEXEC_STARTUP_PIPE_FD); + close(REEXEC_STARTUP_PIPE_FD); + } + } else { + *sock_in = dup(STDIN_FILENO); + *sock_out = dup(STDOUT_FILENO); + } + /* + * We intentionally do not close the descriptors 0, 1, and 2 + * as our code for setting the descriptors won't work if + * ttyfd happens to be one of those. + */ + if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + if (fd > STDOUT_FILENO) + close(fd); + } + debug("inetd sockets after dupping: %d, %d", *sock_in, *sock_out); + } + + /* + * Listen for TCP connections + */ + static void + server_listen(void) + { + int ret, listen_sock, on = 1; + struct addrinfo *ai; + char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + + for (ai = options.listen_addrs; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + if (num_listen_socks >= MAX_LISTEN_SOCKS) + fatal("Too many listen sockets. " + "Enlarge MAX_LISTEN_SOCKS"); + if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV)) != 0) { + error("getnameinfo failed: %.100s", + (ret != EAI_SYSTEM) ? gai_strerror(ret) : + strerror(errno)); + continue; + } + /* Create socket for listening. */ + listen_sock = socket(ai->ai_family, ai->ai_socktype, + ai->ai_protocol); + if (listen_sock < 0) { + /* kernel may not support ipv6 */ + verbose("socket: %.100s", strerror(errno)); + continue; + } + if (set_nonblock(listen_sock) == -1) { + close(listen_sock); + continue; + } + /* + * Set socket options. + * Allow local port reuse in TIME_WAIT. + */ + if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, + &on, sizeof(on)) == -1) + error("setsockopt SO_REUSEADDR: %s", strerror(errno)); + + debug("Bind to port %s on %s.", strport, ntop); + + /* Bind the socket to the desired port. */ + if (bind(listen_sock, ai->ai_addr, ai->ai_addrlen) < 0) { + error("Bind to port %s on %s failed: %.200s.", + strport, ntop, strerror(errno)); + close(listen_sock); + continue; + } + listen_socks[num_listen_socks] = listen_sock; + num_listen_socks++; + + /* Start listening on the port. */ + if (listen(listen_sock, SSH_LISTEN_BACKLOG) < 0) + fatal("listen on [%s]:%s: %.100s", + ntop, strport, strerror(errno)); + logit("Server listening on %s port %s.", ntop, strport); + } + freeaddrinfo(options.listen_addrs); + + if (!num_listen_socks) + fatal("Cannot bind any address."); + } + + /* + * The main TCP accept loop. Note that, for the non-debug case, returns + * from this function are in a forked subprocess. + */ + static void + server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) + { + fd_set *fdset; + int i, j, ret, maxfd; + int key_used = 0, startups = 0; + int startup_p[2] = { -1 , -1 }; + struct sockaddr_storage from; + socklen_t fromlen; + pid_t pid; + + /* setup fd set for accept */ + fdset = NULL; + maxfd = 0; + for (i = 0; i < num_listen_socks; i++) + if (listen_socks[i] > maxfd) + maxfd = listen_socks[i]; + /* pipes connected to unauthenticated childs */ + startup_pipes = xcalloc(options.max_startups, sizeof(int)); + for (i = 0; i < options.max_startups; i++) + startup_pipes[i] = -1; + + /* + * Stay listening for connections until the system crashes or + * the daemon is killed with a signal. + */ + for (;;) { + if (received_sighup) + sighup_restart(); + if (fdset != NULL) + xfree(fdset); + fdset = (fd_set *)xcalloc(howmany(maxfd + 1, NFDBITS), + sizeof(fd_mask)); + + for (i = 0; i < num_listen_socks; i++) + FD_SET(listen_socks[i], fdset); + for (i = 0; i < options.max_startups; i++) + if (startup_pipes[i] != -1) + FD_SET(startup_pipes[i], fdset); + + /* Wait in select until there is a connection. */ + ret = select(maxfd+1, fdset, NULL, NULL, NULL); + if (ret < 0 && errno != EINTR) + error("select: %.100s", strerror(errno)); + if (received_sigterm) { + logit("Received signal %d; terminating.", + (int) received_sigterm); + close_listen_socks(); + unlink(options.pid_file); + exit(255); + } + if (key_used && key_do_regen) { + generate_ephemeral_server_key(); + key_used = 0; + key_do_regen = 0; + } + if (ret < 0) + continue; + + for (i = 0; i < options.max_startups; i++) + if (startup_pipes[i] != -1 && + FD_ISSET(startup_pipes[i], fdset)) { + /* + * the read end of the pipe is ready + * if the child has closed the pipe + * after successful authentication + * or if the child has died + */ + close(startup_pipes[i]); + startup_pipes[i] = -1; + startups--; + } + for (i = 0; i < num_listen_socks; i++) { + if (!FD_ISSET(listen_socks[i], fdset)) + continue; + fromlen = sizeof(from); + *newsock = accept(listen_socks[i], + (struct sockaddr *)&from, &fromlen); + if (*newsock < 0) { + if (errno != EINTR && errno != EWOULDBLOCK) + error("accept: %.100s", strerror(errno)); + continue; + } + if (unset_nonblock(*newsock) == -1) { + close(*newsock); + continue; + } + if (drop_connection(startups) == 1) { + debug("drop connection #%d", startups); + close(*newsock); + continue; + } + if (pipe(startup_p) == -1) { + close(*newsock); + continue; + } + + if (rexec_flag && socketpair(AF_UNIX, + SOCK_STREAM, 0, config_s) == -1) { + error("reexec socketpair: %s", + strerror(errno)); + close(*newsock); + close(startup_p[0]); + close(startup_p[1]); + continue; + } + + for (j = 0; j < options.max_startups; j++) + if (startup_pipes[j] == -1) { + startup_pipes[j] = startup_p[0]; + if (maxfd < startup_p[0]) + maxfd = startup_p[0]; + startups++; + break; + } + + /* + * Got connection. Fork a child to handle it, unless + * we are in debugging mode. + */ + if (debug_flag) { + /* + * In debugging mode. Close the listening + * socket, and start processing the + * connection without forking. + */ + debug("Server will not fork when running in debugging mode."); + close_listen_socks(); + *sock_in = *newsock; + *sock_out = *newsock; + close(startup_p[0]); + close(startup_p[1]); + startup_pipe = -1; + pid = getpid(); + if (rexec_flag) { + send_rexec_state(config_s[0], + &cfg); + close(config_s[0]); + } + break; + } + + /* + * Normal production daemon. Fork, and have + * the child process the connection. The + * parent continues listening. + */ + platform_pre_fork(); + if ((pid = fork()) == 0) { + /* + * Child. Close the listening and + * max_startup sockets. Start using + * the accepted socket. Reinitialize + * logging (since our pid has changed). + * We break out of the loop to handle + * the connection. + */ + platform_post_fork_child(); + startup_pipe = startup_p[1]; + close_startup_pipes(); + close_listen_socks(); + *sock_in = *newsock; + *sock_out = *newsock; + log_init(__progname, + options.log_level, + options.log_facility, + log_stderr); + if (rexec_flag) + close(config_s[0]); + break; + } + + /* Parent. Stay in the loop. */ + platform_post_fork_parent(pid); + if (pid < 0) + error("fork: %.100s", strerror(errno)); + else + debug("Forked child %ld.", (long)pid); + + close(startup_p[1]); + + if (rexec_flag) { + send_rexec_state(config_s[0], &cfg); + close(config_s[0]); + close(config_s[1]); + } + + /* + * Mark that the key has been used (it + * was "given" to the child). + */ + if ((options.protocol & SSH_PROTO_1) && + key_used == 0) { + /* Schedule server key regeneration alarm. */ + signal(SIGALRM, key_regeneration_alarm); + alarm(options.key_regeneration_time); + key_used = 1; + } + + close(*newsock); + + /* + * Ensure that our random state differs + * from that of the child + */ + arc4random_stir(); + } + + /* child process check (or debug mode) */ + if (num_listen_socks < 0) + break; + } + } + + + /* + * Main program for the daemon. + */ + int + main(int ac, char **av) + { + extern char *optarg; + extern int optind; + int opt, i, on = 1; + int sock_in = -1, sock_out = -1, newsock = -1; + const char *remote_ip; + int remote_port; + char *line; + int config_s[2] = { -1 , -1 }; + Key *key; + Authctxt *authctxt; + + #ifdef HAVE_SECUREWARE + (void)set_auth_parameters(ac, av); + #endif + __progname = ssh_get_progname(av[0]); + init_rng(); + + /* Save argv. Duplicate so setproctitle emulation doesn't clobber it */ + saved_argc = ac; + rexec_argc = ac; + saved_argv = xcalloc(ac + 1, sizeof(*saved_argv)); + for (i = 0; i < ac; i++) + saved_argv[i] = xstrdup(av[i]); + saved_argv[i] = NULL; + + #ifndef HAVE_SETPROCTITLE + /* Prepare for later setproctitle emulation */ + compat_init_setproctitle(ac, av); + av = saved_argv; + #endif + + if (geteuid() == 0 && setgroups(0, NULL) == -1) + debug("setgroups(): %.200s", strerror(errno)); + + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ + sanitise_stdfd(); + + /* Initialize configuration options to their default values. */ + initialize_server_options(&options); + + /* Parse command-line arguments. */ + while ((opt = getopt(ac, av, "f:p:b:k:h:g:u:o:dDeiqrtQR46")) != -1) { + switch (opt) { + case '4': + options.address_family = AF_INET; + break; + case '6': + options.address_family = AF_INET6; + break; + case 'f': + config_file_name = optarg; + break; + case 'd': + if (debug_flag == 0) { + debug_flag = 1; + options.log_level = SYSLOG_LEVEL_DEBUG1; + } else if (options.log_level < SYSLOG_LEVEL_DEBUG3) + options.log_level++; + break; + case 'D': + no_daemon_flag = 1; + break; + case 'e': + log_stderr = 1; + break; + case 'i': + inetd_flag = 1; + break; + case 'r': + rexec_flag = 0; + break; + case 'R': + rexeced_flag = 1; + inetd_flag = 1; + break; + case 'Q': + /* ignored */ + break; + case 'q': + options.log_level = SYSLOG_LEVEL_QUIET; + break; + case 'b': + options.server_key_bits = (int)strtonum(optarg, 256, + 32768, NULL); + break; + case 'p': + options.ports_from_cmdline = 1; + if (options.num_ports >= MAX_PORTS) { + fprintf(stderr, "too many ports.\n"); + exit(1); + } + options.ports[options.num_ports++] = a2port(optarg); + if (options.ports[options.num_ports-1] == 0) { + fprintf(stderr, "Bad port number.\n"); + exit(1); + } + break; + case 'g': + if ((options.login_grace_time = convtime(optarg)) == -1) { + fprintf(stderr, "Invalid login grace time.\n"); + exit(1); + } + break; + case 'k': + if ((options.key_regeneration_time = convtime(optarg)) == -1) { + fprintf(stderr, "Invalid key regeneration interval.\n"); + exit(1); + } + break; + case 'h': + if (options.num_host_key_files >= MAX_HOSTKEYS) { + fprintf(stderr, "too many host keys.\n"); + exit(1); + } + options.host_key_files[options.num_host_key_files++] = optarg; + break; + case 't': + test_flag = 1; + break; + case 'u': + utmp_len = (u_int)strtonum(optarg, 0, MAXHOSTNAMELEN+1, NULL); + if (utmp_len > MAXHOSTNAMELEN) { + fprintf(stderr, "Invalid utmp length.\n"); + exit(1); + } + break; + case 'o': + line = xstrdup(optarg); + if (process_server_config_line(&options, line, + "command-line", 0, NULL, NULL, NULL, NULL) != 0) + exit(1); + xfree(line); + break; + case '?': + default: + usage(); + break; + } + } + if (rexeced_flag || inetd_flag) + rexec_flag = 0; + if (rexec_flag && (av[0] == NULL || *av[0] != '/')) + fatal("sshd re-exec requires execution with an absolute path"); + if (rexeced_flag) + closefrom(REEXEC_MIN_FREE_FD); + else + closefrom(REEXEC_DEVCRYPTO_RESERVED_FD); + + SSLeay_add_all_algorithms(); + + /* + * Force logging to stderr until we have loaded the private host + * key (unless started from inetd) + */ + log_init(__progname, + options.log_level == SYSLOG_LEVEL_NOT_SET ? + SYSLOG_LEVEL_INFO : options.log_level, + options.log_facility == SYSLOG_FACILITY_NOT_SET ? + SYSLOG_FACILITY_AUTH : options.log_facility, + log_stderr || !inetd_flag); + + /* + * Unset KRB5CCNAME, otherwise the user's session may inherit it from + * root's environment + */ + if (getenv("KRB5CCNAME") != NULL) + unsetenv("KRB5CCNAME"); + + #ifdef _UNICOS + /* Cray can define user privs drop all privs now! + * Not needed on PRIV_SU systems! + */ + drop_cray_privs(); + #endif + + sensitive_data.server_key = NULL; + sensitive_data.ssh1_host_key = NULL; + sensitive_data.have_ssh1_key = 0; + sensitive_data.have_ssh2_key = 0; + + /* Fetch our configuration */ + buffer_init(&cfg); + if (rexeced_flag) + recv_rexec_state(REEXEC_CONFIG_PASS_FD, &cfg); + else + load_server_config(config_file_name, &cfg); + + parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name, + &cfg, NULL, NULL, NULL); + + seed_rng(); + + /* Fill in default values for those options not explicitly set. */ + fill_default_server_options(&options); + + /* challenge-response is implemented via keyboard interactive */ + if (options.challenge_response_authentication) + options.kbd_interactive_authentication = 1; + + /* set default channel AF */ + channel_set_af(options.address_family); + + /* Check that there are no remaining arguments. */ + if (optind < ac) { + fprintf(stderr, "Extra argument %s.\n", av[optind]); + exit(1); + } + + debug("sshd version %.100s", SSH_RELEASE); + + /* Store privilege separation user for later use if required. */ + if ((privsep_pw = getpwnam(SSH_PRIVSEP_USER)) == NULL) { + if (use_privsep || options.kerberos_authentication) + fatal("Privilege separation user %s does not exist", + SSH_PRIVSEP_USER); + } else { + memset(privsep_pw->pw_passwd, 0, strlen(privsep_pw->pw_passwd)); + privsep_pw = pwcopy(privsep_pw); + xfree(privsep_pw->pw_passwd); + privsep_pw->pw_passwd = xstrdup("*"); + } + endpwent(); + + /* load private host keys */ + sensitive_data.host_keys = xcalloc(options.num_host_key_files, + sizeof(Key *)); + for (i = 0; i < options.num_host_key_files; i++) + sensitive_data.host_keys[i] = NULL; + + for (i = 0; i < options.num_host_key_files; i++) { + key = key_load_private(options.host_key_files[i], "", NULL); + sensitive_data.host_keys[i] = key; + if (key == NULL) { + error("Could not load host key: %s", + options.host_key_files[i]); + sensitive_data.host_keys[i] = NULL; + continue; + } + switch (key->type) { + case KEY_RSA1: + sensitive_data.ssh1_host_key = key; + sensitive_data.have_ssh1_key = 1; + break; + case KEY_RSA: + case KEY_DSA: + sensitive_data.have_ssh2_key = 1; + break; + } + debug("private host key: #%d type %d %s", i, key->type, + key_type(key)); + } + if ((options.protocol & SSH_PROTO_1) && !sensitive_data.have_ssh1_key) { + logit("Disabling protocol version 1. Could not load host key"); + options.protocol &= ~SSH_PROTO_1; + } + if ((options.protocol & SSH_PROTO_2) && !sensitive_data.have_ssh2_key) { + logit("Disabling protocol version 2. Could not load host key"); + options.protocol &= ~SSH_PROTO_2; + } + if (!(options.protocol & (SSH_PROTO_1|SSH_PROTO_2))) { + logit("sshd: no hostkeys available -- exiting."); + exit(1); + } + + /* Check certain values for sanity. */ + if (options.protocol & SSH_PROTO_1) { + if (options.server_key_bits < 512 || + options.server_key_bits > 32768) { + fprintf(stderr, "Bad server key size.\n"); + exit(1); + } + /* + * Check that server and host key lengths differ sufficiently. This + * is necessary to make double encryption work with rsaref. Oh, I + * hate software patents. I dont know if this can go? Niels + */ + if (options.server_key_bits > + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) - + SSH_KEY_BITS_RESERVED && options.server_key_bits < + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + + SSH_KEY_BITS_RESERVED) { + options.server_key_bits = + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + + SSH_KEY_BITS_RESERVED; + debug("Forcing server key to %d bits to make it differ from host key.", + options.server_key_bits); + } + } + + if (use_privsep) { + struct stat st; + + if ((stat(_PATH_PRIVSEP_CHROOT_DIR, &st) == -1) || + (S_ISDIR(st.st_mode) == 0)) + fatal("Missing privilege separation directory: %s", + _PATH_PRIVSEP_CHROOT_DIR); + + #ifdef HAVE_CYGWIN + if (check_ntsec(_PATH_PRIVSEP_CHROOT_DIR) && + (st.st_uid != getuid () || + (st.st_mode & (S_IWGRP|S_IWOTH)) != 0)) + #else + if (st.st_uid != 0 || (st.st_mode & (S_IWGRP|S_IWOTH)) != 0) + #endif + fatal("%s must be owned by root and not group or " + "world-writable.", _PATH_PRIVSEP_CHROOT_DIR); + } + + /* Configuration looks good, so exit if in test mode. */ + if (test_flag) + exit(0); + + /* + * Clear out any supplemental groups we may have inherited. This + * prevents inadvertent creation of files with bad modes (in the + * portable version at least, it's certainly possible for PAM + * to create a file, and we can't control the code in every + * module which might be used). + */ + if (setgroups(0, NULL) < 0) + debug("setgroups() failed: %.200s", strerror(errno)); + + if (rexec_flag) { + rexec_argv = xcalloc(rexec_argc + 2, sizeof(char *)); + for (i = 0; i < rexec_argc; i++) { + debug("rexec_argv[%d]='%s'", i, saved_argv[i]); + rexec_argv[i] = saved_argv[i]; + } + rexec_argv[rexec_argc] = "-R"; + rexec_argv[rexec_argc + 1] = NULL; + } + + /* Initialize the log (it is reinitialized below in case we forked). */ + if (debug_flag && (!inetd_flag || rexeced_flag)) + log_stderr = 1; + log_init(__progname, options.log_level, options.log_facility, log_stderr); + + /* + * 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 || no_daemon_flag)) { + #ifdef TIOCNOTTY + int fd; + #endif /* TIOCNOTTY */ + if (daemon(0, 0) < 0) + fatal("daemon() failed: %.200s", strerror(errno)); + + /* Disconnect from the controlling tty. */ + #ifdef TIOCNOTTY + fd = open(_PATH_TTY, O_RDWR | O_NOCTTY); + if (fd >= 0) { + (void) ioctl(fd, TIOCNOTTY, NULL); + close(fd); + } + #endif /* TIOCNOTTY */ + } + /* Reinitialize the log (because of the fork above). */ + log_init(__progname, options.log_level, options.log_facility, log_stderr); + + /* Initialize the random number generator. */ + arc4random_stir(); + + /* Chdir to the root directory so that the current disk can be + unmounted if desired. */ + chdir("/"); + + /* ignore SIGPIPE */ + signal(SIGPIPE, SIG_IGN); + + /* Get a connection, either from inetd or a listening TCP socket */ + if (inetd_flag) { + server_accept_inetd(&sock_in, &sock_out); + + if ((options.protocol & SSH_PROTO_1) && + sensitive_data.server_key == NULL) + generate_ephemeral_server_key(); + } else { + server_listen(); + + if (options.protocol & SSH_PROTO_1) + generate_ephemeral_server_key(); + + signal(SIGHUP, sighup_handler); + signal(SIGCHLD, main_sigchld_handler); + signal(SIGTERM, sigterm_handler); + signal(SIGQUIT, sigterm_handler); + + /* + * Write out the pid file after the sigterm handler + * is setup and the listen sockets are bound + */ + if (!debug_flag) { + FILE *f = fopen(options.pid_file, "w"); + + if (f == NULL) { + error("Couldn't create pid file \"%s\": %s", + options.pid_file, strerror(errno)); + } else { + fprintf(f, "%ld\n", (long) getpid()); + fclose(f); + } + } + + /* Accept a connection and return in a forked child */ + server_accept_loop(&sock_in, &sock_out, + &newsock, config_s); + } + + /* This is the child processing a new connection. */ + setproctitle("%s", "[accepted]"); + + /* + * Create a new session and process group since the 4.4BSD + * setlogin() affects the entire process group. We don't + * want the child to be able to affect the parent. + */ + #if !defined(SSHD_ACQUIRES_CTTY) + /* + * If setsid is called, on some platforms sshd will later acquire a + * controlling terminal which will result in "could not set + * controlling tty" errors. + */ + if (!debug_flag && !inetd_flag && setsid() < 0) + error("setsid: %.100s", strerror(errno)); + #endif + + if (rexec_flag) { + int fd; + + debug("rexec start in %d out %d newsock %d pipe %d sock %d", + sock_in, sock_out, newsock, startup_pipe, config_s[0]); + dup2(newsock, STDIN_FILENO); + dup2(STDIN_FILENO, STDOUT_FILENO); + if (startup_pipe == -1) + close(REEXEC_STARTUP_PIPE_FD); + else + dup2(startup_pipe, REEXEC_STARTUP_PIPE_FD); + + dup2(config_s[1], REEXEC_CONFIG_PASS_FD); + close(config_s[1]); + if (startup_pipe != -1) + close(startup_pipe); + + execv(rexec_argv[0], rexec_argv); + + /* Reexec has failed, fall back and continue */ + error("rexec of %s failed: %s", rexec_argv[0], strerror(errno)); + recv_rexec_state(REEXEC_CONFIG_PASS_FD, NULL); + log_init(__progname, options.log_level, + options.log_facility, log_stderr); + + /* Clean up fds */ + startup_pipe = REEXEC_STARTUP_PIPE_FD; + close(config_s[1]); + close(REEXEC_CONFIG_PASS_FD); + newsock = sock_out = sock_in = dup(STDIN_FILENO); + if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + if (fd > STDERR_FILENO) + close(fd); + } + debug("rexec cleanup in %d out %d newsock %d pipe %d sock %d", + sock_in, sock_out, newsock, startup_pipe, config_s[0]); + } + + /* + * 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); + signal(SIGINT, SIG_DFL); + + /* + * Register our connection. This turns encryption off because we do + * not have a key. + */ + packet_set_connection(sock_in, sock_out); + packet_set_server(); + + /* Set SO_KEEPALIVE if requested. */ + if (options.tcp_keep_alive && packet_connection_is_on_socket() && + setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) < 0) + error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); + + if ((remote_port = get_remote_port()) < 0) { + debug("get_remote_port failed"); + cleanup_exit(255); + } + + /* + * We use get_canonical_hostname with usedns = 0 instead of + * get_remote_ipaddr here so IP options will be checked. + */ + (void) get_canonical_hostname(0); + /* + * The rest of the code depends on the fact that + * get_remote_ipaddr() caches the remote ip, even if + * the socket goes away. + */ + remote_ip = get_remote_ipaddr(); + + #ifdef SSH_AUDIT_EVENTS + audit_connection_from(remote_ip, remote_port); + #endif + #ifdef LIBWRAP + /* Check whether logins are denied from this host. */ + if (packet_connection_is_on_socket()) { + struct request_info req; + + request_init(&req, RQ_DAEMON, __progname, RQ_FILE, sock_in, 0); + fromhost(&req); + + if (!hosts_access(&req)) { + debug("Connection refused by tcp wrapper"); + refuse(&req); + /* NOTREACHED */ + fatal("libwrap refuse returns"); + } + } + #endif /* LIBWRAP */ + + /* Log the connection. */ + verbose("Connection from %.500s port %d", remote_ip, remote_port); + + /* + * 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); + + sshd_exchange_identification(sock_in, sock_out); + + packet_set_nonblocking(); + + /* allocate authentication context */ + authctxt = xcalloc(1, sizeof(*authctxt)); + + authctxt->loginmsg = &loginmsg; + + /* XXX global for cleanup, access from other modules */ + the_authctxt = authctxt; + + /* prepare buffer to collect messages to display to user after login */ + buffer_init(&loginmsg); + + if (use_privsep) + if (privsep_preauth(authctxt) == 1) + goto authenticated; + + /* perform the key exchange */ + /* authenticate user and start session */ + if (compat20) { + do_ssh2_kex(); + do_authentication2(authctxt); + } else { + do_ssh1_kex(); + do_authentication(authctxt); + } + /* + * If we use privilege separation, the unprivileged child transfers + * the current keystate and exits + */ + if (use_privsep) { + mm_send_keystate(pmonitor); + exit(0); + } + + authenticated: + /* + * Cancel the alarm we set to limit the time taken for + * authentication. + */ + alarm(0); + signal(SIGALRM, SIG_DFL); + authctxt->authenticated = 1; + if (startup_pipe != -1) { + close(startup_pipe); + startup_pipe = -1; + } + + #ifdef SSH_AUDIT_EVENTS + audit_event(SSH_AUTH_SUCCESS); + #endif + + /* + * In privilege separation, we fork another child and prepare + * file descriptor passing. + */ + if (use_privsep) { + privsep_postauth(authctxt); + /* the monitor process [priv] will not return */ + if (!compat20) + destroy_sensitive_data(); + } + + /* Start session. */ + do_authenticated(authctxt); + + /* The connection has been terminated. */ + verbose("Closing connection to %.100s", remote_ip); + + #ifdef USE_PAM + if (options.use_pam) + finish_pam(); + #endif /* USE_PAM */ + + #ifdef SSH_AUDIT_EVENTS + PRIVSEP(audit_event(SSH_CONNECTION_CLOSE)); + #endif + + packet_close(); + + if (use_privsep) + mm_terminate(); + + exit(0); + } + + /* + * Decrypt session_key_int using our private server key and private host key + * (key with larger modulus first). + */ + int + ssh1_session_key(BIGNUM *session_key_int) + { + int rsafail = 0; + + if (BN_cmp(sensitive_data.server_key->rsa->n, + sensitive_data.ssh1_host_key->rsa->n) > 0) { + /* Server key has bigger modulus. */ + if (BN_num_bits(sensitive_data.server_key->rsa->n) < + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + + SSH_KEY_BITS_RESERVED) { + fatal("do_connection: %s: " + "server_key %d < host_key %d + SSH_KEY_BITS_RESERVED %d", + get_remote_ipaddr(), + BN_num_bits(sensitive_data.server_key->rsa->n), + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), + SSH_KEY_BITS_RESERVED); + } + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.server_key->rsa) <= 0) + rsafail++; + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.ssh1_host_key->rsa) <= 0) + rsafail++; + } else { + /* Host key has bigger modulus (or they are equal). */ + if (BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) < + BN_num_bits(sensitive_data.server_key->rsa->n) + + SSH_KEY_BITS_RESERVED) { + fatal("do_connection: %s: " + "host_key %d < server_key %d + SSH_KEY_BITS_RESERVED %d", + get_remote_ipaddr(), + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), + BN_num_bits(sensitive_data.server_key->rsa->n), + SSH_KEY_BITS_RESERVED); + } + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.ssh1_host_key->rsa) < 0) + rsafail++; + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.server_key->rsa) < 0) + rsafail++; + } + return (rsafail); + } + /* + * SSH1 key exchange + */ + static void + do_ssh1_kex(void) + { + int i, len; + int rsafail = 0; + BIGNUM *session_key_int; + u_char session_key[SSH_SESSION_KEY_LENGTH]; + u_char cookie[8]; + u_int cipher_type, auth_mask, protocol_flags; + u_int32_t rnd = 0; + + /* + * 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++) { + if (i % 4 == 0) + rnd = arc4random(); + cookie[i] = rnd & 0xff; + rnd >>= 8; + } + + /* + * 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(cookie[i]); + + /* Store our public server RSA key. */ + packet_put_int(BN_num_bits(sensitive_data.server_key->rsa->n)); + packet_put_bignum(sensitive_data.server_key->rsa->e); + packet_put_bignum(sensitive_data.server_key->rsa->n); + + /* Store our public host RSA key. */ + packet_put_int(BN_num_bits(sensitive_data.ssh1_host_key->rsa->n)); + packet_put_bignum(sensitive_data.ssh1_host_key->rsa->e); + packet_put_bignum(sensitive_data.ssh1_host_key->rsa->n); + + /* Put protocol flags. */ + packet_put_int(SSH_PROTOFLAG_HOST_IN_FWD_OPEN); + + /* Declare which ciphers we support. */ + packet_put_int(cipher_mask_ssh1(0)); + + /* Declare supported authentication types. */ + auth_mask = 0; + if (options.rhosts_rsa_authentication) + auth_mask |= 1 << SSH_AUTH_RHOSTS_RSA; + if (options.rsa_authentication) + auth_mask |= 1 << SSH_AUTH_RSA; + if (options.challenge_response_authentication == 1) + auth_mask |= 1 << SSH_AUTH_TIS; + 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(); + + debug("Sent %d bit server key and %d bit host key.", + BN_num_bits(sensitive_data.server_key->rsa->n), + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n)); + + /* Read clients reply (cipher type and session key). */ + packet_read_expect(SSH_CMSG_SESSION_KEY); + + /* Get cipher type and check whether we accept this. */ + cipher_type = packet_get_char(); + + if (!(cipher_mask_ssh1(0) & (1 << cipher_type))) + packet_disconnect("Warning: client selects unsupported cipher."); + + /* 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 (cookie[i] != packet_get_char()) + packet_disconnect("IP Spoofing check bytes do not match."); + + debug("Encryption type: %.200s", cipher_name(cipher_type)); + + /* Get the encrypted integer. */ + if ((session_key_int = BN_new()) == NULL) + fatal("do_ssh1_kex: BN_new failed"); + packet_get_bignum(session_key_int); + + protocol_flags = packet_get_int(); + packet_set_protocol_flags(protocol_flags); + packet_check_eom(); + + /* Decrypt session_key_int using host/server keys */ + rsafail = PRIVSEP(ssh1_session_key(session_key_int)); + + /* + * 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. + */ + if (!rsafail) { + (void) BN_mask_bits(session_key_int, sizeof(session_key) * 8); + len = BN_num_bytes(session_key_int); + if (len < 0 || (u_int)len > sizeof(session_key)) { + error("do_ssh1_kex: bad session key len from %s: " + "session_key_int %d > sizeof(session_key) %lu", + get_remote_ipaddr(), len, (u_long)sizeof(session_key)); + rsafail++; + } else { + memset(session_key, 0, sizeof(session_key)); + BN_bn2bin(session_key_int, + session_key + sizeof(session_key) - len); + + derive_ssh1_session_id( + sensitive_data.ssh1_host_key->rsa->n, + sensitive_data.server_key->rsa->n, + cookie, session_id); + /* + * 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]; + } + } + if (rsafail) { + int bytes = BN_num_bytes(session_key_int); + u_char *buf = xmalloc(bytes); + MD5_CTX md; + + logit("do_connection: generating a fake encryption key"); + BN_bn2bin(session_key_int, buf); + MD5_Init(&md); + MD5_Update(&md, buf, bytes); + MD5_Update(&md, sensitive_data.ssh1_cookie, SSH_SESSION_KEY_LENGTH); + MD5_Final(session_key, &md); + MD5_Init(&md); + MD5_Update(&md, session_key, 16); + MD5_Update(&md, buf, bytes); + MD5_Update(&md, sensitive_data.ssh1_cookie, SSH_SESSION_KEY_LENGTH); + MD5_Final(session_key + 16, &md); + memset(buf, 0, bytes); + xfree(buf); + for (i = 0; i < 16; i++) + session_id[i] = session_key[i] ^ session_key[i + 16]; + } + /* Destroy the private and public keys. No longer. */ + destroy_sensitive_data(); + + if (use_privsep) + mm_ssh1_session_id(session_id); + + /* Destroy the decrypted integer. It is no longer needed. */ + BN_clear_free(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); + + /* Destroy our copy of the session key. It is no longer needed. */ + memset(session_key, 0, sizeof(session_key)); + + debug("Received session key; encryption turned on."); + + /* Send an acknowledgment packet. Note that this packet is sent encrypted. */ + packet_start(SSH_SMSG_SUCCESS); + packet_send(); + packet_write_wait(); + } + + /* + * SSH2 key exchange: diffie-hellman-group1-sha1 + */ + static void + do_ssh2_kex(void) + { + Kex *kex; + + if (options.ciphers != NULL) { + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; + } + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_CTOS]); + myproposal[PROPOSAL_ENC_ALGS_STOC] = + compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_STOC]); + + if (options.macs != NULL) { + myproposal[PROPOSAL_MAC_ALGS_CTOS] = + myproposal[PROPOSAL_MAC_ALGS_STOC] = options.macs; + } + if (options.compression == COMP_NONE) { + myproposal[PROPOSAL_COMP_ALGS_CTOS] = + myproposal[PROPOSAL_COMP_ALGS_STOC] = "none"; + } else if (options.compression == COMP_DELAYED) { + myproposal[PROPOSAL_COMP_ALGS_CTOS] = + myproposal[PROPOSAL_COMP_ALGS_STOC] = "none,zlib@openssh.com"; + } + + myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = list_hostkey_types(); + + /* start key exchange */ + kex = kex_setup(myproposal); + kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; + kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; + kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; + kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; + kex->server = 1; + kex->client_version_string=client_version_string; + kex->server_version_string=server_version_string; + kex->load_host_key=&get_hostkey_by_type; + kex->host_key_index=&get_hostkey_index; + + xxx_kex = kex; + + dispatch_run(DISPATCH_BLOCK, &kex->done, kex); + + session_id2 = kex->session_id; + session_id2_len = kex->session_id_len; + + #ifdef DEBUG_KEXDH + /* send 1st encrypted/maced/compressed message */ + packet_start(SSH2_MSG_IGNORE); + packet_put_cstring("markus"); + packet_send(); + packet_write_wait(); + #endif + debug("KEX done"); + } + + /* server specific fatal cleanup */ + void + cleanup_exit(int i) + { + if (the_authctxt) + do_cleanup(the_authctxt); + #ifdef SSH_AUDIT_EVENTS + /* done after do_cleanup so it can cancel the PAM auth 'thread' */ + if (!use_privsep || mm_is_monitor()) + audit_event(SSH_CONNECTION_ABANDON); + #endif + _exit(i); + }