diff options
author | Damien Miller <djm@mindrot.org> | 1999-10-27 13:42:43 +1000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 1999-10-27 13:42:43 +1000 |
commit | d4a8b7e34dd619a4debf9a206c81db26d1402ea6 (patch) | |
tree | a47d770a2f790f40d18b0982d4e55fa7cfb1fa3b |
Initial revision
-rw-r--r-- | COPYING.Ylonen | 70 | ||||
-rw-r--r-- | ChangeLog | 578 | ||||
-rw-r--r-- | ChangeLog.linux | 20 | ||||
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | Makefile.GNU | 50 | ||||
-rw-r--r-- | Makefile.inc | 11 | ||||
-rw-r--r-- | OVERVIEW | 164 | ||||
-rw-r--r-- | README | 563 | ||||
-rw-r--r-- | README.openssh | 44 | ||||
-rw-r--r-- | RFC.nroff | 1780 | ||||
-rw-r--r-- | auth-krb4.c | 209 | ||||
-rw-r--r-- | auth-passwd.c | 209 | ||||
-rw-r--r-- | auth-rh-rsa.c | 83 | ||||
-rw-r--r-- | auth-rhosts.c | 298 | ||||
-rw-r--r-- | auth-rsa.c | 478 | ||||
-rw-r--r-- | auth-skey.c | 149 | ||||
-rw-r--r-- | authfd.c | 565 | ||||
-rw-r--r-- | authfd.h | 102 | ||||
-rw-r--r-- | authfile.c | 350 | ||||
-rw-r--r-- | bufaux.c | 141 | ||||
-rw-r--r-- | bufaux.h | 51 | ||||
-rw-r--r-- | buffer.c | 150 | ||||
-rw-r--r-- | buffer.h | 66 | ||||
-rw-r--r-- | canohost.c | 234 | ||||
-rw-r--r-- | channels.c | 1500 | ||||
-rw-r--r-- | channels.h | 41 | ||||
-rw-r--r-- | cipher.c | 304 | ||||
-rw-r--r-- | cipher.h | 84 | ||||
-rw-r--r-- | clientloop.c | 924 | ||||
-rw-r--r-- | compat.c | 10 | ||||
-rw-r--r-- | compat.h | 7 | ||||
-rw-r--r-- | compress.c | 160 | ||||
-rw-r--r-- | compress.h | 46 | ||||
-rw-r--r-- | crc32.c | 120 | ||||
-rw-r--r-- | crc32.h | 25 | ||||
-rw-r--r-- | deattack.c | 180 | ||||
-rw-r--r-- | deattack.h | 27 | ||||
-rw-r--r-- | getput.h | 64 | ||||
-rw-r--r-- | helper.c | 108 | ||||
-rw-r--r-- | helper.h | 43 | ||||
-rw-r--r-- | hostfile.c | 279 | ||||
-rw-r--r-- | includes.h | 78 | ||||
-rw-r--r-- | log-client.c | 138 | ||||
-rw-r--r-- | log-server.c | 233 | ||||
-rw-r--r-- | login.c | 118 | ||||
-rw-r--r-- | match.c | 78 | ||||
-rw-r--r-- | mktemp.c | 181 | ||||
-rw-r--r-- | mktemp.h | 7 | ||||
-rw-r--r-- | mpaux.c | 46 | ||||
-rw-r--r-- | mpaux.h | 32 | ||||
-rw-r--r-- | nchan.c | 187 | ||||
-rw-r--r-- | nchan.h | 57 | ||||
-rw-r--r-- | nchan.ms | 71 | ||||
-rw-r--r-- | openssh.spec | 105 | ||||
-rw-r--r-- | packet.c | 762 | ||||
-rw-r--r-- | packet.h | 166 | ||||
-rw-r--r-- | pty.c | 264 | ||||
-rw-r--r-- | pty.h | 40 | ||||
-rw-r--r-- | radix.c | 258 | ||||
-rw-r--r-- | rc4.c | 105 | ||||
-rw-r--r-- | rc4.h | 110 | ||||
-rw-r--r-- | readconf.c | 684 | ||||
-rw-r--r-- | readconf.h | 116 | ||||
-rw-r--r-- | readpass.c | 114 | ||||
-rw-r--r-- | rsa.c | 164 | ||||
-rw-r--r-- | rsa.h | 36 | ||||
-rw-r--r-- | scp.1 | 110 | ||||
-rw-r--r-- | scp.c | 1220 | ||||
-rw-r--r-- | servconf.c | 567 | ||||
-rw-r--r-- | servconf.h | 86 | ||||
-rw-r--r-- | serverloop.c | 644 | ||||
-rw-r--r-- | ssh-add.1 | 116 | ||||
-rw-r--r-- | ssh-add.c | 254 | ||||
-rw-r--r-- | ssh-agent.1 | 124 | ||||
-rw-r--r-- | ssh-agent.c | 572 | ||||
-rw-r--r-- | ssh-keygen.1 | 155 | ||||
-rw-r--r-- | ssh-keygen.c | 552 | ||||
-rw-r--r-- | ssh.1 | 966 | ||||
-rw-r--r-- | ssh.c | 809 | ||||
-rw-r--r-- | ssh.h | 589 | ||||
-rw-r--r-- | ssh.pam | 7 | ||||
-rw-r--r-- | ssh_config | 30 | ||||
-rw-r--r-- | sshconnect.c | 1495 | ||||
-rw-r--r-- | sshd.8 | 781 | ||||
-rw-r--r-- | sshd.c | 2445 | ||||
-rwxr-xr-x | sshd.init | 49 | ||||
-rw-r--r-- | sshd_config | 44 | ||||
-rw-r--r-- | strlcpy.c | 68 | ||||
-rw-r--r-- | strlcpy.h | 4 | ||||
-rw-r--r-- | tildexpand.c | 70 | ||||
-rw-r--r-- | ttymodes.c | 359 | ||||
-rw-r--r-- | ttymodes.h | 138 | ||||
-rw-r--r-- | uidswap.c | 95 | ||||
-rw-r--r-- | uidswap.h | 30 | ||||
-rw-r--r-- | version.h | 1 | ||||
-rw-r--r-- | xmalloc.c | 56 | ||||
-rw-r--r-- | xmalloc.h | 34 |
97 files changed, 26920 insertions, 0 deletions
diff --git a/COPYING.Ylonen b/COPYING.Ylonen new file mode 100644 index 00000000..5e681edd --- /dev/null +++ b/COPYING.Ylonen @@ -0,0 +1,70 @@ +This file is part of the ssh software, Copyright (c) 1995 Tatu Ylonen, Finland + + +COPYING POLICY AND OTHER LEGAL ISSUES + +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". + +However, I am not implying to give any licenses to any patents or +copyrights held by third parties, and the software includes parts that +are not under my direct control. As far as I know, all included +source code is used in accordance with the relevant license agreements +and can be used freely for any purpose (the GNU license being the most +restrictive); see below for details. + +[ RSA is no longer included. ] +[ IDEA is no longer included. ] +[ DES is now external. ] +[ GMP is now external. No more GNU licence. ] +[ Zlib is now external. ] +[ The make-ssh-known-hosts script is no longer included. ] +[ TSS has been removed. ] +[ MD5 is now external. ] +[ RC4 support has been removed. ] +[ Blowfish is now external. ] + +The 32-bit CRC implementation in crc32.c is due to Gary S. Brown. +Comments in the file indicate it may be used for any purpose without +restrictions. + +The 32-bit CRC compensation attack detector in deattack.c was +contributed by CORE SDI S.A. under a BSD-style license. See +http://www.core-sdi.com/english/ssh/ for details. + +Note that any information and cryptographic algorithms used in this +software are publicly available on the Internet and at any major +bookstore, scientific library, and patent office worldwide. More +information can be found e.g. at "http://www.cs.hut.fi/crypto". + +The legal status of this program is some combination of all these +permissions and restrictions. Use only at your own responsibility. +You will be responsible for any legal consequences yourself; I am not +making any claims whether possessing or using this is legal or not in +your country, and I am not taking any responsibility on your behalf. + + + NO WARRANTY + +BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..08d90f78 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,578 @@ +Fri Nov 17 16:19:20 1995 Tatu Ylonen <ylo@trance.olari.clinet.fi> + + * Released 1.2.12. + + * channels.c: Commented out debugging messages about output draining. + + * Added file OVERVIEW to give some idea about the structure of the + ssh software. + +Thu Nov 16 16:40:17 1995 Tatu Ylonen <ylo@trance.olari.clinet.fi> + + * canohost.c (get_remote_hostname): Don't ever return NULL (causes + segmentation violation). + + * sshconnect.c: Host ip address printed incorrectly with -v. + + * Implemented SSH_TTY environment variable. + +Wed Nov 15 01:47:40 1995 Tatu Ylonen <ylo@trance.olari.clinet.fi> + + * Implemented server and client option KeepAlive to specify + whether to set SO_KEEPALIVE. Both default to "yes"; to disable + keepalives, set the value to "no" in both the server and the + client configuration files. Updated manual pages. + + * sshd.c: Fixed Solaris utmp problem: wrong pid stored in utmp + (patch from Petri Virkkula <argon@bat.cs.hut.fi>). + + * login.c (record_logout): Fixed removing user from utmp on BSD + (with HAVE_LIBUTIL_LOGIN). + + * Added cleanup functions to be called from fatal(). Arranged for + utmp to be cleaned if sshd terminates by calling fatal (e.g., + after dropping connection). Eliminated separate client-side + fatal() functions and moved fatal() to log-client.c. Made all + cleanups, including channel_stop_listening() and packet_close() + be called using this mechanism. + +Thu Nov 9 09:58:05 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * sshd.c: Permit immediate login with empty password only if + password authentication is allowed. + +Wed Nov 8 00:43:55 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * Eliminated unix-domain X11 forwarding. Inet-domain forwarding is + now the only supported form. Renamed server option + X11InetForwarding to X11Forwarding, and eliminated + X11UnixForwarding. Updated documentation. Updated RFC (marked + the SSH_CMSG_X11_REQUEST_FORWARDING message (code 26) as + obsolete, and removed all references to it). Increased protocol + version number to 1.3. + + * scp.c (main): Added -B (BatchMode). Updated manual page. + + * Cleaned up and updated all manual pages. + + * clientloop.c: Added new escape sequences ~# (lists forwarded + connections), ~& (background ssh when waiting for forwarded + connections to terminate), ~? (list available escapes). + Polished the output of the connection listing. Updated + documentation. + + * uidswap.c: If _POSIX_SAVED_IDS is defined, don't change the real + uid. Assume that _POSIX_SAVED_IDS also applies to seteuid. + This may solve problems with tcp_wrappers (libwrap) showing + connections as coming from root. + +Tue Nov 7 20:28:57 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * Added RandomSeed server configuration option. The argument + specifies the location of the random seed file. Updated + documentation. + + * Locate perl5 in configure. Generate make-ssh-known-hosts (with + the correct path for perl5) in Makefile.in, and install it with + the other programs. Updated manual page. + + * sshd.c (main): Added a call to umask to set the umask to a + reasonable value. + + * compress.c (buffer_compress): Fixed to follow the zlib + documentation (which is slightly confusing). + + * INSTALL: Added information about Linux libc.so.4 problem. + +Mon Nov 6 15:42:36 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * (Actually autoconf fix) Installed patch to AC_ARG_PROGRAM. + + * sshd.c, sshd.8.in: Renamed $HOME/.environment -> + $HOME/.ssh/environment. + + * configure.in: Disable shadow password checking on convex. + Convex has /etc/shadow, but sets pw_passwd automatically if + running as root. + + * Eliminated HAVE_ETC_MASTER_PASSWD (NetBSD, FreeBSD); the + pw_passwd field is automatically filled if running as root. + Put explicit code in configure.in to prevent shadow password + checking on FreeBSD and NetBSD. + + * serverloop.c (signchld_handler): Don't print error if wait + returns -1. + + * Makefile.in (install): Fixed modes of data files. + + * Makefile.in (install): Make links for slogin.1. + + * make-ssh-known-hosts: Merged a patch from melo@ci.uminho.pt to + fix the ping command. + +Fri Nov 3 16:25:28 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * ssh.1.in: Added more information about X11 forwarding. + +Thu Nov 2 18:42:13 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * Changes to use O_NONBLOCK_BROKEN consistently. + + * pty.c (pty_make_controlling_tty): Use setpgid instead of + setsid() on Ultrix. + + * includes.h: Removed redundant #undefs for Ultrix and Sony News; + these are already handled in configure.in. + +Tue Oct 31 13:31:28 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * configure.in: Define SSH_WTMP to /var/adm/wtmp is wtmp not found. + + * configure.in: Disable vhangup on Ultrix. I am told this fixes + the server problems. + +Sat Oct 28 14:22:05 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * sshconnect.c: Fixed a bug in connecting to a multi-homed host. + Restructured the connecting code to never try to use the same + socket a second time after a failed connection. + + * Makefile.in: Added explicit -m option to install, and umask 022 + when creating directories and the host key. + +Fri Oct 27 01:05:10 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * Makefile.in: Added cleaning of $(ZLIBDIR) to clean and distclean. + + * login.c (get_last_login_time): Fixed a typo (define -> defined). + +Thu Oct 26 01:28:07 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * configure.in: Moved testing for ANSI C compiler after the host + specific code (problems on HPUX). + + * Minor fixes to /etc/default/login stuff from Bryan O'Sullivan. + + * Fixed .SH NAME sections in manual pages. + + * compress.c: Trying to fix a mysterious bug in the compression + glue. + + * ssh-1.2.11. + + * scp.c: disable agent forwarding when running ssh from scp. + + * Added compression of plaintext packets using the gzip library + (zlib). Client configuration options Compression and + CompressionLevel (1-9 as in gzip). New ssh and scp option -C + (to enable compression). Updated RFC. + +Wed Oct 25 05:11:55 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * Implemented ProxyCommand stuff based on patches from Bryan + O'Sullivan <bos@serpentine.com>. + + * Merged BSD login/logout/lastlog patches from Mark Treacy + <mark@labtam.oz.au>. + + * sshd.c: Added chdir("/"). + +Tue Oct 24 00:29:01 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * Merged RSA environment= patches from Felix Leitner + <leitner@prz.tu-berlin.de> with some changes. + + * sshd.c: Made the packet code use two separate descriptors for + the connection (one for input, the other for output). This will + make future extensions easier (e.g., non-socket transports, etc.). + sshd -i now uses both stdin and stdout separately. + +Mon Oct 23 21:29:28 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * sshd.c: Merged execle -> execve patches from Mark Martinec + <Mark.Martinec@nsc.ijs.si>. This may help with execle bugs on + Convex (environment not getting passed properly). This might + also solve similar problems on Sonys; please test! + + * Removed all compatibility code for protocol version 1.0. + THIS MEANS THAT WE ARE NO LONGER COMPATIBLE WITH SSH VERSIONS + PRIOR TO 1.1.0. + + * randoms.c (random_acquire_light_environmental_noise): If + /dev/random is available, read up to 32 bytes (256 bits) from + there in non-blocking mode, and mix the new random bytes into + the pool. + + * Added client configuration option StrictHostKeyChecking + (disabled by default). If this is enabled, the client will not + automatically add new host keys to $HOME/.ssh/known_hosts; + instead the connection will be refused if the host key is not + known. Similarly, if the host key has changed, the connection + will be refused instead if just issuing a warning. This + provides additional security against man-in-the-middle/trojan + horse attacks (especially in scripts where there is no-one to + see the warnings), but may be quite inconvenient in everyday + interactive use unless /etc/ssh_known_hosts is very complete, + because new host keys must now be added manually. + + * sshconnect.c (ssh_connect): Use the user's uid when creating the + socket and connecting it. I am hoping that this might help with + tcp_wrappers showing the remote user as root. + + * ssh.c: Try inet-domain X11 forwarding regardless of whether we + can get local authorization information. If we don't, we just + come up with fake information; the forwarding code will anyway + generate its own fake information and validate that the client + knows that information. It will then substitute our fake + information for that, but that info should get ignored by the + server if it doesn't support it. + + * Added option BatchMode to disable password/passphrase querying + in scripts. + + * auth-rh-rsa.c: Changed to use uid-swapping when reading + .ssh/known_hosts. + + * sshd.8.in (command): Improved documentation of file permissions + on the manual pages. + +Thu Oct 19 21:05:51 1995 Tatu Ylonen <ylo@soikko.cs.hut.fi> + + * ssh-add.c (add_file): Fixed a bug causing ssh to sometimes refer + to freed memory (comment -> saved_comment). + + * log-server.c: Added a prefix to debug/warning/error/fatal + messages describing message types. Syslog does not include that + information automatically. + +Sun Oct 8 01:56:01 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * Merged /etc/default/login and MAIL environment variable changes + from Bryan O'Sullivan <bos@serpentine.com>. + - mail spool file location + - process /etc/default/login + - add HAVE_ETC_DEFAULT_LOGIN + - new function child_get_env and read_etc_default_login (sshd.c) + + * ssh-add.c (add_file): Fixed asking for passphrase. + + * Makefile.in: Fixed installing configure-generated man pages when + compiling in a separate object directory. + + * sshd.c (main): Moved RSA key generation until after allocating + the port number. (Actually, the code got duplicated because we + never listen when run from inetd.) + + * ssh.c: Fixed a problem that caused scp to hang when called with + stdin closed. + +Sat Oct 7 03:08:06 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * Added server config option StrictModes. It specifies whether to + check ownership and modes of home directory and .rhosts files. + + * ssh.c: If ssh is renamed/linked to a host name, connect to that + host. + + * serverloop.c, clientloop.c: Ignore EAGAIN reported on read from + connection. Solaris has a kernel bug which causes select() to + sometimes wake up even though there is no data available. + + * Display all open connections when printing the "Waiting for + forwarded connections to terminate" message. + + * sshd.c, readconf.c: Added X11InetForwarding and + X11UnixForwarding server config options. + +Thu Oct 5 17:41:16 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * Some more SCO fixes. + +Tue Oct 3 01:04:34 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * Fixes and cleanups in README, INSTALL, COPYING. + +Mon Oct 2 03:36:08 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * ssh-add.c (add_file): Fixed a bug in ssh-add (xfree: NULL ...). + + * Removed .BR from ".SH NAME" in man pages. + +Sun Oct 1 04:16:07 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * ssh-1.2.10. + + * configure.in: When checking that the compiler works, check that + it understands ANSI C prototypes. + + * Made uidswap error message a debug() to avoid confusing errors + on AIX (AIX geteuid is brain-damaged and fails even for root). + + * Fixed an error in sshd.8 (FacistLogging -> FascistLogging). + + * Fixed distribution in Makefile.in (missing manual page .in files). + +Sat Sep 30 17:38:46 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * auth-rhosts.c: Fixed serious security problem in + /etc/hosts.equiv authentication. + +Fri Sep 29 00:41:02 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * Include machine/endian.h on Paragon. + + * ssh-add.c (add_file): Made ssh-add keep asking for the + passphrase until the user just types return or cancels. + Make the dialog display the comment of the key. + + * Read use shosts.equiv in addition to /etc/hosts.equiv. + + * sshd.8 is now sshd.8.in and is processed by configure to + substitute the proper paths for various files. Ditto for ssh.1. + Ditto for make-ssh-known-hosts.1. + + * configure.in: Moved /etc/sshd_pid to PIDDIR/sshd.pid. PIDDIR + will be /var/run if it exists, and ETCDIR otherwise. + +Thu Sep 28 21:52:42 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * On Ultrix, check if sys/syslog.h needs to be included in + addition to syslog.h. + + * make-ssh-known-hosts.pl: Merged Kivinen's fixes for HPUX. + + * configure.in: Put -lwrap, -lsocks, etc. at the head of LIBS. + + * Fixed case-insensitivity in auth-rhosts.c. + + * Added missing socketpair.c to EXTRA_SRCS (needed on SCO), plus + other SCO fixes. + + * Makefile.in: Fixed missing install_prefixes. + +Wed Sep 27 03:57:00 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * ssh-1.2.9. + + * Added SOCKS support. + + * Fixed default setting of IgnoreRhosts option. + + * Pass the magic cookie to xauth in stdin instead of command line; + the command line is visible in ps. + + * Added processing $HOME/.ssh/rc and /etc/sshrc. + + * Added a section to sshd.8 on what happens at login time. + +Tue Sep 26 01:27:40 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * Don't define speed_t on SunOS 4.1.1; it conflicts with system + headers. + + * Added support for .hushlogin. + + * Added --with-etcdir. + + * Read $HOME/.environment after /etc/environment. + +Mon Sep 25 03:26:06 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * Merged patches for SCO Unix (from Michael Henits). + +Sun Sep 24 22:28:02 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * Added ssh option ConnectionAttempts. + +Sat Sep 23 12:30:15 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * sshd.c: Don't print last login time and /etc/motd if a command + has been specified (with ssh -t host command). + + * Added support for passing the screen number in X11 forwarding. + It is implemented as a compatible protocol extension, signalled + by SSH_PROTOFLAG_SCREEN_NUMBER by the child. + + * clientloop.c: Fixed bugs in the order in which things were + processed. This may solve problems with some data not getting + sent to the server as soon as possible (probably solves the TCP + forwarding delayed close problem). Also, it looked like window + changes might not get transmitted as early as possible in some + cases. + + * clientloop.c: Changed to detect window size change that + happened while ssh was suspended. + + * ssh.c: Moved the do_session function (client main loop) to + clientloop.c. Divided it into smaller functions. General cleanup. + + * ssh-1.2.8 + +Fri Sep 22 22:07:46 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * sshconnect.c (ssh_login): Made ssh_login take the options + structure as argument, instead of the individual arguments. + + * auth-rhosts.c (check_rhosts_file): Added support for netgroups. + + * auth-rhosts.c (check_rhosts_file): Added support for negated + entries. + +Thu Sep 21 00:07:56 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * auth-rhosts.c: Restructured rhosts authentication code. + Hosts.equiv now has same format as .rhosts: user names are allowed. + + * Added support for the Intel Paragon. + + * sshd.c: Don't use X11 forwarding with spoofing if no xauth + program. Changed configure.in to not define XAUTH_PATH if + there is no xauth program. + + * ssh-1.2.7 + + * sshd.c: Rewrote the code to build the environment. Now also reads + /etc/environment. + + * sshd.c: Fixed problems in libwrap code. --with-libwrap now + takes optional library name/path. + + * ssh-1.2.6 + + * Define USE_PIPES by default. + + * Added support for Univel Unixware and MachTen. + + * Added IgnoreRhosts server option. + + * Added USE_STRLEN_FOR_AF_UNIX; it is needed at least on MachTen. + +Wed Sep 20 02:41:02 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * sshd.c (do_child): don't call packet_close when /etc/nologin, + because packet_close does shutdown, and the message does not get + sent. + + * pty.c (pty_allocate): Push ttcompat streams module. + + * randoms.c (random_acquire_light_environmental_noise): Don't use + the second argument to gettimeofday as it is not supported on + all systems. + + * login.c (record_login): Added NULL second argument to gettimeofday. + +Tue Sep 19 13:25:48 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * fixed pclose wait() in sshd key regeneration (now only collects + easily available noise). + + * configure.in: test for bsdi before bsd*. + + * ssh.c: Don't print "Connection closed" if -q. + +Wed Sep 13 04:19:52 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * Released ssh-1.2.5. + + * Hopefully fixed "Waiting for forwarded connections to terminate" + message. + + * randoms.c, md5.c: Large modifications to make these work on Cray + (which has no 32 bit integer type). + + * Fixed a problem with forwarded connection closes not being + reported immediately. + + * ssh.c: fixed rhosts authentication (broken by uid-swapping). + + * scp.c: Don't use -l if server user not specified (it made + setting User in the configuration file not work). + + * configure.in: don't use -pipe on BSDI. + + * randoms.c: Major modifications to make it work without 32 bit + integers (e.g. Cray). + + * md5.c: Major modifications to make it work without 32 bit + integers (e.g. Cray). + + * Eliminated HPSUX_BROKEN_PTYS. The code is now enabled by + default on all systems. + +Mon Sep 11 00:53:12 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * sshd.c: don't include sshd pathname in log messages. + + * Added libwrap stuff (includes support for identd). + + * Added OSF/1 C2 extended security stuff. + + * Fixed interactions between getuid() and uid-swap stuff. + +Sun Sep 10 00:29:27 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * serverloop.c: Don't send stdout data to client until after a few + milliseconds if there is very little data. This is because some + systems give data from pty one character at a time, which would + multiply data size by about 16. + + * serverloop.c: Moved server do_session to a separate file and + renamed it server_loop. Split it into several functions and + partially rewrote it. Fixed "cat /etc/termcap | ssh foo cat" hangup. + + * Screwed up something while checking stuff in under cvs. No harm, + but bogus log entries... + +Sat Sep 9 02:24:51 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * minfd.c (_get_permanent_fd): Use SHELL environment variable. + + * channels.c (x11_create_display_inet): Created + HPSUX_NONSTANDARD_X11_KLUDGE; it causes DISPLAY to contain the + IP address of the host instead of the name, because HPSUX uses + some magic shared memory communication for local connections. + + * Changed SIGHUP processing in server; it should now work multiple + times. + + * Added length limits in many debug/log/error/fatal calls just in + case. + + * login.c (get_last_login_time): Fixed location of lastlog. + + * Rewrote all uid-swapping code. New files uidswap.h, uidswap.c. + + * Fixed several security problems involving chmod and chgrp (race + conditions). Added warnings about dubious modes for /tmp/.X11-unix. + +Fri Sep 8 20:03:36 1995 Tatu Ylonen <ylo@shadows.cs.hut.fi> + + * Changed readconf.c to never display anything from the config + file. This should now be prevented otherwise, but let's play safe. + + * log-server.c: Use %.500s in syslog() just to be sure (they + should already be shorter than 1024 though). + + * sshd.c: Moved setuid in child a little earlier (just to be + conservative, there was no security problem that I could detect). + + * README, INSTALL: Added info about mailing list and WWW page. + + * sshd.c: Added code to use SIGCHLD and wait zombies immediately. + + * Merged patch to set ut_addr in utmp. + + * Created ChangeLog and added it to Makefile.in. + + * Use read_passphrase instead of getpass(). + + * Added SSH_FALLBACK_CIPHER. Fixed a bug in default cipher + selection (IDEA used to be selected even if not supported by the + server). + + * Use no encryption for key files if empty passphrase. + + * Added section about --without-idea in INSTALL. + + * Version 1.2.0 was released a couple of days ago. + diff --git a/ChangeLog.linux b/ChangeLog.linux new file mode 100644 index 00000000..a28e577a --- /dev/null +++ b/ChangeLog.linux @@ -0,0 +1,20 @@ +19991027 + - Adapted PAM patch. + - Released 1.0pre2 + + - Excised my buggy replacements for strlcpy and mkdtemp + - Imported correct OpenBSD strlcpy and mkdtemp routines. + - Reduced arc4random_stir entropy read to 32 bytes (256 bits) + - Picked up correct version number from OpenBSD + - Added sshd.pam PAM configuration file + - Added sshd.init Redhat init script + - Added openssh.spec RPM spec file + - Released 1.2pre3 + +19991026 + - Fixed include paths of OpenSSL functions + - Use OpenSSL MD5 routines + - Imported RC4 code from nanocrypt + - Wrote replacements for OpenBSD arc4random* functions + - Wrote replacements for strlcpy and mkdtemp + - Released 1.0pre1 diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..668900c3 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +# $OpenBSD: Makefile,v 1.5 1999/10/25 20:27:26 markus Exp $ + +.include <bsd.own.mk> + +SUBDIR= lib ssh sshd ssh-add ssh-keygen ssh-agent scp + +distribution: + install -C -o root -g wheel -m 0644 ${.CURDIR}/ssh_config \ + ${DESTDIR}/etc/ssh_config + install -C -o root -g wheel -m 0644 ${.CURDIR}/sshd_config \ + ${DESTDIR}/etc/sshd_config + +.include <bsd.subdir.mk> diff --git a/Makefile.GNU b/Makefile.GNU new file mode 100644 index 00000000..f36bdb3d --- /dev/null +++ b/Makefile.GNU @@ -0,0 +1,50 @@ +OPT_FLAGS=-g +CFLAGS=$(OPT_FLAGS) -Wall -DETCDIR=\"/etc/ssh\" -DHAVE_PAM +TARGETS=bin/libssh.a bin/ssh bin/sshd bin/ssh-add bin/ssh-keygen bin/ssh-agent bin/scp +LFLAGS=-L./bin +LIBS=-lssh -lcrypto -lz -lutil -lpam -ldl +AR=ar +RANLIB=ranlib + +OBJS= authfd.o authfile.o auth-passwd.o auth-rhosts.o auth-rh-rsa.o \ + auth-rsa.o bufaux.o buffer.o canohost.o channels.o cipher.o \ + clientloop.o compress.o crc32.o deattack.o hostfile.o \ + log-client.o login.o log-server.o match.o mpaux.o packet.o pty.o \ + readconf.o readpass.o rsa.o servconf.o serverloop.o \ + sshconnect.o tildexpand.o ttymodes.o uidswap.o xmalloc.o \ + helper.o mktemp.o strlcpy.o rc4.o + +all: $(OBJS) $(TARGETS) + +bin/libssh.a: authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o cipher.o compat.o compress.o crc32.o deattack.o hostfile.o match.o mpaux.o nchan.o packet.o readpass.o rsa.o tildexpand.o ttymodes.o uidswap.o xmalloc.o helper.o rc4.o mktemp.o strlcpy.o + [ -d bin ] || mkdir bin + $(AR) rv $@ $^ + $(RANLIB) $@ + +bin/ssh: ssh.o sshconnect.o log-client.o readconf.o clientloop.o + [ -d bin ] || mkdir bin + $(CC) -o $@ $^ $(LFLAGS) $(LIBS) + +bin/sshd: sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o pty.o log-server.o login.o servconf.o serverloop.o + [ -d bin ] || mkdir bin + $(CC) -o $@ $^ $(LFLAGS) $(LIBS) + +bin/scp: scp.o + [ -d bin ] || mkdir bin + $(CC) -o $@ $^ $(LFLAGS) $(LIBS) + +bin/ssh-add: ssh-add.o log-client.o + [ -d bin ] || mkdir bin + $(CC) -o $@ $^ $(LFLAGS) $(LIBS) + +bin/ssh-agent: ssh-agent.o log-client.o + [ -d bin ] || mkdir bin + $(CC) -o $@ $^ $(LFLAGS) $(LIBS) + +bin/ssh-keygen: ssh-keygen.o log-client.o + [ -d bin ] || mkdir bin + $(CC) -o $@ $^ $(LFLAGS) $(LIBS) + +clean: + rm -f *.o core bin/* + diff --git a/Makefile.inc b/Makefile.inc new file mode 100644 index 00000000..fddf3da2 --- /dev/null +++ b/Makefile.inc @@ -0,0 +1,11 @@ +CFLAGS+= -I${.CURDIR}/.. + +.include <bsd.obj.mk> + +.if exists(${.CURDIR}/../lib/${__objdir}) +LDADD+= -L${.CURDIR}/../lib/${__objdir} -lssh +DPADD+= ${.CURDIR}/../lib/${__objdir}/libssh.a +.else +LDADD+= -L${.CURDIR}/../lib -lssh +DPADD+= ${.CURDIR}/../lib/libssh.a +.endif diff --git a/OVERVIEW b/OVERVIEW new file mode 100644 index 00000000..a8b67e4e --- /dev/null +++ b/OVERVIEW @@ -0,0 +1,164 @@ +This document is inteded for those who wish to read the ssh source +code. This tries to give an overview of the structure of the code. + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi> +Updated 17 Nov 1995. +Updated 19 Oct 1999 for OpenSSH-1.2 + +The software consists of ssh (client), sshd (server), scp, sdist, and +the auxiliary programs ssh-keygen, ssh-agent, ssh-add, and +make-ssh-known-hosts. The main program for each of these is in a .c +file with the same name. + +There are some subsystems/abstractions that are used by a number of +these programs. + + Buffer manipulation routines + + - These provide an arbitrary size buffer, where data can be appended. + Data can be consumed from either end. The code is used heavily + throughout ssh. The basic buffer manipulation functions are in + buffer.c (header buffer.h), and additional code to manipulate specific + data types is in bufaux.c. + + Compression Library + + - Ssh uses the GNU GZIP compression library (ZLIB). + + Encryption/Decryption + + - Ssh contains several encryption algorithms. These are all + accessed through the cipher.h interface. The interface code is + in cipher.c, and the implementations are in libc. + + Multiple Precision Integer Library + + - Uses the SSLeay BIGNUM sublibrary. + - Some auxiliary functions for mp-int manipulation are in mpaux.c. + + Random Numbers + + - Uses arc4random() and such. + + RSA key generation, encryption, decryption + + - Ssh uses the RSA routines in libssl. + + RSA key files + + - RSA keys are stored in files with a special format. The code to + read/write these files is in authfile.c. The files are normally + encrypted with a passphrase. The functions to read passphrases + are in readpass.c (the same code is used to read passwords). + + Binary packet protocol + + - The ssh binary packet protocol is implemented in packet.c. The + code in packet.c does not concern itself with packet types or their + execution; it contains code to build packets, to receive them and + extract data from them, and the code to compress and/or encrypt + packets. CRC code comes from crc32.c. + + - The code in packet.c calls the buffer manipulation routines + (buffer.c, bufaux.c), compression routines (compress.c, zlib), + and the encryption routines. + + X11, TCP/IP, and Agent forwarding + + - Code for various types of channel forwarding is in channels.c. + The file defines a generic framework for arbitrary communication + channels inside the secure channel, and uses this framework to + implement X11 forwarding, TCP/IP forwarding, and authentication + agent forwarding. + The new, Protocol 1.5, channel close implementation is in nchan.c + + Authentication agent + + - Code to communicate with the authentication agent is in authfd.c. + + Authentication methods + + - Code for various authentication methods resides in auth-*.c + (auth-passwd.c, auth-rh-rsa.c, auth-rhosts.c, auth-rsa.c). This + code is linked into the server. The routines also manipulate + known hosts files using code in hostfile.c. Code in canohost.c + is used to retrieve the canonical host name of the remote host. + Code in match.c is used to match host names. + + - In the client end, authentication code is in sshconnect.c. It + reads Passwords/passphrases using code in readpass.c. It reads + RSA key files with authfile.c. It communicates the + authentication agent using authfd.c. + + The ssh client + + - The client main program is in ssh.c. It first parses arguments + and reads configuration (readconf.c), then calls ssh_connect (in + sshconnect.c) to open a connection to the server (possibly via a + proxy), and performs authentication (ssh_login in sshconnect.c). + It then makes any pty, forwarding, etc. requests. It may call + code in ttymodes.c to encode current tty modes. Finally it + calls client_loop in clientloop.c. This does the real work for + the session. + + - The client is suid root. It tries to temporarily give up this + rights while reading the configuration data. The root + privileges are only used to make the connection (from a + privileged socket). Any extra privileges are dropped before + calling ssh_login. + + Pseudo-tty manipulation and tty modes + + - Code to allocate and use a pseudo tty is in pty.c. Code to + encode and set terminal modes is in ttymodes.c. + + Logging in (updating utmp, lastlog, etc.) + + - The code to do things that are done when a user logs in are in + login.c. This includes things such as updating the utmp, wtmp, + and lastlog files. Some of the code is in sshd.c. + + Writing to the system log and terminal + + - The programs use the functions fatal(), log(), debug(), error() + in many places to write messages to system log or user's + terminal. The implementation that logs to system log is in + log-server.c; it is used in the server program. The other + programs use an implementation that sends output to stderr; it + is in log-client.c. The definitions are in ssh.h. + + The sshd server (daemon) + + - The sshd daemon starts by processing arguments and reading the + configuration file (servconf.c). It then reads the host key, + starts listening for connections, and generates the server key. + The server key will be regenerated every hour by an alarm. + + - When the server receives a connection, it forks, disables the + regeneration alarm, and starts communicating with the client. + They first perform identification string exchange, then + negotiate encryption, then perform authentication, preparatory + operations, and finally the server enters the normal session + mode by calling server_loop in serverloop.c. This does the real + work, calling functions in other modules. + + - The code for the server is in sshd.c. It contains a lot of + stuff, including: + - server main program + - waiting for connections + - processing new connection + - authentication + - preparatory operations + - building up the execution environment for the user program + - starting the user program. + + Auxiliary files + + - There are several other files in the distribution that contain + various auxiliary routines: + ssh.h the main header file for ssh (various definitions) + getput.h byte-order independent storage of integers + includes.h includes most system headers. Lots of #ifdefs. + tildexpand.c expand tilde in file names + uidswap.c uid-swapping + xmalloc.c "safe" malloc routines @@ -0,0 +1,563 @@ +Ssh (Secure Shell) is a program to log into another computer over a +network, to execute commands in a remote machine, and to move files +from one machine to another. It provides strong authentication and +secure communications over insecure channels. It is inteded as a +replacement for rlogin, rsh, rcp, and rdist. + +See the file INSTALL for installation instructions. See COPYING for +license terms and other legal issues. See RFC for a description of +the protocol. There is a WWW page for ssh; see http://www.cs.hut.fi/ssh. + +This file has been updated to match ssh-1.2.12. + + +FEATURES + + o Strong authentication. Closes several security holes (e.g., IP, + routing, and DNS spoofing). New authentication methods: .rhosts + together with RSA based host authentication, and pure RSA + authentication. + + o Improved privacy. All communications are automatically and + transparently encrypted. RSA is used for key exchange, and a + conventional cipher (normally IDEA, DES, or triple-DES) for + encrypting the session. Encryption is started before + authentication, and no passwords or other information is + transmitted in the clear. Encryption is also used to protect + against spoofed packets. + + o Secure X11 sessions. The program automatically sets DISPLAY on + the server machine, and forwards any X11 connections over the + secure channel. Fake Xauthority information is automatically + generated and forwarded to the remote machine; the local client + automatically examines incoming X11 connections and replaces the + fake authorization data with the real data (never telling the + remote machine the real information). + + o Arbitrary TCP/IP ports can be redirected through the encrypted channel + in both directions (e.g., for e-cash transactions). + + o No retraining needed for normal users; everything happens + automatically, and old .rhosts files will work with strong + authentication if administration installs host key files. + + o Never trusts the network. Minimal trust on the remote side of + the connection. Minimal trust on domain name servers. Pure RSA + authentication never trusts anything but the private key. + + o Client RSA-authenticates the server machine in the beginning of + every connection to prevent trojan horses (by routing or DNS + spoofing) and man-in-the-middle attacks, and the server + RSA-authenticates the client machine before accepting .rhosts or + /etc/hosts.equiv authentication (to prevent DNS, routing, or + IP-spoofing). + + o Host authentication key distribution can be centrally by the + administration, automatically when the first connection is made + to a machine (the key obtained on the first connection will be + recorded and used for authentication in the future), or manually + by each user for his/her own use. The central and per-user host + key repositories are both used and complement each other. Host + keys can be generated centrally or automatically when the software + is installed. Host authentication keys are typically 1024 bits. + + o Any user can create any number of user authentication RSA keys for + his/her own use. Each user has a file which lists the RSA public + keys for which proof of possession of the corresponding private + key is accepted as authentication. User authentication keys are + typically 1024 bits. + + o The server program has its own server RSA key which is + automatically regenerated every hour. This key is never saved in + any file. Exchanged session keys are encrypted using both the + server key and the server host key. The purpose of the separate + server key is to make it impossible to decipher a captured session by + breaking into the server machine at a later time; one hour from + the connection even the server machine cannot decipher the session + key. The key regeneration interval is configurable. The server + key is normally 768 bits. + + o An authentication agent, running in the user's laptop or local + workstation, can be used to hold the user's RSA authentication + keys. Ssh automatically forwards the connection to the + authentication agent over any connections, and there is no need to + store the RSA authentication keys on any machine in the network + (except the user's own local machine). The authentication + protocols never reveal the keys; they can only be used to verify + that the user's agent has a certain key. Eventually the agent + could rely on a smart card to perform all authentication + computations. + + o The software can be installed and used (with restricted + functionality) even without root privileges. + + o The client is customizable in system-wide and per-user + configuration files. Most aspects of the client's operation can + be configured. Different options can be specified on a per-host basis. + + o Automatically executes conventional rsh (after displaying a + warning) if the server machine is not running sshd. + + o Optional compression of all data with gzip (including forwarded X11 + and TCP/IP port data), which may result in significant speedups on + slow connections. + + o Complete replacement for rlogin, rsh, and rcp. + + +WHY TO USE SECURE SHELL + +Currently, almost all communications in computer networks are done +without encryption. As a consequence, anyone who has access to any +machine connected to the network can listen in on any communication. +This is being done by hackers, curious administrators, employers, +criminals, industrial spies, and governments. Some networks leak off +enough electromagnetic radiation that data may be captured even from a +distance. + +When you log in, your password goes in the network in plain +text. Thus, any listener can then use your account to do any evil he +likes. Many incidents have been encountered worldwide where crackers +have started programs on workstations without the owners knowledge +just to listen to the network and collect passwords. Programs for +doing this are available on the Internet, or can be built by a +competent programmer in a few hours. + +Any information that you type or is printed on your screen can be +monitored, recorded, and analyzed. For example, an intruder who has +penetrated a host connected to a major network can start a program +that listens to all data flowing in the network, and whenever it +encounters a 16-digit string, it checks if it is a valid credit card +number (using the check digit), and saves the number plus any +surrounding text (to catch expiration date and holder) in a file. +When the intruder has collected a few thousand credit card numbers, he +makes smallish mail-order purchases from a few thousand stores around +the world, and disappears when the goods arrive but before anyone +suspects anything. + +Businesses have trade secrets, patent applications in preparation, +pricing information, subcontractor information, client data, personnel +data, financial information, etc. Currently, anyone with access to +the network (any machine on the network) can listen to anything that +goes in the network, without any regard to normal access restrictions. + +Many companies are not aware that information can so easily be +recovered from the network. They trust that their data is safe +since nobody is supposed to know that there is sensitive information +in the network, or because so much other data is transferred in the +network. This is not a safe policy. + +Individual persons also have confidential information, such as +diaries, love letters, health care documents, information about their +personal interests and habits, professional data, job applications, +tax reports, political documents, unpublished manuscripts, etc. + +One should also be aware that economical intelligence and industrial +espionage has recently become a major priority of the intelligence +agencies of major governments. President Clinton recently assigned +economical espionage as the primary task of the CIA, and the French +have repeatedly been publicly boasting about their achievements on +this field. + + +There is also another frightening aspect about the poor security of +communications. Computer storage and analysis capability has +increased so much that it is feasible for governments, major +companies, and criminal organizations to automatically analyze, +identify, classify, and file information about millions of people over +the years. Because most of the work can be automated, the cost of +collecting this information is getting very low. + +Government agencies may be able to monitor major communication +systems, telephones, fax, computer networks, etc., and passively +collect huge amounts of information about all people with any +significant position in the society. Most of this information is not +sensitive, and many people would say there is no harm in someone +getting that information. However, the information starts to get +sensitive when someone has enough of it. You may not mind someone +knowing what you bought from the shop one random day, but you might +not like someone knowing every small thing you have bought in the last +ten years. + +If the government some day starts to move into a more totalitarian +direction (one should remember that Nazi Germany was created by +democratic elections), there is considerable danger of an ultimate +totalitarian state. With enough information (the automatically +collected records of an individual can be manually analyzed when the +person becomes interesting), one can form a very detailed picture of +the individual's interests, opinions, beliefs, habits, friends, +lovers, weaknesses, etc. This information can be used to 1) locate +any persons who might oppose the new system 2) use deception to +disturb any organizations which might rise against the government 3) +eliminate difficult individuals without anyone understanding what +happened. Additionally, if the government can monitor communications +too effectively, it becomes too easy to locate and eliminate any +persons distributing information contrary to the official truth. + +Fighting crime and terrorism are often used as grounds for domestic +surveillance and restricting encryption. These are good goals, but +there is considerable danger that the surveillance data starts to get +used for questionable purposes. I find that it is better to tolerate +a small amount of crime in the society than to let the society become +fully controlled. I am in favor of a fairly strong state, but the +state must never get so strong that people become unable to spread +contra-offical information and unable to overturn the government if it +is bad. The danger is that when you notice that the government is +too powerful, it is too late. Also, the real power may not be where +the official government is. + +For these reasons (privacy, protecting trade secrets, and making it +more difficult to create a totalitarian state), I think that strong +cryptography should be integrated to the tools we use every day. +Using it causes no harm (except for those who wish to monitor +everything), but not using it can cause huge problems. If the society +changes in undesirable ways, then it will be to late to start +encrypting. + +Encryption has had a "military" or "classified" flavor to it. There +are no longer any grounds for this. The military can and will use its +own encryption; that is no excuse to prevent the civilians from +protecting their privacy and secrets. Information on strong +encryption is available in every major bookstore, scientific library, +and patent office around the world, and strong encryption software is +available in every country on the Internet. + +Some people would like to make it illegal to use encryption, or to +force people to use encryption that governments can break. This +approach offers no protection if the government turns bad. Also, the +"bad guys" will be using true strong encryption anyway. Good +encryption techniques are too widely known to make them disappear. +Thus, any "key escrow encryption" or other restrictions will only help +monitor ordinary people and petty criminals. It does not help against +powerful criminals, terrorists, or espionage, because they will know +how to use strong encryption anyway. (One source for internationally +available encryption software is http://www.cs.hut.fi/crypto.) + + +OVERVIEW OF SECURE SHELL + +The software consists of a number of programs. + + sshd Server program run on the server machine. This + listens for connections from client machines, and + whenever it receives a connection, it performs + authentication and starts serving the client. + + ssh This is the client program used to log into another + machine or to execute commands on the other machine. + "slogin" is another name for this program. + + scp Securely copies files from one machine to another. + + ssh-keygen Used to create RSA keys (host keys and user + authentication keys). + + ssh-agent Authentication agent. This can be used to hold RSA + keys for authentication. + + ssh-add Used to register new keys with the agent. + + make-ssh-known-hosts + Used to create the /etc/ssh_known_hosts file. + + +Ssh is the program users normally use. It is started as + + ssh host + +or + + ssh host command + +The first form opens a new shell on the remote machine (after +authentication). The latter form executes the command on the remote +machine. + +When started, the ssh connects sshd on the server machine, verifies +that the server machine really is the machine it wanted to connect, +exchanges encryption keys (in a manner which prevents an outside +listener from getting the keys), performs authentication using .rhosts +and /etc/hosts.equiv, RSA authentication, or conventional password +based authentication. The server then (normally) allocates a +pseudo-terminal and starts an interactive shell or user program. + +The TERM environment variable (describing the type of the user's +terminal) is passed from the client side to the remote side. Also, +terminal modes will be copied from the client side to the remote side +to preserve user preferences (e.g., the erase character). + +If the DISPLAY variable is set on the client side, the server will +create a dummy X server and set DISPLAY accordingly. Any connections +to the dummy X server will be forwarded through the secure channel, +and will be made to the real X server from the client side. An +arbitrary number of X programs can be started during the session, and +starting them does not require anything special from the user. (Note +that the user must not manually set DISPLAY, because then it would +connect directly to the real display instead of going through the +encrypted channel). This behavior can be disabled in the +configuration file or by giving the -x option to the client. + +Arbitrary IP ports can be forwarded over the secure channel. The +program then creates a port on one side, and whenever a connection is +opened to this port, it will be passed over the secure channel, and a +connection will be made from the other side to a specified host:port +pair. Arbitrary IP forwarding must always be explicitly requested, +and cannot be used to forward privileged ports (unless the user is +root). It is possible to specify automatic forwards in a per-user +configuration file, for example to make electronic cash systems work +securely. + +If there is an authentication agent on the client side, connection to +it will be automatically forwarded to the server side. + +For more infomation, see the manual pages ssh(1), sshd(8), scp(1), +ssh-keygen(1), ssh-agent(1), ssh-add(1), and make-ssh-known-hosts(1) +included in this distribution. + + +X11 CONNECTION FORWARDING + +X11 forwarding serves two purposes: it is a convenience to the user +because there is no need to set the DISPLAY variable, and it provides +encrypted X11 connections. I cannot think of any other easy way to +make X11 connections encrypted; modifying the X server, clients or +libraries would require special work for each machine, vendor and +application. Widely used IP-level encryption does not seem likely for +several years. Thus what we have left is faking an X server on the +same machine where the clients are run, and forwarding the connections +to a real X server over the secure channel. + +X11 forwarding works as follows. The client extracts Xauthority +information for the server. It then creates random authorization +data, and sends the random data to the server. The server allocates +an X11 display number, and stores the (fake) Xauthority data for this +display. Whenever an X11 connection is opened, the server forwards +the connection over the secure channel to the client, and the client +parses the first packet of the X11 protocol, substitutes real +authentication data for the fake data (if the fake data matched), and +forwards the connection to the real X server. + +If the display does not have Xauthority data, the server will create a +unix domain socket in /tmp/.X11-unix, and use the unix domain socket +as the display. No authentication information is forwarded in this +case. X11 connections are again forwarded over the secure channel. +To the X server the connections appear to come from the client +machine, and the server must have connections allowed from the local +machine. Using authentication data is always recommended because not +using it makes the display insecure. If XDM is used, it automatically +generates the authentication data. + +One should be careful not to use "xin" or "xstart" or other similar +scripts that explicitly set DISPLAY to start X sessions in a remote +machine, because the connection will then not go over the secure +channel. The recommended way to start a shell in a remote machine is + + xterm -e ssh host & + +and the recommended way to execute an X11 application in a remote +machine is + + ssh -n host emacs & + +If you need to type a password/passphrase for the remote machine, + + ssh -f host emacs + +may be useful. + + + +RSA AUTHENTICATION + +RSA authentication is based on public key cryptograpy. The idea is +that there are two encryption keys, one for encryption and another for +decryption. It is not possible (on human timescale) to derive the +decryption key from the encryption key. The encryption key is called +the public key, because it can be given to anyone and it is not +secret. The decryption key, on the other hand, is secret, and is +called the private key. + +RSA authentication is based on the impossibility of deriving the +private key from the public key. The public key is stored on the +server machine in the user's $HOME/.ssh/authorized_keys file. The +private key is only kept on the user's local machine, laptop, or other +secure storage. Then the user tries to log in, the client tells the +server the public key that the user wishes to use for authentication. +The server then checks if this public key is admissible. If so, it +generates a 256 bit random number, encrypts it with the public key, +and sends the value to the client. The client then decrypts the +number with its private key, computes a 128 bit MD5 checksum from the +resulting data, and sends the checksum back to the server. (Only a +checksum is sent to prevent chosen-plaintext attacks against RSA.) +The server checks computes a checksum from the correct data, +and compares the checksums. Authentication is accepted if the +checksums match. (Theoretically this indicates that the client +only probably knows the correct key, but for all practical purposes +there is no doubt.) + +The RSA private key can be protected with a passphrase. The +passphrase can be any string; it is hashed with MD5 to produce an +encryption key for IDEA, which is used to encrypt the private part of +the key file. With passphrase, authorization requires access to the key +file and the passphrase. Without passphrase, authorization only +depends on possession of the key file. + +RSA authentication is the most secure form of authentication supported +by this software. It does not rely on the network, routers, domain +name servers, or the client machine. The only thing that matters is +access to the private key. + +All this, of course, depends on the security of the RSA algorithm +itself. RSA has been widely known since about 1978, and no effective +methods for breaking it are known if it is used properly. Care has +been taken to avoid the well-known pitfalls. Breaking RSA is widely +believed to be equivalent to factoring, which is a very hard +mathematical problem that has received considerable public research. +So far, no effective methods are known for numbers bigger than about +512 bits. However, as computer speeds and factoring methods are +increasing, 512 bits can no longer be considered secure. The +factoring work is exponential, and 768 or 1024 bits are widely +considered to be secure in the near future. + + +RHOSTS AUTHENTICATION + +Conventional .rhosts and hosts.equiv based authentication mechanisms +are fundamentally insecure due to IP, DNS (domain name server) and +routing spoofing attacks. Additionally this authentication method +relies on the integrity of the client machine. These weaknesses is +tolerable, and been known and exploited for a long time. + +Ssh provides an improved version of these types of authentication, +because they are very convenient for the user (and allow easy +transition from rsh and rlogin). It permits these types of +authentication, but additionally requires that the client host be +authenticated using RSA. + +The server has a list of host keys stored in /etc/ssh_known_host, and +additionally each user has host keys in $HOME/.ssh/known_hosts. Ssh +uses the name servers to obtain the canonical name of the client host, +looks for its public key in its known host files, and requires the +client to prove that it knows the private host key. This prevents IP +and routing spoofing attacks (as long as the client machine private +host key has not been compromized), but is still vulnerable to DNS +attacks (to a limited extent), and relies on the integrity of the +client machine as to who is requesting to log in. This prevents +outsiders from attacking, but does not protect against very powerful +attackers. If maximal security is desired, only RSA authentication +should be used. + +It is possible to enable conventional .rhosts and /etc/hosts.equiv +authentication (without host authentication) at compile time by giving +the option --with-rhosts to configure. However, this is not +recommended, and is not done by default. + +These weaknesses are present in rsh and rlogin. No improvement in +security will be obtained unless rlogin and rsh are completely +disabled (commented out in /etc/inetd.conf). This is highly +recommended. + + +WEAKEST LINKS IN SECURITY + +One should understand that while this software may provide +cryptographically secure communications, it may be easy to +monitor the communications at their endpoints. + +Basically, anyone with root access on the local machine on which you +are running the software may be able to do anything. Anyone with root +access on the server machine may be able to monitor your +communications, and a very talented root user might even be able to +send his/her own requests to your authentication agent. + +One should also be aware that computers send out electromagnetic +radition that can sometimes be picked up hundreds of meters away. +Your keyboard is particularly easy to listen to. The image on your +monitor might also be seen on another monitor in a van parked behind +your house. + +Beware that unwanted visitors might come to your home or office and +use your machine while you are away. They might also make +modifications or install bugs in your hardware or software. + +Beware that the most effective way for someone to decrypt your data +may be with a rubber hose. + + +LEGAL ISSUES + +As far as I am concerned, anyone is permitted to use this software +freely. However, see the file COPYING for detailed copying, +licensing, and distribution information. + +In some countries, particularly France, Russia, Iraq, and Pakistan, +it may be illegal to use any encryption at all without a special +permit, and the rumor has it that you cannot get a permit for any +strong encryption. + +This software may be freely imported into the United States; however, +the United States Government may consider re-exporting it a criminal +offence. + +Note that any information and cryptographic algorithms used in this +software are publicly available on the Internet and at any major +bookstore, scientific library, or patent office worldwide. + +THERE IS NO WARRANTY FOR THIS PROGRAM. Please consult the file +COPYING for more information. + + +MAILING LISTS AND OTHER INFORMATION + +There is a mailing list for ossh. It is ossh@sics.se. If you would +like to join, send a message to majordomo@sics.se with "subscribe +ssh" in body. + +The WWW home page for ssh is http://www.cs.hut.fi/ssh. It contains an +archive of the mailing list, and detailed information about new +releases, mailing lists, and other relevant issues. + +Bug reports should be sent to ossh-bugs@sics.se. + + +ABOUT THE AUTHOR + +This software was written by Tatu Ylonen <ylo@cs.hut.fi>. I work as a +researcher at Helsinki University of Technology, Finland. For more +information, see http://www.cs.hut.fi/~ylo/. My PGP public key is +available via finger from ylo@cs.hut.fi and from the key servers. I +prefer PGP encrypted mail. + +The author can be contacted via ordinary mail at + Tatu Ylonen + Helsinki University of Technology + Otakaari 1 + FIN-02150 ESPOO + Finland + + Fax. +358-0-4513293 + + +ACKNOWLEDGEMENTS + +I thank Tero Kivinen, Timo Rinne, Janne Snabb, and Heikki Suonsivu for +their help and comments in the design, implementation and porting of +this software. I also thank numerous contributors, including but not +limited to Walker Aumann, Jurgen Botz, Hans-Werner Braun, Stephane +Bortzmeyer, Adrian Colley, Michael Cooper, David Dombek, Jerome +Etienne, Bill Fithen, Mark Fullmer, Bert Gijsbers, Andreas Gustafsson, +Michael Henits, Steve Johnson, Thomas Koenig, Felix Leitner, Gunnar +Lindberg, Andrew Macpherson, Marc Martinec, Paul Mauvais, Donald +McKillican, Leon Mlakar, Robert Muchsel, Mark Treacy, Bryan +O'Sullivan, Mikael Suokas, Ollivier Robert, Jakob Schlyter, Tomasz +Surmacz, Alvar Vinacua, Petri Virkkula, Michael Warfield, and +Cristophe Wolfhugel. + +Thanks also go to Philip Zimmermann, whose PGP software and the +associated legal battle provided inspiration, motivation, and many +useful techniques, and to Bruce Schneier whose book Applied +Cryptography has done a great service in widely distributing knowledge +about cryptographic methods. + + +Copyright (c) 1995 Tatu Ylonen, Espoo, Finland. diff --git a/README.openssh b/README.openssh new file mode 100644 index 00000000..02cb3c60 --- /dev/null +++ b/README.openssh @@ -0,0 +1,44 @@ +This is a Linux port of OpenBSD's excellent OpenSSH. + +OpenSSH is based on the last free version of Tatu Ylonen's SSH with all +patent-encumbered algorithms removed, all known security bugs fixed, new +features reintroduced and many other clean-ups. + +This Linux port basically consists of a few fixes to deal with the way that +OpenSSL is usually installed on Linux systems, a few replacements for +OpenBSD library functions and the introduction of partial PAM support. + +The PAM support is less than optimal - it is only used when password +authentication is requested, so things like pam_limits will not apply if a +user authenticates with a RSA key. OTOH this is exactly the level of support +that the popular Linux SSH packages have. Perhaps a PAM hacker can rectify +this? + +All new code is released under a XFree style license, which is very liberal. +This code is released with no warranties of any kind, neither I nor my +employer (Internet Business Solutions) will take any responsibility for +any loss, damage or liability arising from the use or abuse of this software. + +OpenSSH depends on Zlib, OpenSSL and PAM. Use the Makefile.GNU to build it. + +Damien Miller <djm@ibs.com.au> +Internet Business Solutions + + +Credits - + +The OpenBSD team +'jonchen' - the original author of PAM support of SSH + +Miscellania - + +This version of SSH is based upon code retrieved from the OpenBSD CVS +repository on 1999-10-26, which in turn was based on the last free +version of SSH released by Tatu Ylonen. + +Code in helper.[ch] is Copyright 1999 Internet Business Solutions and +is released under a X11-style license (see source file for details). + +(A)RC4 code in rc4.[ch] is Copyright 1999 Damien Miller. It too is +under a X11-style license (see source file for details). + diff --git a/RFC.nroff b/RFC.nroff new file mode 100644 index 00000000..cc197aaf --- /dev/null +++ b/RFC.nroff @@ -0,0 +1,1780 @@ +.\" -*- nroff -*- +.\" +.\" $Id: RFC.nroff,v 1.1 1999/10/27 03:42:43 damien Exp $ +.\" +.pl 10.0i +.po 0 +.ll 7.2i +.lt 7.2i +.nr LL 7.2i +.nr LT 7.2i +.ds LF Ylonen +.ds RF FORMFEED[Page %] +.ds CF +.ds LH Internet-Draft +.ds RH 15 November 1995 +.ds CH SSH (Secure Shell) Remote Login Protocol +.na +.hy 0 +.in 0 +Network Working Group T. Ylonen +Internet-Draft Helsinki University of Technology +draft-ylonen-ssh-protocol-00.txt 15 November 1995 +Expires: 15 May 1996 + +.in 3 + +.ce +The SSH (Secure Shell) Remote Login Protocol + +.ti 0 +Status of This Memo + +This document is an Internet-Draft. Internet-Drafts are working +documents of the Internet Engineering Task Force (IETF), its areas, +and its working groups. Note that other groups may also distribute +working documents as Internet-Drafts. + +Internet-Drafts are draft documents valid for a maximum of six +months and may be updated, replaced, or obsoleted by other docu- +ments at any time. It is inappropriate to use Internet-Drafts as +reference material or to cite them other than as ``work in pro- +gress.'' + +To learn the current status of any Internet-Draft, please check the +``1id-abstracts.txt'' listing contained in the Internet- Drafts Shadow +Directories on ftp.is.co.za (Africa), nic.nordu.net (Europe), +munnari.oz.au (Pacific Rim), ds.internic.net (US East Coast), or +ftp.isi.edu (US West Coast). + +The distribution of this memo is unlimited. + +.ti 0 +Introduction + +SSH (Secure Shell) is a program to log into another computer over a +network, to execute commands in a remote machine, and to move files +from one machine to another. It provides strong authentication and +secure communications over insecure networks. Its features include +the following: +.IP o +Closes several security holes (e.g., IP, routing, and DNS spoofing). +New authentication methods: .rhosts together with RSA [RSA] based host +authentication, and pure RSA authentication. +.IP o +All communications are automatically and transparently encrypted. +Encryption is also used to protect integrity. +.IP o +X11 connection forwarding provides secure X11 sessions. +.IP o +Arbitrary TCP/IP ports can be redirected over the encrypted channel +in both directions. +.IP o +Client RSA-authenticates the server machine in the beginning of every +connection to prevent trojan horses (by routing or DNS spoofing) and +man-in-the-middle attacks, and the server RSA-authenticates the client +machine before accepting .rhosts or /etc/hosts.equiv authentication +(to prevent DNS, routing, or IP spoofing). +.IP o +An authentication agent, running in the user's local workstation or +laptop, can be used to hold the user's RSA authentication keys. +.RT + +The goal has been to make the software as easy to use as possible for +ordinary users. The protocol has been designed to be as secure as +possible while making it possible to create implementations that +are easy to use and install. The sample implementation has a number +of convenient features that are not described in this document as they +are not relevant for the protocol. + + +.ti 0 +Overview of the Protocol + +The software consists of a server program running on a server machine, +and a client program running on a client machine (plus a few auxiliary +programs). The machines are connected by an insecure IP [RFC0791] +network (that can be monitored, tampered with, and spoofed by hostile +parties). + +A connection is always initiated by the client side. The server +listens on a specific port waiting for connections. Many clients may +connect to the same server machine. + +The client and the server are connected via a TCP/IP [RFC0793] socket +that is used for bidirectional communication. Other types of +transport can be used but are currently not defined. + +When the client connects the server, the server accepts the connection +and responds by sending back its version identification string. The +client parses the server's identification, and sends its own +identification. The purpose of the identification strings is to +validate that the connection was to the correct port, declare the +protocol version number used, and to declare the software version used +on each side (for debugging purposes). The identification strings are +human-readable. If either side fails to understand or support the +other side's version, it closes the connection. + +After the protocol identification phase, both sides switch to a packet +based binary protocol. The server starts by sending its host key +(every host has an RSA key used to authenticate the host), server key +(an RSA key regenerated every hour), and other information to the +client. The client then generates a 256 bit session key, encrypts it +using both RSA keys (see below for details), and sends the encrypted +session key and selected cipher type to the server. Both sides then +turn on encryption using the selected algorithm and key. The server +sends an encrypted confirmation message to the client. + +The client then authenticates itself using any of a number of +authentication methods. The currently supported authentication +methods are .rhosts or /etc/hosts.equiv authentication (disabled by +default), the same with RSA-based host authentication, RSA +authentication, and password authentication. + +After successful authentication, the client makes a number of requests +to prepare for the session. Typical requests include allocating a +pseudo tty, starting X11 [X11] or TCP/IP port forwarding, starting +authentication agent forwarding, and executing the shell or a command. + +When a shell or command is executed, the connection enters interactive +session mode. In this mode, data is passed in both directions, +new forwarded connections may be opened, etc. The interactive session +normally terminates when the server sends the exit status of the +program to the client. + + +The protocol makes several reservations for future extensibility. +First of all, the initial protocol identification messages include the +protocol version number. Second, the first packet by both sides +includes a protocol flags field, which can be used to agree on +extensions in a compatible manner. Third, the authentication and +session preparation phases work so that the client sends requests to +the server, and the server responds with success or failure. If the +client sends a request that the server does not support, the server +simply returns failure for it. This permits compatible addition of +new authentication methods and preparation operations. The +interactive session phase, on the other hand, works asynchronously and +does not permit the use of any extensions (because there is no easy +and reliable way to signal rejection to the other side and problems +would be hard to debug). Any compatible extensions to this phase must +be agreed upon during any of the earlier phases. + +.ti 0 +The Binary Packet Protocol + +After the protocol identification strings, both sides only send +specially formatted packets. The packet layout is as follows: +.IP o +Packet length: 32 bit unsigned integer, coded as four 8-bit bytes, msb +first. Gives the length of the packet, not including the length field +and padding. The maximum length of a packet (not including the length +field and padding) is 262144 bytes. +.IP o +Padding: 1-8 bytes of random data (or zeroes if not encrypting). The +amount of padding is (8 - (length % 8)) bytes (where % stands for the +modulo operator). The rationale for always having some random padding +at the beginning of each packet is to make known plaintext attacks +more difficult. +.IP o +Packet type: 8-bit unsigned byte. The value 255 is reserved for +future extension. +.IP o +Data: binary data bytes, depending on the packet type. The number of +data bytes is the "length" field minus 5. +.IP o +Check bytes: 32-bit crc, four 8-bit bytes, msb first. The crc is the +Cyclic Redundancy Check, with the polynomial 0xedb88320, of the +Padding, Packet type, and Data fields. The crc is computed before +any encryption. +.RT + +The packet, except for the length field, may be encrypted using any of +a number of algorithms. The length of the encrypted part (Padding + +Type + Data + Check) is always a multiple of 8 bytes. Typically the +cipher is used in a chained mode, with all packets chained together as +if it was a single data stream (the length field is never included in +the encryption process). Details of encryption are described below. + +When the session starts, encryption is turned off. Encryption is +enabled after the client has sent the session key. The encryption +algorithm to use is selected by the client. + + +.ti 0 +Packet Compression + +If compression is supported (it is an optional feature, see +SSH_CMSG_REQUEST_COMPRESSION below), the packet type and data fields +of the packet are compressed using the gzip deflate algorithm [GZIP]. +If compression is in effect, the packet length field indicates the +length of the compressed data, plus 4 for the crc. The amount of +padding is computed from the compressed data, so that the amount of +data to be encrypted becomes a multiple of 8 bytes. + +When compressing, the packets (type + data portions) in each direction +are compressed as if they formed a continuous data stream, with only the +current compression block flushed between packets. This corresponds +to the GNU ZLIB library Z_PARTIAL_FLUSH option. The compression +dictionary is not flushed between packets. The two directions are +compressed independently of each other. + + +.ti 0 +Packet Encryption + +The protocol supports several encryption methods. During session +initialization, the server sends a bitmask of all encryption methods +that it supports, and the client selects one of these methods. The +client also generates a 256-bit random session key (32 8-bit bytes) and +sends it to the server. + +The encryption methods supported by the current implementation, and +their codes are: +.TS +center; +l r l. +SSH_CIPHER_NONE 0 No encryption +SSH_CIPHER_IDEA 1 IDEA in CFB mode +SSH_CIPHER_DES 2 DES in CBC mode +SSH_CIPHER_3DES 3 Triple-DES in CBC mode +SSH_CIPHER_TSS 4 An experimental stream cipher +SSH_CIPHER_RC4 5 RC4 +.TE + +All implementations are required to support SSH_CIPHER_DES and +SSH_CIPHER_3DES. Supporting SSH_CIPHER_IDEA, SSH_CIPHER_RC4, and +SSH_CIPHER_NONE is recommended. Support for SSH_CIPHER_TSS is +optional (and it is not described in this document). Other ciphers +may be added at a later time; support for them is optional. + +For encryption, the encrypted portion of the packet is considered a +linear byte stream. The length of the stream is always a multiple of +8. The encrypted portions of consecutive packets (in the same +direction) are encrypted as if they were a continuous buffer (that is, +any initialization vectors are passed from the previous packet to the +next packet). Data in each direction is encrypted independently. +.IP SSH_CIPHER_DES +The key is taken from the first 8 bytes of the session key. The least +significant bit of each byte is ignored. This results in 56 bits of +key data. DES [DES] is used in CBC mode. The iv (initialization vector) is +initialized to all zeroes. +.IP SSH_CIPHER_3DES +The variant of triple-DES used here works as follows: there are three +independent DES-CBC ciphers, with independent initialization vectors. +The data (the whole encrypted data stream) is first encrypted with the +first cipher, then decrypted with the second cipher, and finally +encrypted with the third cipher. All these operations are performed +in CBC mode. + +The key for the first cipher is taken from the first 8 bytes of the +session key; the key for the next cipher from the next 8 bytes, and +the key for the third cipher from the following 8 bytes. All three +initialization vectors are initialized to zero. + +(Note: the variant of 3DES used here differs from some other +descriptions.) +.IP SSH_CIPHER_IDEA +The key is taken from the first 16 bytes of the session key. IDEA +[IDEA] is used in CFB mode. The initialization vector is initialized +to all zeroes. +.IP SSH_CIPHER_TSS +All 32 bytes of the session key are used as the key. + +There is no reference available for the TSS algorithm; it is currently +only documented in the sample implementation source code. The +security of this cipher is unknown (but it is quite fast). The cipher +is basically a stream cipher that uses MD5 as a random number +generator and takes feedback from the data. +.IP SSH_CIPHER_RC4 +The first 16 bytes of the session key are used as the key for the +server to client direction. The remaining 16 bytes are used as the +key for the client to server direction. This gives independent +128-bit keys for each direction. + +This algorithm is the alleged RC4 cipher posted to the Usenet in 1995. +It is widely believed to be equivalent with the original RSADSI RC4 +cipher. This is a very fast algorithm. +.RT + + +.ti 0 +Data Type Encodings + +The Data field of each packet contains data encoded as described in +this section. There may be several data items; each item is coded as +described here, and their representations are concatenated together +(without any alignment or padding). + +Each data type is stored as follows: +.IP "8-bit byte" +The byte is stored directly as a single byte. +.IP "32-bit unsigned integer" +Stored in 4 bytes, msb first. +.IP "Arbitrary length binary string" +First 4 bytes are the length of the string, msb first (not including +the length itself). The following "length" bytes are the string +value. There are no terminating null characters. +.IP "Multiple-precision integer" +First 2 bytes are the number of bits in the integer, msb first (for +example, the value 0x00012345 would have 17 bits). The value zero has +zero bits. It is permissible that the number of bits be larger than the +real number of bits. + +The number of bits is followed by (bits + 7) / 8 bytes of binary data, +msb first, giving the value of the integer. +.RT + + +.ti 0 +TCP/IP Port Number and Other Options + +The server listens for connections on TCP/IP port 22. + +The client may connect the server from any port. However, if the +client wishes to use any form of .rhosts or /etc/hosts.equiv +authentication, it must connect from a privileged port (less than +1024). + +For the IP Type of Service field [RFC0791], it is recommended that +interactive sessions (those having a user terminal or forwarding X11 +connections) use the IPTOS_LOWDELAY, and non-interactive connections +use IPTOS_THROUGHPUT. + +It is recommended that keepalives are used, because otherwise programs +on the server may never notice if the other end of the connection is +rebooted. + + +.ti 0 +Protocol Version Identification + +After the socket is opened, the server sends an identification string, +which is of the form +"SSH-<protocolmajor>.<protocolminor>-<version>\\n", where +<protocolmajor> and <protocolminor> are integers and specify the +protocol version number (not software distribution version). +<version> is server side software version string (max 40 characters); +it is not interpreted by the remote side but may be useful for +debugging. + +The client parses the server's string, and sends a corresponding +string with its own information in response. If the server has lower +version number, and the client contains special code to emulate it, +the client responds with the lower number; otherwise it responds with +its own number. The server then compares the version number the +client sent with its own, and determines whether they can work +together. The server either disconnects, or sends the first packet +using the binary packet protocol and both sides start working +according to the lower of the protocol versions. + +By convention, changes which keep the protocol compatible with +previous versions keep the same major protocol version; changes that +are not compatible increment the major version (which will hopefully +never happen). The version described in this document is 1.3. + +The client will + +.ti 0 +Key Exchange and Server Host Authentication + +The first message sent by the server using the packet protocol is +SSH_SMSG_PUBLIC_KEY. It declares the server's host key, server public +key, supported ciphers, supported authentication methods, and flags +for protocol extensions. It also contains a 64-bit random number +(cookie) that must be returned in the client's reply (to make IP +spoofing more difficult). No encryption is used for this message. + +Both sides compute a session id as follows. The modulus of the server +key is interpreted as a byte string (without explicit length field, +with minimum length able to hold the whole value), most significant +byte first. This string is concatenated with the server host key +interpreted the same way. Additionally, the cookie is concatenated +with this. Both sides compute MD5 of the resulting string. The +resulting 16 bytes (128 bits) are stored by both parties and are +called the session id. + +The client responds with a SSH_CMSG_SESSION_KEY message, which +contains the selected cipher type, a copy of the 64-bit cookie sent by +the server, client's protocol flags, and a session key encrypted +with both the server's host key and server key. No encryption is used +for this message. + +The session key is 32 8-bit bytes (a total of 256 random bits +generated by the client). The client first xors the 16 bytes of the +session id with the first 16 bytes of the session key. The resulting +string is then encrypted using the smaller key (one with smaller +modulus), and the result is then encrypted using the other key. The +number of bits in the public modulus of the two keys must differ by at +least 128 bits. + +At each encryption step, a multiple-precision integer is constructed +from the data to be encrypted as follows (the integer is here +interpreted as a sequence of bytes, msb first; the number of bytes is +the number of bytes needed to represent the modulus). + +The most significant byte (which is only partial as the value must be +less than the public modulus, which is never a power of two) is zero. + +The next byte contains the value 2 (which stands for public-key +encrypted data in the PKCS standard [PKCS#1]). Then, there are +non-zero random bytes to fill any unused space, a zero byte, and the +data to be encrypted in the least significant bytes, the last byte of +the data in the least significant byte. + +This algorithm is used twice. First, it is used to encrypt the 32 +random bytes generated by the client to be used as the session key +(xored by the session id). This value is converted to an integer as +described above, and encrypted with RSA using the key with the smaller +modulus. The resulting integer is converted to a byte stream, msb +first. This byte stream is padded and encrypted identically using the +key with the larger modulus. + +After the client has sent the session key, it starts to use the +selected algorithm and key for decrypting any received packets, and +for encrypting any sent packets. Separate ciphers are used for +different directions (that is, both directions have separate +initialization vectors or other state for the ciphers). + +When the server has received the session key message, and has turned +on encryption, it sends a SSH_SMSG_SUCCESS message to the client. + +The recommended size of the host key is 1024 bits, and 768 bits for +the server key. The minimum size is 512 bits for the smaller key. + + +.ti 0 +Declaring the User Name + +The client then sends a SSH_CMSG_USER message to the server. This +message specifies the user name to log in as. + +The server validates that such a user exists, checks whether +authentication is needed, and responds with either SSH_SMSG_SUCCESS or +SSH_SMSG_FAILURE. SSH_SMSG_SUCCESS indicates that no authentication +is needed for this user (no password), and authentication phase has +now been completed. SSH_SMSG_FAILURE indicates that authentication is +needed (or the user does not exist). + +If the user does not exist, it is recommended that this returns +failure, but the server keeps reading messages from the client, and +responds to any messages (except SSH_MSG_DISCONNECT, SSH_MSG_IGNORE, +and SSH_MSG_DEBUG) with SSH_SMSG_FAILURE. This way the client cannot +be certain whether the user exists. + + +.ti 0 +Authentication Phase + +Provided the server didn't immediately accept the login, an +authentication exchange begins. The client sends messages to the +server requesting different types of authentication in arbitrary order as +many times as desired (however, the server may close the connection +after a timeout). The server always responds with SSH_SMSG_SUCCESS if +it has accepted the authentication, and with SSH_SMSG_FAILURE if it has +denied authentication with the requested method or it does not +recognize the message. Some authentication methods cause an exchange +of further messages before the final result is sent. The +authentication phase ends when the server responds with success. + +The recommended value for the authentication timeout (timeout before +disconnecting if no successful authentication has been made) is 5 +minutes. + +The following authentication methods are currently supported: +.TS +center; +l r l. +SSH_AUTH_RHOSTS 1 .rhosts or /etc/hosts.equiv +SSH_AUTH_RSA 2 pure RSA authentication +SSH_AUTH_PASSWORD 3 password authentication +SSH_AUTH_RHOSTS_RSA 4 .rhosts with RSA host authentication +.TE +.IP SSH_AUTH_RHOSTS + +This is the authentication method used by rlogin and rsh [RFC1282]. + +The client sends SSH_CMSG_AUTH_RHOSTS with the client-side user name +as an argument. + +The server checks whether to permit authentication. On UNIX systems, +this is usually done by checking /etc/hosts.equiv, and .rhosts in the +user's home directory. The connection must come from a privileged +port. + +It is recommended that the server checks that there are no IP options +(such as source routing) specified for the socket before accepting +this type of authentication. The client host name should be +reverse-mapped and then forward mapped to ensure that it has the +proper IP-address. + +This authentication method trusts the remote host (root on the remote +host can pretend to be any other user on that host), the name +services, and partially the network: anyone who can see packets coming +out from the server machine can do IP-spoofing and pretend to be any +machine; however, the protocol prevents blind IP-spoofing (which used +to be possible with rlogin). + +Many sites probably want to disable this authentication method because +of the fundamental insecurity of conventional .rhosts or +/etc/hosts.equiv authentication when faced with spoofing. It is +recommended that this method not be supported by the server by +default. +.IP SSH_AUTH_RHOSTS_RSA + +In addition to conventional .rhosts and hosts.equiv authentication, +this method additionally requires that the client host be +authenticated using RSA. + +The client sends SSH_CMSG_AUTH_RHOSTS_RSA specifying the client-side +user name, and the public host key of the client host. + +The server first checks if normal .rhosts or /etc/hosts.equiv +authentication would be accepted, and if not, responds with +SSH_SMSG_FAILURE. Otherwise, it checks whether it knows the host key +for the client machine (using the same name for the host that was used +for checking the .rhosts and /etc/hosts.equiv files). If it does not +know the RSA key for the client, access is denied and SSH_SMSG_FAILURE +is sent. + +If the server knows the host key of the client machine, it verifies +that the given host key matches that known for the client. If not, +access is denied and SSH_SMSG_FAILURE is sent. + +The server then sends a SSH_SMSG_AUTH_RSA_CHALLENGE message containing +an encrypted challenge for the client. The challenge is 32 8-bit +random bytes (256 bits). When encrypted, the highest (partial) byte +is left as zero, the next byte contains the value 2, the following are +non-zero random bytes, followed by a zero byte, and the challenge put +in the remaining bytes. This is then encrypted using RSA with the +client host's public key. (The padding and encryption algorithm is +the same as that used for the session key.) + +The client decrypts the challenge using its private host key, +concatenates this with the session id, and computes an MD5 checksum +of the resulting 48 bytes. The MD5 output is returned as 16 bytes in +a SSH_CMSG_AUTH_RSA_RESPONSE message. (MD5 is used to deter chosen +plaintext attacks against RSA; the session id binds it to a specific +session). + +The server verifies that the MD5 of the decrypted challenge returned by +the client matches that of the original value, and sends SSH_SMSG_SUCCESS if +so. Otherwise it sends SSH_SMSG_FAILURE and refuses the +authentication attempt. + +This authentication method trusts the client side machine in that root +on that machine can pretend to be any user on that machine. +Additionally, it trusts the client host key. The name and/or IP +address of the client host is only used to select the public host key. +The same host name is used when scanning .rhosts or /etc/hosts.equiv +and when selecting the host key. It would in principle be possible to +eliminate the host name entirely and substitute it directly by the +host key. IP and/or DNS [RFC1034] spoofing can only be used +to pretend to be a host for which the attacker has the private host +key. +.IP SSH_AUTH_RSA + +The idea behind RSA authentication is that the server recognizes the +public key offered by the client, generates a random challenge, and +encrypts the challenge with the public key. The client must then +prove that it has the corresponding private key by decrypting the +challenge. + +The client sends SSH_CMSG_AUTH_RSA with public key modulus (n) as an +argument. + +The server may respond immediately with SSH_SMSG_FAILURE if it does +not permit authentication with this key. Otherwise it generates a +challenge, encrypts it using the user's public key (stored on the +server and identified using the modulus), and sends +SSH_SMSG_AUTH_RSA_CHALLENGE with the challenge (mp-int) as an +argument. + +The challenge is 32 8-bit random bytes (256 bits). When encrypted, +the highest (partial) byte is left as zero, the next byte contains the +value 2, the following are non-zero random bytes, followed by a zero +byte, and the challenge put in the remaining bytes. This is then +encrypted with the public key. (The padding and encryption algorithm +is the same as that used for the session key.) + +The client decrypts the challenge using its private key, concatenates +it with the session id, and computes an MD5 checksum of the resulting +48 bytes. The MD5 output is returned as 16 bytes in a +SSH_CMSG_AUTH_RSA_RESPONSE message. (Note that the MD5 is necessary +to avoid chosen plaintext attacks against RSA; the session id binds it +to a specific session.) + +The server verifies that the MD5 of the decrypted challenge returned +by the client matches that of the original value, and sends +SSH_SMSG_SUCCESS if so. Otherwise it sends SSH_SMSG_FAILURE and +refuses the authentication attempt. + +This authentication method does not trust the remote host, the +network, name services, or anything else. Authentication is based +solely on the possession of the private identification keys. Anyone +in possession of the private keys can log in, but nobody else. + +The server may have additional requirements for a successful +authentiation. For example, to limit damage due to a compromised RSA +key, a server might restrict access to a limited set of hosts. +.IP SSH_AUTH_PASSWORD + +The client sends a SSH_CMSG_AUTH_PASSWORD message with the plain text +password. (Note that even though the password is plain text inside +the message, it is normally encrypted by the packet mechanism.) + +The server verifies the password, and sends SSH_SMSG_SUCCESS if +authentication was accepted and SSH_SMSG_FAILURE otherwise. + +Note that the password is read from the user by the client; the user +never interacts with a login program. + +This authentication method does not trust the remote host, the +network, name services or anything else. Authentication is based +solely on the possession of the password. Anyone in possession of the +password can log in, but nobody else. +.RT + +.ti 0 +Preparatory Operations + +After successful authentication, the server waits for a request from +the client, processes the request, and responds with SSH_SMSG_SUCCESS +whenever a request has been successfully processed. If it receives a +message that it does not recognize or it fails to honor a request, it +returns SSH_SMSG_FAILURE. It is expected that new message types might +be added to this phase in future. + +The following messages are currently defined for this phase. +.IP SSH_CMSG_REQUEST_COMPRESSION +Requests that compression be enabled for this session. A +gzip-compatible compression level (1-9) is passed as an argument. +.IP SSH_CMSG_REQUEST_PTY +Requests that a pseudo terminal device be allocated for this session. +The user terminal type and terminal modes are supplied as arguments. +.IP SSH_CMSG_X11_REQUEST_FORWARDING +Requests forwarding of X11 connections from the remote machine to the +local machine over the secure channel. Causes an internet-domain +socket to be allocated and the DISPLAY variable to be set on the server. +X11 authentication data is automatically passed to the server, and the +client may implement spoofing of authentication data for added +security. The authentication data is passed as arguments. +.IP SSH_CMSG_PORT_FORWARD_REQUEST +Requests forwarding of a TCP/IP port on the server host over the +secure channel. What happens is that whenever a connection is made to +the port on the server, a connection will be made from the client end +to the specified host/port. Any user can forward unprivileged ports; +only the root can forward privileged ports (as determined by +authentication done earlier). +.IP SSH_CMSG_AGENT_REQUEST_FORWARDING +Requests forwarding of the connection to the authentication agent. +.IP SSH_CMSG_EXEC_SHELL +Starts a shell (command interpreter) for the user, and moves into +interactive session mode. +.IP SSH_CMSG_EXEC_CMD +Executes the given command (actually "<shell> -c <command>" or +equivalent) for the user, and moves into interactive session mode. +.RT + + +.ti 0 +Interactive Session and Exchange of Data + +During the interactive session, any data written by the shell or +command running on the server machine is forwarded to stdin or +stderr on the client machine, and any input available from stdin on +the client machine is forwarded to the program on the server machine. + +All exchange is asynchronous; either side can send at any time, and +there are no acknowledgements (TCP/IP already provides reliable +transport, and the packet protocol protects against tampering or IP +spoofing). + +When the client receives EOF from its standard input, it will send +SSH_CMSG_EOF; however, this in no way terminates the exchange. The +exchange terminates and interactive mode is left when the server sends +SSH_SMSG_EXITSTATUS to indicate that the client program has +terminated. Alternatively, either side may disconnect at any time by +sending SSH_MSG_DISCONNECT or closing the connection. + +The server may send any of the following messages: +.IP SSH_SMSG_STDOUT_DATA +Data written to stdout by the program running on the server. The data +is passed as a string argument. The client writes this data to +stdout. +.IP SSH_SMSG_STDERR_DATA +Data written to stderr by the program running on the server. The data +is passed as a string argument. The client writes this data to +stderr. (Note that if the program is running on a tty, it is not +possible to separate stdout and stderr data, and all data will be sent +as stdout data.) +.IP SSH_SMSG_EXITSTATUS +Indicates that the shell or command has exited. Exit status is passed +as an integer argument. This message causes termination of the +interactive session. +.IP SSH_SMSG_AGENT_OPEN +Indicates that someone on the server side is requesting a connection +to the authentication agent. The server-side channel number is passed +as an argument. The client must respond with either +SSH_CHANNEL_OPEN_CONFIRMATION or SSH_CHANNEL_OPEN_FAILURE. +.IP SSH_SMSG_X11_OPEN +Indicates that a connection has been made to the X11 socket on the +server side and should be forwarded to the real X server. An integer +argument indicates the channel number allocated for this connection on +the server side. The client should send back either +SSH_MSG_CHANNEL_OPEN_CONFIRMATION or SSH_MSG_CHANNEL_OPEN_FAILURE with +the same server side channel number. +.IP SSH_MSG_PORT_OPEN +Indicates that a connection has been made to a port on the server side +for which forwarding has been requested. Arguments are server side +channel number, host name to connect to, and port to connect to. The +client should send back either +SSH_MSG_CHANNEL_OPEN_CONFIRMATION or SSH_MSG_CHANNEL_OPEN_FAILURE with +the same server side channel number. +.IP SSH_MSG_CHANNEL_OPEN_CONFIRMATION +This is sent by the server to indicate that it has opened a connection +as requested in a previous message. The first argument indicates the +client side channel number, and the second argument is the channel number +that the server has allocated for this connection. +.IP SSH_MSG_CHANNEL_OPEN_FAILURE +This is sent by the server to indicate that it failed to open a +connection as requested in a previous message. The client-side +channel number is passed as an argument. The client will close the +descriptor associated with the channel and free the channel. +.IP SSH_MSG_CHANNEL_DATA +This packet contains data for a channel from the server. The first +argument is the client-side channel number, and the second argument (a +string) is the data. +.IP SSH_MSG_CHANNEL_CLOSE +This is sent by the server to indicate that whoever was in the other +end of the channel has closed it. The argument is the client side channel +number. The client will let all buffered data in the channel to +drain, and when ready, will close the socket, free the channel, and +send the server a SSH_MSG_CHANNEL_CLOSE_CONFIRMATION message for the +channel. +.IP SSH_MSG_CHANNEL_CLOSE_CONFIRMATION +This is send by the server to indicate that a channel previously +closed by the client has now been closed on the server side as well. +The argument indicates the client channel number. The client frees +the channel. +.RT + +The client may send any of the following messages: +.IP SSH_CMSG_STDIN_DATA +This is data to be sent as input to the program running on the server. +The data is passed as a string. +.IP SSH_CMSG_EOF +Indicates that the client has encountered EOF while reading standard +input. The server will allow any buffered input data to drain, and +will then close the input to the program. +.IP SSH_CMSG_WINDOW_SIZE +Indicates that window size on the client has been changed. The server +updates the window size of the tty and causes SIGWINCH to be sent to +the program. The new window size is passed as four integer arguments: +row, col, xpixel, ypixel. +.IP SSH_MSG_PORT_OPEN +Indicates that a connection has been made to a port on the client side +for which forwarding has been requested. Arguments are client side +channel number, host name to connect to, and port to connect to. The +server should send back either SSH_MSG_CHANNEL_OPEN_CONFIRMATION or +SSH_MSG_CHANNEL_OPEN_FAILURE with the same client side channel number. +.IP SSH_MSG_CHANNEL_OPEN_CONFIRMATION +This is sent by the client to indicate that it has opened a connection +as requested in a previous message. The first argument indicates the +server side channel number, and the second argument is the channel +number that the client has allocated for this connection. +.IP SSH_MSG_CHANNEL_OPEN_FAILURE +This is sent by the client to indicate that it failed to open a +connection as requested in a previous message. The server side +channel number is passed as an argument. The server will close the +descriptor associated with the channel and free the channel. +.IP SSH_MSG_CHANNEL_DATA +This packet contains data for a channel from the client. The first +argument is the server side channel number, and the second argument (a +string) is the data. +.IP SSH_MSG_CHANNEL_CLOSE +This is sent by the client to indicate that whoever was in the other +end of the channel has closed it. The argument is the server channel +number. The server will allow buffered data to drain, and when ready, +will close the socket, free the channel, and send the client a +SSH_MSG_CHANNEL_CLOSE_CONFIRMATION message for the channel. +.IP SSH_MSG_CHANNEL_CLOSE_CONFIRMATION +This is send by the client to indicate that a channel previously +closed by the server has now been closed on the client side as well. +The argument indicates the server channel number. The server frees +the channel. +.RT + +Any unsupported messages during interactive mode cause the connection +to be terminated with SSH_MSG_DISCONNECT and an error message. +Compatible protocol upgrades should agree about any extensions during +the preparation phase or earlier. + + +.ti 0 +Termination of the Connection + +Normal termination of the connection is always initiated by the server +by sending SSH_SMSG_EXITSTATUS after the program has exited. The +client responds to this message by sending SSH_CMSG_EXIT_CONFIRMATION +and closes the socket; the server then closes the socket. There are +two purposes for the confirmation: some systems may lose previously +sent data when the socket is closed, and closing the client side first +causes any TCP/IP TIME_WAIT [RFC0793] waits to occur on the client side, not +consuming server resources. + +If the program terminates due to a signal, the server will send +SSH_MSG_DISCONNECT with an appropriate message. If the connection is +closed, all file descriptors to the program will be closed and the +server will exit. If the program runs on a tty, the kernel sends it +the SIGHUP signal when the pty master side is closed. + +.ti 0 +Protocol Flags + +Both the server and the client pass 32 bits of protocol flags to the +other side. The flags are intended for compatible protocol extension; +the server first announces which added capabilities it supports, and +the client then sends the capabilities that it supports. + +The following flags are currently defined (the values are bit masks): +.IP "1 SSH_PROTOFLAG_SCREEN_NUMBER" +This flag can only be sent by the client. It indicates that the X11 +forwarding requests it sends will include the screen number. +.IP "2 SSH_PROTOFLAG_HOST_IN_FWD_OPEN" +If both sides specify this flag, SSH_SMSG_X11_OPEN and +SSH_MSG_PORT_OPEN messages will contain an additional field containing +a description of the host at the other end of the connection. +.RT + +.ti 0 +Detailed Description of Packet Types and Formats + +The supported packet types and the corresponding message numbers are +given in the following table. Messages with _MSG_ in their name may +be sent by either side. Messages with _CMSG_ are only sent by the +client, and messages with _SMSG_ only by the server. + +A packet may contain additional data after the arguments specified +below. Any such data should be ignored by the receiver. However, it +is recommended that no such data be stored without good reason. (This +helps build compatible extensions.) +.IP "0 SSH_MSG_NONE" +This code is reserved. This message type is never sent. +.IP "1 SSH_MSG_DISCONNECT" +.TS +; +l l. +string Cause of disconnection +.TE +This message may be sent by either party at any time. It causes the +immediate disconnection of the connection. The message is intended to +be displayed to a human, and describes the reason for disconnection. +.IP "2 SSH_SMSG_PUBLIC_KEY" +.TS +; +l l. +8 bytes anti_spoofing_cookie +32-bit int server_key_bits +mp-int server_key_public_exponent +mp-int server_key_public_modulus +32-bit int host_key_bits +mp-int host_key_public_exponent +mp-int host_key_public_modulus +32-bit int protocol_flags +32-bit int supported_ciphers_mask +32-bit int supported_authentications_mask +.TE +Sent as the first message by the server. This message gives the +server's host key, server key, protocol flags (intended for compatible +protocol extension), supported_ciphers_mask (which is the +bitwise or of (1 << cipher_number), where << is the left shift +operator, for all supported ciphers), and +supported_authentications_mask (which is the bitwise or of (1 << +authentication_type) for all supported authentication types). The +anti_spoofing_cookie is 64 random bytes, and must be sent back +verbatim by the client in its reply. It is used to make IP-spoofing +more difficult (encryption and host keys are the real defense against +spoofing). +.IP "3 SSH_CMSG_SESSION_KEY" +.TS +; +l l. +1 byte cipher_type (must be one of the supported values) +8 bytes anti_spoofing_cookie (must match data sent by the server) +mp-int double-encrypted session key +32-bit int protocol_flags +.TE +Sent by the client as the first message in the session. Selects the +cipher to use, and sends the encrypted session key to the server. The +anti_spoofing_cookie must be the same bytes that were sent by the +server. Protocol_flags is intended for negotiating compatible +protocol extensions. +.IP "4 SSH_CMSG_USER" +.TS +; +l l. +string user login name on server +.TE +Sent by the client to begin authentication. Specifies the user name +on the server to log in as. The server responds with SSH_SMSG_SUCCESS +if no authentication is needed for this user, or SSH_SMSG_FAILURE if +authentication is needed (or the user does not exist). [Note to the +implementator: the user name is of arbitrary size. The implementation +must be careful not to overflow internal buffers.] +.IP "5 SSH_CMSG_AUTH_RHOSTS" +.TS +; +l l. +string client-side user name +.TE +Requests authentication using /etc/hosts.equiv and .rhosts (or +equivalent mechanisms). This authentication method is normally +disabled in the server because it is not secure (but this is the +method used by rsh and rlogin). The server responds with +SSH_SMSG_SUCCESS if authentication was successful, and +SSH_SMSG_FAILURE if access was not granted. The server should check +that the client side port number is less than 1024 (a privileged +port), and immediately reject authentication if it is not. Supporting +this authentication method is optional. This method should normally +not be enabled in the server because it is not safe. (However, not +enabling this only helps if rlogind and rshd are disabled.) +.IP "6 SSH_CMSG_AUTH_RSA" +.TS +; +l l. +mp-int identity_public_modulus +.TE +Requests authentication using pure RSA authentication. The server +checks if the given key is permitted to log in, and if so, responds +with SSH_SMSG_AUTH_RSA_CHALLENGE. Otherwise, it responds with +SSH_SMSG_FAILURE. The client often tries several different keys in +sequence until one supported by the server is found. Authentication +is accepted if the client gives the correct response to the challenge. +The server is free to add other criteria for authentication, such as a +requirement that the connection must come from a certain host. Such +additions are not visible at the protocol level. Supporting this +authentication method is optional but recommended. +.IP "7 SSH_SMSG_AUTH_RSA_CHALLENGE" +.TS +; +l l. +mp-int encrypted challenge +.TE +Presents an RSA authentication challenge to the client. The challenge +is a 256-bit random value encrypted as described elsewhere in this +document. The client must decrypt the challenge using the RSA private +key, compute MD5 of the challenge plus session id, and send back the +resulting 16 bytes using SSH_CMSG_AUTH_RSA_RESPONSE. +.IP "8 SSH_CMSG_AUTH_RSA_RESPONSE" +.TS +; +l l. +16 bytes MD5 of decrypted challenge +.TE +This message is sent by the client in response to an RSA challenge. +The MD5 checksum is returned instead of the decrypted challenge to +deter known-plaintext attacks against the RSA key. The server +responds to this message with either SSH_SMSG_SUCCESS or +SSH_SMSG_FAILURE. +.IP "9 SSH_CMSG_AUTH_PASSWORD" +.TS +; +l l. +string plain text password +.TE +Requests password authentication using the given password. Note that +even though the password is plain text inside the packet, the whole +packet is normally encrypted by the packet layer. It would not be +possible for the client to perform password encryption/hashing, +because it cannot know which kind of encryption/hashing, if any, the +server uses. The server responds to this message with +SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE. +.IP "10 SSH_CMSG_REQUEST_PTY" +.TS +; +l l. +string TERM environment variable value (e.g. vt100) +32-bit int terminal height, rows (e.g., 24) +32-bit int terminal width, columns (e.g., 80) +32-bit int terminal width, pixels (0 if no graphics) (e.g., 480) +32-bit int terminal height, pixels (0 if no graphics) (e.g., 640) +n bytes tty modes encoded in binary +.TE +Requests a pseudo-terminal to be allocated for this command. This +message can be used regardless of whether the session will later +execute the shell or a command. If a pty has been requested with this +message, the shell or command will run on a pty. Otherwise it will +communicate with the server using pipes, sockets or some other similar +mechanism. + +The terminal type gives the type of the user's terminal. In the UNIX +environment it is passed to the shell or command in the TERM +environment variable. + +The width and height values give the initial size of the user's +terminal or window. All values can be zero if not supported by the +operating system. The server will pass these values to the kernel if +supported. + +Terminal modes are encoded into a byte stream in a portable format. +The exact format is described later in this document. + +The server responds to the request with either SSH_SMSG_SUCCESS or +SSH_SMSG_FAILURE. If the server does not have the concept of pseudo +terminals, it should return success if it is possible to execute a +shell or a command so that it looks to the client as if it was running +on a pseudo terminal. +.IP "11 SSH_CMSG_WINDOW_SIZE" +.TS +; +l l. +32-bit int terminal height, rows +32-bit int terminal width, columns +32-bit int terminal width, pixels +32-bit int terminal height, pixels +.TE +This message can only be sent by the client during the interactive +session. This indicates that the size of the user's window has +changed, and provides the new size. The server will update the +kernel's notion of the window size, and a SIGWINCH signal or +equivalent will be sent to the shell or command (if supported by the +operating system). +.IP "12 SSH_CMSG_EXEC_SHELL" + +(no arguments) + +Starts a shell (command interpreter), and enters interactive session +mode. +.IP "13 SSH_CMSG_EXEC_CMD" +.TS +; +l l. +string command to execute +.TE +Starts executing the given command, and enters interactive session +mode. On UNIX, the command is run as "<shell> -c <command>", where +<shell> is the user's login shell. +.IP "14 SSH_SMSG_SUCCESS" + +(no arguments) + +This message is sent by the server in response to the session key, a +successful authentication request, and a successfully completed +preparatory operation. +.IP "15 SSH_SMSG_FAILURE" + +(no arguments) + +This message is sent by the server in response to a failed +authentication operation to indicate that the user has not yet been +successfully authenticated, and in response to a failed preparatory +operation. This is also sent in response to an authentication or +preparatory operation request that is not recognized or supported. +.IP "16 SSH_CMSG_STDIN_DATA" +.TS +; +l l. +string data +.TE +Delivers data from the client to be supplied as input to the shell or +program running on the server side. This message can only be used in +the interactive session mode. No acknowledgement is sent for this +message. +.IP "17 SSH_SMSG_STDOUT_DATA" +.TS +; +l l. +string data +.TE +Delivers data from the server that was read from the standard output of +the shell or program running on the server side. This message can +only be used in the interactive session mode. No acknowledgement is +sent for this message. +.IP "18 SSH_SMSG_STDERR_DATA" +.TS +; +l l. +string data +.TE +Delivers data from the server that was read from the standard error of +the shell or program running on the server side. This message can +only be used in the interactive session mode. No acknowledgement is +sent for this message. +.IP "19 SSH_CMSG_EOF" + +(no arguments) + +This message is sent by the client to indicate that EOF has been +reached on the input. Upon receiving this message, and after all +buffered input data has been sent to the shell or program, the server +will close the input file descriptor to the program. This message can +only be used in the interactive session mode. No acknowledgement is +sent for this message. +.IP "20 SSH_SMSG_EXITSTATUS" +.TS +; +l l. +32-bit int exit status of the command +.TE +Returns the exit status of the shell or program after it has exited. +The client should respond with SSH_CMSG_EXIT_CONFIRMATION when it has +received this message. This will be the last message sent by the +server. If the program being executed dies with a signal instead of +exiting normally, the server should terminate the session with +SSH_MSG_DISCONNECT (which can be used to pass a human-readable string +indicating that the program died due to a signal) instead of using +this message. +.IP "21 SSH_MSG_CHANNEL_OPEN_CONFIRMATION" +.TS +; +l l. +32-bit int remote_channel +32-bit int local_channel +.TE +This is sent in response to any channel open request if the channel +has been successfully opened. Remote_channel is the channel number +received in the initial open request; local_channel is the channel +number the side sending this message has allocated for the channel. +Data can be transmitted on the channel after this message. +.IP "22 SSH_MSG_CHANNEL_OPEN_FAILURE" +.TS +; +l l. +32-bit int remote_channel +.TE +This message indicates that an earlier channel open request by the +other side has failed or has been denied. Remote_channel is the +channel number given in the original request. +.IP "23 SSH_MSG_CHANNEL_DATA" +.TS +; +l l. +32-bit int remote_channel +string data +.TE +Data is transmitted in a channel in these messages. A channel is +bidirectional, and both sides can send these messages. There is no +acknowledgement for these messages. It is possible that either side +receives these messages after it has sent SSH_MSG_CHANNEL_CLOSE for +the channel. These messages cannot be received after the party has +sent or received SSH_MSG_CHANNEL_CLOSE_CONFIRMATION. +.IP "24 SSH_MSG_CHANNEL_CLOSE" +.TS +; +l l. +32-bit int remote_channel +.TE +When a channel is closed at one end of the connection, that side sends +this message. Upon receiving this message, the channel should be +closed. When this message is received, if the channel is already +closed (the receiving side has sent this message for the same channel +earlier), the channel is freed and no further action is taken; +otherwise the channel is freed and SSH_MSG_CHANNEL_CLOSE_CONFIRMATION +is sent in response. (It is possible that the channel is closed +simultaneously at both ends.) +.IP "25 SSH_MSG_CHANNEL_CLOSE_CONFIRMATION" +.TS +; +l l. +32-bit int remote_channel +.TE +This message is sent in response to SSH_MSG_CHANNEL_CLOSE unless the +channel was already closed. When this message is sent or received, +the channel is freed. +.IP "26 (OBSOLETED; was unix-domain X11 forwarding) +.IP "27 SSH_SMSG_X11_OPEN" +.TS +; +l l. +32-bit int local_channel +string originator_string (see below) +.TE +This message can be sent by the server during the interactive session +mode to indicate that a client has connected the fake X server. +Local_channel is the channel number that the server has allocated for +the connection. The client should try to open a connection to the +real X server, and respond with SSH_MSG_CHANNEL_OPEN_CONFIRMATION or +SSH_MSG_CHANNEL_OPEN_FAILURE. + +The field originator_string is present if both sides +specified SSH_PROTOFLAG_HOST_IN_FWD_OPEN in the protocol flags. It +contains a description of the host originating the connection. +.IP "28 SSH_CMSG_PORT_FORWARD_REQUEST" +.TS +; +l l. +32-bit int server_port +string host_to_connect +32-bit int port_to_connect +.TE +Sent by the client in the preparatory phase, this message requests +that server_port on the server machine be forwarded over the secure +channel to the client machine, and from there to the specified host +and port. The server should start listening on the port, and send +SSH_MSG_PORT_OPEN whenever a connection is made to it. Supporting +this message is optional, and the server is free to reject any forward +request. For example, it is highly recommended that unless the user +has been authenticated as root, forwarding any privileged port numbers +(below 1024) is denied. +.IP "29 SSH_MSG_PORT_OPEN" +.TS +; +l l. +32-bit int local_channel +string host_name +32-bit int port +string originator_string (see below) +.TE +Sent by either party in interactive session mode, this message +indicates that a connection has been opened to a forwarded TCP/IP +port. Local_channel is the channel number that the sending party has +allocated for the connection. Host_name is the host the connection +should be be forwarded to, and the port is the port on that host to +connect. The receiving party should open the connection, and respond +with SSH_MSG_CHANNEL_OPEN_CONFIRMATION or +SSH_MSG_CHANNEL_OPEN_FAILURE. It is recommended that the receiving +side check the host_name and port for validity to avoid compromising +local security by compromised remote side software. Particularly, it +is recommended that the client permit connections only to those ports +for which it has requested forwarding with SSH_CMSG_PORT_FORWARD_REQUEST. + +The field originator_string is present if both sides +specified SSH_PROTOFLAG_HOST_IN_FWD_OPEN in the protocol flags. It +contains a description of the host originating the connection. +.IP "30 SSH_CMSG_AGENT_REQUEST_FORWARDING" + +(no arguments) + +Requests that the connection to the authentication agent be forwarded +over the secure channel. The method used by clients to contact the +authentication agent within each machine is implementation and machine +dependent. If the server accepts this request, it should arrange that +any clients run from this session will actually contact the server +program when they try to contact the authentication agent. The server +should then send a SSH_SMSG_AGENT_OPEN to open a channel to the agent, +and the client should forward the connection to the real +authentication agent. Supporting this message is optional. +.IP "31 SSH_SMSG_AGENT_OPEN" +.TS +; +l l. +32-bit int local_channel +.TE +Sent by the server in interactive session mode, this message requests +opening a channel to the authentication agent. The client should open +a channel, and respond with either SSH_MSG_CHANNEL_OPEN_CONFIRMATION +or SSH_MSG_CHANNEL_OPEN_FAILURE. +.IP "32 SSH_MSG_IGNORE" +.TS +; +l l. +string data +.TE +Either party may send this message at any time. This message, and the +argument string, is silently ignored. This message might be used in +some implementations to make traffic analysis more difficult. This +message is not currently sent by the implementation, but all +implementations are required to recognize and ignore it. +.IP "33 SSH_CMSG_EXIT_CONFIRMATION" + +(no arguments) + +Sent by the client in response to SSH_SMSG_EXITSTATUS. This is the +last message sent by the client. +.IP "34 SSH_CMSG_X11_REQUEST_FORWARDING" +.TS +; +l l. +string x11_authentication_protocol +string x11_authentication_data +32-bit int screen number (if SSH_PROTOFLAG_SCREEN_NUMBER) +.TE +Sent by the client during the preparatory phase, this message requests +that the server create a fake X11 display and set the DISPLAY +environment variable accordingly. An internet-domain display is +preferable. The given authentication protocol and the associated data +should be recorded by the server so that it is used as authentication +on connections (e.g., in .Xauthority). The authentication protocol +must be one of the supported X11 authentication protocols, e.g., +"MIT-MAGIC-COOKIE-1". Authentication data must be a lowercase hex +string of even length. Its interpretation is protocol dependent. +The data is in a format that can be used with e.g. the xauth program. +Supporting this message is optional. + +The client is permitted (and recommended) to generate fake +authentication information and send fake information to the server. +This way, a corrupt server will not have access to the user's terminal +after the connection has terminated. The correct authorization codes +will also not be left hanging around in files on the server (many +users keep the same X session for months, thus protecting the +authorization data becomes important). + +X11 authentication spoofing works by initially sending fake (random) +authentication data to the server, and interpreting the first packet +sent by the X11 client after the connection has been opened. The +first packet contains the client's authentication. If the packet +contains the correct fake data, it is replaced by the client by the +correct authentication data, and then sent to the X server. +.IP "35 SSH_CMSG_AUTH_RHOSTS_RSA" +.TS +; +l l. +string clint-side user name +32-bit int client_host_key_bits +mp-int client_host_key_public_exponent +mp-int client_host_key_public_modulus +.TE +Requests authentication using /etc/hosts.equiv and .rhosts (or +equivalent) together with RSA host authentication. The server should +check that the client side port number is less than 1024 (a privileged +port), and immediately reject authentication if it is not. The server +responds with SSH_SMSG_FAILURE or SSH_SMSG_AUTH_RSA_CHALLENGE. The +client must respond to the challenge with the proper +SSH_CMSG_AUTH_RSA_RESPONSE. The server then responds with success if +access was granted, or failure if the client gave a wrong response. +Supporting this authentication method is optional but recommended in +most environments. +.IP "36 SSH_MSG_DEBUG" +.TS +; +l l. +string debugging message sent to the other side +.TE +This message may be sent by either party at any time. It is used to +send debugging messages that may be informative to the user in +solving various problems. For example, if authentication fails +because of some configuration error (e.g., incorrect permissions for +some file), it can be very helpful for the user to make the cause of +failure available. On the other hand, one should not make too much +information available for security reasons. It is recommended that +the client provides an option to display the debugging information +sent by the sender (the user probably does not want to see it by default). +The server can log debugging data sent by the client (if any). Either +party is free to ignore any received debugging data. Every +implementation must be able to receive this message, but no +implementation is required to send these. +.IP "37 SSH_CMSG_REQUEST_COMPRESSION" +.TS +; +l l. +32-bit int gzip compression level (1-9) +.TE +This message can be sent by the client in the preparatory operations +phase. The server responds with SSH_SMSG_FAILURE if it does not +support compression or does not want to compress; it responds with +SSH_SMSG_SUCCESS if it accepted the compression request. In the +latter case the response to this packet will still be uncompressed, +but all further packets in either direction will be compressed by gzip. +.RT + + +.ti 0 +Encoding of Terminal Modes + +Terminal modes (as passed in SSH_CMSG_REQUEST_PTY) are encoded into a +byte stream. It is intended that the coding be portable across +different environments. + +The tty mode description is a stream of bytes. The stream consists of +opcode-argument pairs. It is terminated by opcode TTY_OP_END (0). +Opcodes 1-127 have one-byte arguments. Opcodes 128-159 have 32-bit +integer arguments (stored msb first). Opcodes 160-255 are not yet +defined, and cause parsing to stop (they should only be used after any +other data). + +The client puts in the stream any modes it knows about, and the server +ignores any modes it does not know about. This allows some degree of +machine-independence, at least between systems that use a POSIX-like +[POSIX] tty interface. The protocol can support other systems as +well, but the client may need to fill reasonable values for a number +of parameters so the server pty gets set to a reasonable mode (the +server leaves all unspecified mode bits in their default values, and +only some combinations make sense). + +The following opcodes have been defined. The naming of opcodes mostly +follows the POSIX terminal mode flags. +.IP "0 TTY_OP_END" +Indicates end of options. +.IP "1 VINTR" +Interrupt character; 255 if none. Similarly for the other characters. +Not all of these characters are supported on all systems. +.IP "2 VQUIT" +The quit character (sends SIGQUIT signal on UNIX systems). +.IP "3 VERASE" +Erase the character to left of the cursor. +.IP "4 VKILL" +Kill the current input line. +.IP "5 VEOF " +End-of-file character (sends EOF from the terminal). +.IP "6 VEOL " +End-of-line character in addition to carriage return and/or linefeed. +.IP "7 VEOL2" +Additional end-of-line character. +.IP "8 VSTART" +Continues paused output (normally ^Q). +.IP "9 VSTOP" +Pauses output (^S). +.IP "10 VSUSP" +Suspends the current program. +.IP "11 VDSUSP" +Another suspend character. +.IP "12 VREPRINT" +Reprints the current input line. +.IP "13 VWERASE" +Erases a word left of cursor. +.IP "14 VLNEXT" +More special input characters; these are probably not supported on +most systems. +.IP "15 VFLUSH" +.IP "16 VSWTCH" +.IP "17 VSTATUS" +.IP "18 VDISCARD" + +.IP "30 IGNPAR" +The ignore parity flag. The next byte should be 0 if this flag is not +set, and 1 if it is set. +.IP "31 PARMRK" +More flags. The exact definitions can be found in the POSIX standard. +.IP "32 INPCK" +.IP "33 ISTRIP" +.IP "34 INLCR" +.IP "35 IGNCR" +.IP "36 ICRNL" +.IP "37 IUCLC" +.IP "38 IXON" +.IP "39 IXANY" +.IP "40 IXOFF" +.IP "41 IMAXBEL" + +.IP "50 ISIG" +.IP "51 ICANON" +.IP "52 XCASE" +.IP "53 ECHO" +.IP "54 ECHOE" +.IP "55 ECHOK" +.IP "56 ECHONL" +.IP "57 NOFLSH" +.IP "58 TOSTOP" +.IP "59 IEXTEN" +.IP "60 ECHOCTL" +.IP "61 ECHOKE" +.IP "62 PENDIN" + +.IP "70 OPOST" +.IP "71 OLCUC" +.IP "72 ONLCR" +.IP "73 OCRNL" +.IP "74 ONOCR" +.IP "75 ONLRET" + +.IP "90 CS7" +.IP "91 CS8" +.IP "92 PARENB" +.IP "93 PARODD" + +.IP "192 TTY_OP_ISPEED" +Specifies the input baud rate in bits per second. +.IP "193 TTY_OP_OSPEED" +Specifies the output baud rate in bits per second. +.RT + + +.ti 0 +The Authentication Agent Protocol + +The authentication agent is a program that can be used to hold RSA +authentication keys for the user (in future, it might hold data for +other authentication types as well). An authorized program can send +requests to the agent to generate a proper response to an RSA +challenge. How the connection is made to the agent (or its +representative) inside a host and how access control is done inside a +host is implementation-dependent; however, how it is forwarded and how +one interacts with it is specified in this protocol. The connection +to the agent is normally automatically forwarded over the secure +channel. + +A program that wishes to use the agent first opens a connection to its +local representative (typically, the agent itself or an SSH server). +It then writes a request to the connection, and waits for response. +It is recommended that at least five minutes of timeout are provided +waiting for the agent to respond to an authentication challenge (this +gives sufficient time for the user to cut-and-paste the challenge to a +separate machine, perform the computation there, and cut-and-paste the +result back if so desired). + +Messages sent to and by the agent are in the following format: +.TS +; +l l. +4 bytes Length, msb first. Does not include length itself. +1 byte Packet type. The value 255 is reserved for future extensions. +data Any data, depending on packet type. Encoding as in the ssh packet +protocol. +.TE + +The following message types are currently defined: +.IP "1 SSH_AGENTC_REQUEST_RSA_IDENTITIES" + +(no arguments) + +Requests the agent to send a list of all RSA keys for which it can +answer a challenge. +.IP "2 SSH_AGENT_RSA_IDENTITIES_ANSWER" +.TS +; +l l. +32-bit int howmany +howmany times: +32-bit int bits +mp-int public exponent +mp-int public modulus +string comment +.TE +The agent sends this message in response to the to +SSH_AGENTC_REQUEST_RSA_IDENTITIES. The answer lists all RSA keys for +which the agent can answer a challenge. The comment field is intended +to help identify each key; it may be printed by an application to +indicate which key is being used. If the agent is not holding any +keys, howmany will be zero. +.IP "3 SSH_AGENTC_RSA_CHALLENGE +.TS +; +l l. +32-bit int bits +mp-int public exponent +mp-int public modulus +mp-int challenge +16 bytes session_id +32-bit int response_type +.TE +Requests RSA decryption of random challenge to authenticate the other +side. The challenge will be decrypted with the RSA private key +corresponding to the given public key. + +The decrypted challenge must contain a zero in the highest (partial) +byte, 2 in the next byte, followed by non-zero random bytes, a zero +byte, and then the real challenge value in the lowermost bytes. The +real challenge must be 32 8-bit bytes (256 bits). + +Response_type indicates the format of the response to be returned. +Currently the only supported value is 1, which means to compute MD5 of +the real challenge plus session id, and return the resulting 16 bytes +in a SSH_AGENT_RSA_RESPONSE message. +.IP "4 SSH_AGENT_RSA_RESPONSE" +.TS +; +l l. +16 bytes MD5 of decrypted challenge +.TE +Answers an RSA authentication challenge. The response is 16 bytes: +the MD5 checksum of the 32-byte challenge. +.IP "5 SSH_AGENT_FAILURE" + +(no arguments) + +This message is sent whenever the agent fails to answer a request +properly. For example, if the agent cannot answer a challenge (e.g., +no longer has the proper key), it can respond with this. The agent +also responds with this message if it receives a message it does not +recognize. +.IP "6 SSH_AGENT_SUCCESS" + +(no arguments) + +This message is sent by the agent as a response to certain requests +that do not otherwise cause a message be sent. Currently, this is +only sent in response to SSH_AGENTC_ADD_RSA_IDENTITY and +SSH_AGENTC_REMOVE_RSA_IDENTITY. +.IP "7 SSH_AGENTC_ADD_RSA_IDENTITY" +.TS +; +l l. +32-bit int bits +mp-int public modulus +mp-int public exponent +mp-int private exponent +mp-int multiplicative inverse of p mod q +mp-int p +mp-int q +string comment +.TE +Registers an RSA key with the agent. After this request, the agent can +use this RSA key to answer requests. The agent responds with +SSH_AGENT_SUCCESS or SSH_AGENT_FAILURE. +.IP "8 SSH_AGENT_REMOVE_RSA_IDENTITY" +.TS +; +l l. +32-bit int bits +mp-int public exponent +mp-int public modulus +.TE +Removes an RSA key from the agent. The agent will no longer accept +challenges for this key and will not list it as a supported identity. +The agent responds with SSH_AGENT_SUCCESS or SSH_AGENT_FAILURE. +.RT + +If the agent receives a message that it does not understand, it +responds with SSH_AGENT_FAILURE. This permits compatible future +extensions. + +It is possible that several clients have a connection open to the +authentication agent simultaneously. Each client will use a separate +connection (thus, any SSH connection can have multiple agent +connections active simultaneously). + + +.ti 0 +References + +.IP "[DES] " +FIPS PUB 46-1: Data Encryption Standard. National Bureau of +Standards, January 1988. FIPS PUB 81: DES Modes of Operation. +National Bureau of Standards, December 1980. Bruce Schneier: Applied +Cryptography. John Wiley & Sons, 1994. J. Seberry and J. Pieprzyk: +Cryptography: An Introduction to Computer Security. Prentice-Hall, +1989. +.IP "[GZIP] " +The GNU GZIP program; available for anonymous ftp at prep.ai.mit.edu. +Please let me know if you know a paper describing the algorithm. +.IP "[IDEA] " +Xuejia Lai: On the Design and Security of Block Ciphers, ETH Series in +Information Processing, vol. 1, Hartung-Gorre Verlag, Konstanz, +Switzerland, 1992. Bruce Schneier: Applied Cryptography, John Wiley & +Sons, 1994. See also the following patents: PCT/CH91/00117, EP 0 482 +154 B1, US Pat. 5,214,703. +.IP [PKCS#1] +PKCS #1: RSA Encryption Standard. Version 1.5, RSA Laboratories, +November 1993. Available for anonymous ftp at ftp.rsa.com. +.IP [POSIX] +Portable Operating System Interface (POSIX) - Part 1: Application +Program Interface (API) [C language], ISO/IEC 9945-1, IEEE Std 1003.1, +1990. +.IP [RFC0791] +J. Postel: Internet Protocol, RFC 791, USC/ISI, September 1981. +.IP [RFC0793] +J. Postel: Transmission Control Protocol, RFC 793, USC/ISI, September +1981. +.IP [RFC1034] +P. Mockapetris: Domain Names - Concepts and Facilities, RFC 1034, +USC/ISI, November 1987. +.IP [RFC1282] +B. Kantor: BSD Rlogin, RFC 1258, UCSD, December 1991. +.IP "[RSA] " +Bruce Schneier: Applied Cryptography. John Wiley & Sons, 1994. See +also R. Rivest, A. Shamir, and L. M. Adleman: Cryptographic +Communications System and Method. US Patent 4,405,829, 1983. +.IP "[X11] " +R. Scheifler: X Window System Protocol, X Consortium Standard, Version +11, Release 6. Massachusetts Institute of Technology, Laboratory of +Computer Science, 1994. +.RT + + +.ti 0 +Security Considerations + +This protocol deals with the very issue of user authentication and +security. + +First of all, as an implementation issue, the server program will have +to run as root (or equivalent) on the server machine. This is because +the server program will need be able to change to an arbitrary user +id. The server must also be able to create a privileged TCP/IP port. + +The client program will need to run as root if any variant of .rhosts +authentication is to be used. This is because the client program will +need to create a privileged port. The client host key is also usually +stored in a file which is readable by root only. The client needs the +host key in .rhosts authentication only. Root privileges can be +dropped as soon as the privileged port has been created and the host +key has been read. + +The SSH protocol offers major security advantages over existing telnet +and rlogin protocols. +.IP o +IP spoofing is restricted to closing a connection (by encryption, host +keys, and the special random cookie). If encryption is not used, IP +spoofing is possible for those who can hear packets going out from the +server. +.IP o +DNS spoofing is made ineffective (by host keys). +.IP o +Routing spoofing is made ineffective (by host keys). +.IP o +All data is encrypted with strong algorithms to make eavesdropping as +difficult as possible. This includes encrypting any authentication +information such as passwords. The information for decrypting session +keys is destroyed every hour. +.IP o +Strong authentication methods: .rhosts combined with RSA host +authentication, and pure RSA authentication. +.IP o +X11 connections and arbitrary TCP/IP ports can be forwarded securely. +.IP o +Man-in-the-middle attacks are deterred by using the server host key to +encrypt the session key. +.IP o +Trojan horses to catch a password by routing manipulation are deterred +by checking that the host key of the server machine matches that +stored on the client host. +.RT + +The security of SSH against man-in-the-middle attacks and the security +of the new form of .rhosts authentication, as well as server host +validation, depends on the integrity of the host key and the files +containing known host keys. + +The host key is normally stored in a root-readable file. If the host +key is compromised, it permits attackers to use IP, DNS and routing +spoofing as with current rlogin and rsh. It should never be any worse +than the current situation. + +The files containing known host keys are not sensitive. However, if an +attacker gets to modify the known host key files, it has the same +consequences as a compromised host key, because the attacker can then +change the recorded host key. + +The security improvements obtained by this protocol for X11 are of +particular significance. Previously, there has been no way to protect +data communicated between an X server and a client running on a remote +machine. By creating a fake display on the server, and forwarding all +X11 requests over the secure channel, SSH can be used to run any X11 +applications securely without any cooperation with the vendors of the +X server or the application. + +Finally, the security of this program relies on the strength of the +underlying cryptographic algorithms. The RSA algorithm is used for +authentication key exchange. It is widely believed to be secure. Of +the algorithms used to encrypt the session, DES has a rather small key +these days, probably permitting governments and organized criminals to +break it in very short time with specialized hardware. 3DES is +probably safe (but slower). IDEA is widely believed to be secure. +People have varying degrees of confidence in the other algorithms. +This program is not secure if used with no encryption at all. + + +.ti 0 +Additional Information + +Additional information (especially on the implementation and mailing +lists) is available via WWW at http://www.cs.hut.fi/ssh. + +Comments should be sent to Tatu Ylonen <ylo@cs.hut.fi> or the SSH +Mailing List <ssh@clinet.fi>. + +.ti 0 +Author's Address + +.TS +; +l. +Tatu Ylonen +Helsinki University of Technology +Otakaari 1 +FIN-02150 Espoo, Finland + +Phone: +358-0-451-3374 +Fax: +358-0-451-3293 +EMail: ylo@cs.hut.fi +.TE diff --git a/auth-krb4.c b/auth-krb4.c new file mode 100644 index 00000000..720f3a4c --- /dev/null +++ b/auth-krb4.c @@ -0,0 +1,209 @@ +/* + + auth-kerberos.c + + Dug Song <dugsong@UMICH.EDU> + + Kerberos v4 authentication and ticket-passing routines. + + $Id: auth-krb4.c,v 1.1 1999/10/27 03:42:43 damien Exp $ +*/ + +#include "includes.h" +#include "packet.h" +#include "xmalloc.h" +#include "ssh.h" + +#ifdef KRB4 +int ssh_tf_init(uid_t uid) +{ + extern char *ticket; + char *tkt_root = TKT_ROOT; + struct stat st; + int fd; + + /* Set unique ticket string manually since we're still root. */ + ticket = xmalloc(MAXPATHLEN); +#ifdef AFS + if (lstat("/ticket", &st) != -1) + tkt_root = "/ticket/"; +#endif /* AFS */ + snprintf(ticket, MAXPATHLEN, "%s%d_%d", tkt_root, uid, getpid()); + (void) krb_set_tkt_string(ticket); + + /* Make sure we own this ticket file, and we created it. */ + if (lstat(ticket, &st) == -1 && errno == ENOENT) { + /* good, no ticket file exists. create it. */ + if ((fd = open(ticket, O_RDWR|O_CREAT|O_EXCL, 0600)) != -1) { + close(fd); + return 1; + } + } + else { + /* file exists. make sure server_user owns it (e.g. just passed ticket), + and that it isn't a symlink, and that it is mode 600. */ + if (st.st_mode == (S_IFREG|S_IRUSR|S_IWUSR) && st.st_uid == uid) + return 1; + } + /* Failure. */ + log("WARNING: bad ticket file %s", ticket); + return 0; +} + +int auth_krb4(const char *server_user, KTEXT auth, char **client) +{ + AUTH_DAT adat = { 0 }; + KTEXT_ST reply; + char instance[INST_SZ]; + int r, s; + u_int cksum; + Key_schedule schedule; + struct sockaddr_in local, foreign; + + s = packet_get_connection_in(); + + r = sizeof(local); + memset(&local, 0, sizeof(local)); + if (getsockname(s, (struct sockaddr *) &local, &r) < 0) + debug("getsockname failed: %.100s", strerror(errno)); + r = sizeof(foreign); + memset(&foreign, 0, sizeof(foreign)); + if (getpeername(s, (struct sockaddr *)&foreign, &r) < 0) + debug("getpeername failed: %.100s", strerror(errno)); + + instance[0] = '*'; instance[1] = 0; + + /* Get the encrypted request, challenge, and session key. */ + if ((r = krb_rd_req(auth, KRB4_SERVICE_NAME, instance, 0, &adat, ""))) { + packet_send_debug("Kerberos V4 krb_rd_req: %.100s", krb_err_txt[r]); + return 0; + } + des_key_sched((des_cblock *)adat.session, schedule); + + *client = xmalloc(MAX_K_NAME_SZ); + (void) snprintf(*client, MAX_K_NAME_SZ, "%s%s%s@%s", adat.pname, + *adat.pinst ? "." : "", adat.pinst, adat.prealm); + + /* Check ~/.klogin authorization now. */ + if (kuserok(&adat, (char *)server_user) != KSUCCESS) { + packet_send_debug("Kerberos V4 .klogin authorization failed!"); + log("Kerberos V4 .klogin authorization failed for %s to account %s", + *client, server_user); + return 0; + } + /* Increment the checksum, and return it encrypted with the session key. */ + cksum = adat.checksum + 1; + cksum = htonl(cksum); + + /* If we can't successfully encrypt the checksum, we send back an empty + message, admitting our failure. */ + if ((r = krb_mk_priv((u_char *)&cksum, reply.dat, sizeof(cksum)+1, + schedule, &adat.session, &local, &foreign)) < 0) { + packet_send_debug("Kerberos V4 mk_priv: (%d) %s", r, krb_err_txt[r]); + reply.dat[0] = 0; + reply.length = 0; + } + else + reply.length = r; + + /* Clear session key. */ + memset(&adat.session, 0, sizeof(&adat.session)); + + packet_start(SSH_SMSG_AUTH_KERBEROS_RESPONSE); + packet_put_string((char *) reply.dat, reply.length); + packet_send(); + packet_write_wait(); + return 1; +} +#endif /* KRB4 */ + +#ifdef AFS +int auth_kerberos_tgt(struct passwd *pw, const char *string) +{ + CREDENTIALS creds; + extern char *ticket; + int r; + + if (!radix_to_creds(string, &creds)) { + log("Protocol error decoding Kerberos V4 tgt"); + packet_send_debug("Protocol error decoding Kerberos V4 tgt"); + goto auth_kerberos_tgt_failure; + } + if (strncmp(creds.service, "", 1) == 0) /* backward compatibility */ + strlcpy(creds.service, "krbtgt", sizeof creds.service); + + if (strcmp(creds.service, "krbtgt")) { + log("Kerberos V4 tgt (%s%s%s@%s) rejected for uid %d", + creds.pname, creds.pinst[0] ? "." : "", creds.pinst, creds.realm, + pw->pw_uid); + packet_send_debug("Kerberos V4 tgt (%s%s%s@%s) rejected for uid %d", + creds.pname, creds.pinst[0] ? "." : "", creds.pinst, + creds.realm, pw->pw_uid); + goto auth_kerberos_tgt_failure; + } + if (!ssh_tf_init(pw->pw_uid) || + (r = in_tkt(creds.pname, creds.pinst)) || + (r = save_credentials(creds.service, creds.instance, creds.realm, + creds.session, creds.lifetime, creds.kvno, + &creds.ticket_st, creds.issue_date))) { + xfree(ticket); + ticket = NULL; + packet_send_debug("Kerberos V4 tgt refused: couldn't save credentials"); + goto auth_kerberos_tgt_failure; + } + /* Successful authentication, passed all checks. */ + chown(ticket, pw->pw_uid, pw->pw_gid); + packet_send_debug("Kerberos V4 tgt accepted (%s.%s@%s, %s%s%s@%s)", + creds.service, creds.instance, creds.realm, + creds.pname, creds.pinst[0] ? "." : "", + creds.pinst, creds.realm); + + packet_start(SSH_SMSG_SUCCESS); + packet_send(); + packet_write_wait(); + return 1; + +auth_kerberos_tgt_failure: + memset(&creds, 0, sizeof(creds)); + packet_start(SSH_SMSG_FAILURE); + packet_send(); + packet_write_wait(); + return 0; +} + +int auth_afs_token(char *server_user, uid_t uid, const char *string) +{ + CREDENTIALS creds; + + if (!radix_to_creds(string, &creds)) { + log("Protocol error decoding AFS token"); + packet_send_debug("Protocol error decoding AFS token"); + packet_start(SSH_SMSG_FAILURE); + packet_send(); + packet_write_wait(); + return 0; + } + if (strncmp(creds.service, "", 1) == 0) /* backward compatibility */ + strlcpy(creds.service, "afs", sizeof creds.service); + + if (strncmp(creds.pname, "AFS ID ", 7) == 0) + uid = atoi(creds.pname + 7); + + if (kafs_settoken(creds.realm, uid, &creds)) { + log("AFS token (%s@%s) rejected for uid %d", creds.pname, + creds.realm, uid); + packet_send_debug("AFS token (%s@%s) rejected for uid %d", creds.pname, + creds.realm, uid); + packet_start(SSH_SMSG_FAILURE); + packet_send(); + packet_write_wait(); + return 0; + } + packet_send_debug("AFS token accepted (%s@%s, %s@%s)", creds.service, + creds.realm, creds.pname, creds.realm); + packet_start(SSH_SMSG_SUCCESS); + packet_send(); + packet_write_wait(); + return 1; +} +#endif /* AFS */ diff --git a/auth-passwd.c b/auth-passwd.c new file mode 100644 index 00000000..7d684678 --- /dev/null +++ b/auth-passwd.c @@ -0,0 +1,209 @@ +/* + +auth-passwd.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sat Mar 18 05:11:38 1995 ylo + +Password authentication. This file contains the functions to check whether +the password is valid for the user. + +*/ + +#include "includes.h" +RCSID("$Id: auth-passwd.c,v 1.1 1999/10/27 03:42:43 damien Exp $"); + +#include "packet.h" +#include "ssh.h" +#include "servconf.h" +#include "xmalloc.h" + +#ifdef KRB4 +extern char *ticket; +#endif /* KRB4 */ + +#ifdef HAVE_PAM +#include <security/pam_appl.h> +extern pam_handle_t *pamh; +extern int retval; +extern char* pampasswd; +extern int origretval; +#endif /* HAVE_PAM */ + +/* Tries to authenticate the user using password. Returns true if + authentication succeeds. */ + +int auth_password(struct passwd *pw, const char *password) +{ + extern ServerOptions options; + char *encrypted_password; + + if (pw->pw_uid == 0 && options.permit_root_login == 2) + { + /*packet_send_debug("Server does not permit root login with password.");*/ + return 0; + } + + if (*password == '\0' && options.permit_empty_passwd == 0) + { + /*packet_send_debug("Server does not permit empty password login.");*/ + return 0; + } + + /* deny if no user. */ + if (pw == NULL) + return 0; + +#ifdef HAVE_PAM + retval = origretval; + + pampasswd = xstrdup(password); + + if (retval == PAM_SUCCESS) + retval = pam_authenticate ((pam_handle_t *)pamh, 0); + + if (retval == PAM_SUCCESS) + retval = pam_acct_mgmt ((pam_handle_t *)pamh, 0); + + xfree(pampasswd); + + if (retval == PAM_SUCCESS) + retval = pam_open_session ((pam_handle_t *)pamh, 0); + + return (retval == PAM_SUCCESS); + +#else /* HAVE_PAM */ + +#ifdef SKEY + if (options.skey_authentication == 1) { + if (strncasecmp(password, "s/key", 5) == 0) { + char *skeyinfo = skey_keyinfo(pw->pw_name); + if(skeyinfo == NULL){ + debug("generating fake skeyinfo for %.100s.", pw->pw_name); + skeyinfo = skey_fake_keyinfo(pw->pw_name); + } + if(skeyinfo != NULL) + packet_send_debug(skeyinfo); + /* Try again. */ + return 0; + } + else if (skey_haskey(pw->pw_name) == 0 && + skey_passcheck(pw->pw_name, (char *)password) != -1) { + /* Authentication succeeded. */ + return 1; + } + /* Fall back to ordinary passwd authentication. */ + } +#endif + +#if defined(KRB4) + /* Support for Kerberos v4 authentication - Dug Song <dugsong@UMICH.EDU> */ + if (options.kerberos_authentication) + { + AUTH_DAT adata; + KTEXT_ST tkt; + struct hostent *hp; + unsigned long faddr; + char localhost[MAXHOSTNAMELEN]; /* local host name */ + char phost[INST_SZ]; /* host instance */ + char realm[REALM_SZ]; /* local Kerberos realm */ + int r; + + /* Try Kerberos password authentication only for non-root + users and only if Kerberos is installed. */ + if (pw->pw_uid != 0 && krb_get_lrealm(realm, 1) == KSUCCESS) { + + /* Set up our ticket file. */ + if (!ssh_tf_init(pw->pw_uid)) { + log("Couldn't initialize Kerberos ticket file for %s!", + pw->pw_name); + goto kerberos_auth_failure; + } + /* Try to get TGT using our password. */ + r = krb_get_pw_in_tkt((char *)pw->pw_name, "", realm, "krbtgt", realm, + DEFAULT_TKT_LIFE, (char *)password); + if (r != INTK_OK) { + packet_send_debug("Kerberos V4 password authentication for %s " + "failed: %s", pw->pw_name, krb_err_txt[r]); + goto kerberos_auth_failure; + } + /* Successful authentication. */ + chown(ticket, pw->pw_uid, pw->pw_gid); + + (void) gethostname(localhost, sizeof(localhost)); + (void) strlcpy(phost, (char *)krb_get_phost(localhost), INST_SZ); + + /* Now that we have a TGT, try to get a local "rcmd" ticket to + ensure that we are not talking to a bogus Kerberos server. */ + r = krb_mk_req(&tkt, KRB4_SERVICE_NAME, phost, realm, 33); + + if (r == KSUCCESS) { + if (!(hp = gethostbyname(localhost))) { + log("Couldn't get local host address!"); + goto kerberos_auth_failure; + } + memmove((void *)&faddr, (void *)hp->h_addr, sizeof(faddr)); + + /* Verify our "rcmd" ticket. */ + r = krb_rd_req(&tkt, KRB4_SERVICE_NAME, phost, faddr, &adata, ""); + if (r == RD_AP_UNDEC) { + /* Probably didn't have a srvtab on localhost. Allow login. */ + log("Kerberos V4 TGT for %s unverifiable, no srvtab installed? " + "krb_rd_req: %s", pw->pw_name, krb_err_txt[r]); + } + else if (r != KSUCCESS) { + log("Kerberos V4 %s ticket unverifiable: %s", + KRB4_SERVICE_NAME, krb_err_txt[r]); + goto kerberos_auth_failure; + } + } + else if (r == KDC_PR_UNKNOWN) { + /* Allow login if no rcmd service exists, but log the error. */ + log("Kerberos V4 TGT for %s unverifiable: %s; %s.%s " + "not registered, or srvtab is wrong?", pw->pw_name, + krb_err_txt[r], KRB4_SERVICE_NAME, phost); + } + else { + /* TGT is bad, forget it. Possibly spoofed! */ + packet_send_debug("WARNING: Kerberos V4 TGT possibly spoofed for" + "%s: %s", pw->pw_name, krb_err_txt[r]); + goto kerberos_auth_failure; + } + + /* Authentication succeeded. */ + return 1; + + kerberos_auth_failure: + (void) dest_tkt(); + xfree(ticket); + ticket = NULL; + if (!options.kerberos_or_local_passwd ) return 0; + } + else { + /* Logging in as root or no local Kerberos realm. */ + packet_send_debug("Unable to authenticate to Kerberos."); + } + /* Fall back to ordinary passwd authentication. */ + } +#endif /* KRB4 */ + + /* Check for users with no password. */ + if (strcmp(password, "") == 0 && strcmp(pw->pw_passwd, "") == 0) + { + packet_send_debug("Login permitted without a password because the account has no password."); + return 1; /* The user has no password and an empty password was tried. */ + } + + /* Encrypt the candidate password using the proper salt. */ + encrypted_password = crypt(password, + (pw->pw_passwd[0] && pw->pw_passwd[1]) ? + pw->pw_passwd : "xx"); + + /* Authentication is accepted if the encrypted passwords are identical. */ + return (strcmp(encrypted_password, pw->pw_passwd) == 0); +#endif /* HAVE_PAM */ +} diff --git a/auth-rh-rsa.c b/auth-rh-rsa.c new file mode 100644 index 00000000..c433578b --- /dev/null +++ b/auth-rh-rsa.c @@ -0,0 +1,83 @@ +/* + +auth-rh-rsa.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sun May 7 03:08:06 1995 ylo + +Rhosts or /etc/hosts.equiv authentication combined with RSA host +authentication. + +*/ + +#include "includes.h" +RCSID("$Id: auth-rh-rsa.c,v 1.1 1999/10/27 03:42:43 damien Exp $"); + +#include "packet.h" +#include "ssh.h" +#include "xmalloc.h" +#include "uidswap.h" + +/* Tries to authenticate the user using the .rhosts file and the host using + its host key. Returns true if authentication succeeds. + .rhosts and .shosts will be ignored if ignore_rhosts is non-zero. */ + +int auth_rhosts_rsa(struct passwd *pw, const char *client_user, + unsigned int client_host_key_bits, + BIGNUM *client_host_key_e, BIGNUM *client_host_key_n, + int ignore_rhosts, int strict_modes) +{ + const char *canonical_hostname; + HostStatus host_status; + BIGNUM *ke, *kn; + + debug("Trying rhosts with RSA host authentication for %.100s", client_user); + + /* Check if we would accept it using rhosts authentication. */ + if (!auth_rhosts(pw, client_user, ignore_rhosts, strict_modes)) + return 0; + + canonical_hostname = get_canonical_hostname(); + + debug("Rhosts RSA authentication: canonical host %.900s", + canonical_hostname); + + /* Check if we know the host and its host key. */ + /* Check system-wide host file. */ + ke = BN_new(); + kn = BN_new(); + host_status = check_host_in_hostfile(SSH_SYSTEM_HOSTFILE, canonical_hostname, + client_host_key_bits, client_host_key_e, + client_host_key_n, ke, kn); + BN_free(ke); + BN_free(kn); + if (host_status != HOST_OK) { + /* The host key was not found. */ + debug("Rhosts with RSA host authentication denied: unknown or invalid host key"); + packet_send_debug("Your host key cannot be verified: unknown or invalid host key."); + return 0; + } + + /* A matching host key was found and is known. */ + + /* Perform the challenge-response dialog with the client for the host key. */ + if (!auth_rsa_challenge_dialog(client_host_key_bits, + client_host_key_e, client_host_key_n)) + { + log("Client on %.800s failed to respond correctly to host authentication.", + canonical_hostname); + return 0; + } + + /* We have authenticated the user using .rhosts or /etc/hosts.equiv, and + the host using RSA. We accept the authentication. */ + + log("Rhosts with RSA host authentication accepted for %.100s, %.100s on %.700s.", + pw->pw_name, client_user, canonical_hostname); + packet_send_debug("Rhosts with RSA host authentication accepted."); + return 1; +} diff --git a/auth-rhosts.c b/auth-rhosts.c new file mode 100644 index 00000000..ebf2fcbc --- /dev/null +++ b/auth-rhosts.c @@ -0,0 +1,298 @@ +/* + +auth-rhosts.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Fri Mar 17 05:12:18 1995 ylo + +Rhosts authentication. This file contains code to check whether to admit +the login based on rhosts authentication. This file also processes +/etc/hosts.equiv. + +*/ + +#include "includes.h" +RCSID("$Id: auth-rhosts.c,v 1.1 1999/10/27 03:42:43 damien Exp $"); + +#include "packet.h" +#include "ssh.h" +#include "xmalloc.h" +#include "uidswap.h" + +/* This function processes an rhosts-style file (.rhosts, .shosts, or + /etc/hosts.equiv). This returns true if authentication can be granted + based on the file, and returns zero otherwise. */ + +int check_rhosts_file(const char *filename, const char *hostname, + const char *ipaddr, const char *client_user, + const char *server_user) +{ + FILE *f; + char buf[1024]; /* Must not be larger than host, user, dummy below. */ + + /* Open the .rhosts file. */ + f = fopen(filename, "r"); + if (!f) + return 0; /* Cannot read the .rhosts - deny access. */ + + /* Go through the file, checking every entry. */ + while (fgets(buf, sizeof(buf), f)) + { + /* All three must be at least as big as buf to avoid overflows. */ + char hostbuf[1024], userbuf[1024], dummy[1024], *host, *user, *cp; + int negated; + + for (cp = buf; *cp == ' ' || *cp == '\t'; cp++) + ; + if (*cp == '#' || *cp == '\n' || !*cp) + continue; + + /* NO_PLUS is supported at least on OSF/1. We skip it (we don't ever + support the plus syntax). */ + if (strncmp(cp, "NO_PLUS", 7) == 0) + continue; + + /* This should be safe because each buffer is as big as the whole + string, and thus cannot be overwritten. */ + switch (sscanf(buf, "%s %s %s", hostbuf, userbuf, dummy)) + { + case 0: + packet_send_debug("Found empty line in %.100s.", filename); + continue; /* Empty line? */ + case 1: + /* Host name only. */ + strlcpy(userbuf, server_user, sizeof(userbuf)); + break; + case 2: + /* Got both host and user name. */ + break; + case 3: + packet_send_debug("Found garbage in %.100s.", filename); + continue; /* Extra garbage */ + default: + continue; /* Weird... */ + } + + host = hostbuf; + user = userbuf; + negated = 0; + + /* Process negated host names, or positive netgroups. */ + if (host[0] == '-') + { + negated = 1; + host++; + } + else + if (host[0] == '+') + host++; + + if (user[0] == '-') + { + negated = 1; + user++; + } + else + if (user[0] == '+') + user++; + + /* Check for empty host/user names (particularly '+'). */ + if (!host[0] || !user[0]) + { + /* We come here if either was '+' or '-'. */ + packet_send_debug("Ignoring wild host/user names in %.100s.", + filename); + continue; + } + + /* Verify that host name matches. */ + if (host[0] == '@') + { + if (!innetgr(host + 1, hostname, NULL, NULL) && + !innetgr(host + 1, ipaddr, NULL, NULL)) + continue; + } + else + if (strcasecmp(host, hostname) && strcmp(host, ipaddr) != 0) + continue; /* Different hostname. */ + + /* Verify that user name matches. */ + if (user[0] == '@') + { + if (!innetgr(user + 1, NULL, client_user, NULL)) + continue; + } + else + if (strcmp(user, client_user) != 0) + continue; /* Different username. */ + + /* Found the user and host. */ + fclose(f); + + /* If the entry was negated, deny access. */ + if (negated) + { + packet_send_debug("Matched negative entry in %.100s.", + filename); + return 0; + } + + /* Accept authentication. */ + return 1; + } + + /* Authentication using this file denied. */ + fclose(f); + return 0; +} + +/* Tries to authenticate the user using the .shosts or .rhosts file. + Returns true if authentication succeeds. If ignore_rhosts is + true, only /etc/hosts.equiv will be considered (.rhosts and .shosts + are ignored). */ + +int auth_rhosts(struct passwd *pw, const char *client_user, + int ignore_rhosts, int strict_modes) +{ + char buf[1024]; + const char *hostname, *ipaddr; + int port; + struct stat st; + static const char *rhosts_files[] = { ".shosts", ".rhosts", NULL }; + unsigned int rhosts_file_index; + + /* Quick check: if the user has no .shosts or .rhosts files, return failure + immediately without doing costly lookups from name servers. */ + /* Switch to the user's uid. */ + temporarily_use_uid(pw->pw_uid); + for (rhosts_file_index = 0; rhosts_files[rhosts_file_index]; + rhosts_file_index++) + { + /* Check users .rhosts or .shosts. */ + snprintf(buf, sizeof buf, "%.500s/%.100s", + pw->pw_dir, rhosts_files[rhosts_file_index]); + if (stat(buf, &st) >= 0) + break; + } + /* Switch back to privileged uid. */ + restore_uid(); + + if (!rhosts_files[rhosts_file_index] && stat("/etc/hosts.equiv", &st) < 0 && + stat(SSH_HOSTS_EQUIV, &st) < 0) + return 0; /* The user has no .shosts or .rhosts file and there are no + system-wide files. */ + + /* Get the name, address, and port of the remote host. */ + hostname = get_canonical_hostname(); + ipaddr = get_remote_ipaddr(); + port = get_remote_port(); + + /* Check that the connection comes from a privileged port. + Rhosts authentication only makes sense for priviledged programs. + Of course, if the intruder has root access on his local machine, + he can connect from any port. So do not use .rhosts + authentication from machines that you do not trust. */ + if (port >= IPPORT_RESERVED || + port < IPPORT_RESERVED / 2) + { + log("Connection from %.100s from nonpriviledged port %d", + hostname, port); + packet_send_debug("Your ssh client is not running as root."); + return 0; + } + + /* If not logging in as superuser, try /etc/hosts.equiv and shosts.equiv. */ + if (pw->pw_uid != 0) + { + if (check_rhosts_file("/etc/hosts.equiv", hostname, ipaddr, client_user, + pw->pw_name)) + { + packet_send_debug("Accepted for %.100s [%.100s] by /etc/hosts.equiv.", + hostname, ipaddr); + return 1; + } + if (check_rhosts_file(SSH_HOSTS_EQUIV, hostname, ipaddr, client_user, + pw->pw_name)) + { + packet_send_debug("Accepted for %.100s [%.100s] by %.100s.", + hostname, ipaddr, SSH_HOSTS_EQUIV); + return 1; + } + } + + /* Check that the home directory is owned by root or the user, and is not + group or world writable. */ + if (stat(pw->pw_dir, &st) < 0) + { + log("Rhosts authentication refused for %.100: no home directory %.200s", + pw->pw_name, pw->pw_dir); + packet_send_debug("Rhosts authentication refused for %.100: no home directory %.200s", + pw->pw_name, pw->pw_dir); + return 0; + } + if (strict_modes && + ((st.st_uid != 0 && st.st_uid != pw->pw_uid) || + (st.st_mode & 022) != 0)) + { + log("Rhosts authentication refused for %.100s: bad ownership or modes for home directory.", + pw->pw_name); + packet_send_debug("Rhosts authentication refused for %.100s: bad ownership or modes for home directory.", + pw->pw_name); + return 0; + } + + /* Check all .rhosts files (currently .shosts and .rhosts). */ + /* Temporarily use the user's uid. */ + temporarily_use_uid(pw->pw_uid); + for (rhosts_file_index = 0; rhosts_files[rhosts_file_index]; + rhosts_file_index++) + { + /* Check users .rhosts or .shosts. */ + snprintf(buf, sizeof buf, "%.500s/%.100s", + pw->pw_dir, rhosts_files[rhosts_file_index]); + if (stat(buf, &st) < 0) + continue; /* No such file. */ + + /* Make sure that the file is either owned by the user or by root, + and make sure it is not writable by anyone but the owner. This is + to help avoid novices accidentally allowing access to their account + by anyone. */ + if (strict_modes && + ((st.st_uid != 0 && st.st_uid != pw->pw_uid) || + (st.st_mode & 022) != 0)) + { + log("Rhosts authentication refused for %.100s: bad modes for %.200s", + pw->pw_name, buf); + packet_send_debug("Bad file modes for %.200s", buf); + continue; + } + + /* Check if we have been configured to ignore .rhosts and .shosts + files. */ + if (ignore_rhosts) + { + packet_send_debug("Server has been configured to ignore %.100s.", + rhosts_files[rhosts_file_index]); + continue; + } + + /* Check if authentication is permitted by the file. */ + if (check_rhosts_file(buf, hostname, ipaddr, client_user, pw->pw_name)) + { + packet_send_debug("Accepted by %.100s.", + rhosts_files[rhosts_file_index]); + /* Restore the privileged uid. */ + restore_uid(); + return 1; + } + } + + /* Rhosts authentication denied. */ + /* Restore the privileged uid. */ + restore_uid(); + return 0; +} diff --git a/auth-rsa.c b/auth-rsa.c new file mode 100644 index 00000000..8de86d2d --- /dev/null +++ b/auth-rsa.c @@ -0,0 +1,478 @@ +/* + +auth-rsa.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Mon Mar 27 01:46:52 1995 ylo + +RSA-based authentication. This code determines whether to admit a login +based on RSA authentication. This file also contains functions to check +validity of the host key. + +*/ + +#include "includes.h" +RCSID("$Id: auth-rsa.c,v 1.1 1999/10/27 03:42:43 damien Exp $"); + +#include "rsa.h" +#include "packet.h" +#include "xmalloc.h" +#include "ssh.h" +#include "mpaux.h" +#include "uidswap.h" + +#include <openssl/rsa.h> +#include <openssl/md5.h> + +/* Flags that may be set in authorized_keys options. */ +extern int no_port_forwarding_flag; +extern int no_agent_forwarding_flag; +extern int no_x11_forwarding_flag; +extern int no_pty_flag; +extern char *forced_command; +extern struct envstring *custom_environment; + +/* Session identifier that is used to bind key exchange and authentication + responses to a particular session. */ +extern unsigned char session_id[16]; + +/* The .ssh/authorized_keys file contains public keys, one per line, in the + following format: + options bits e n comment + where bits, e and n are decimal numbers, + and comment is any string of characters up to newline. The maximum + length of a line is 8000 characters. See the documentation for a + description of the options. +*/ + +/* Performs the RSA authentication challenge-response dialog with the client, + and returns true (non-zero) if the client gave the correct answer to + our challenge; returns zero if the client gives a wrong answer. */ + +int +auth_rsa_challenge_dialog(unsigned int bits, BIGNUM *e, BIGNUM *n) +{ + BIGNUM *challenge, *encrypted_challenge, *aux; + RSA *pk; + BN_CTX *ctx = BN_CTX_new(); + unsigned char buf[32], mdbuf[16], response[16]; + MD5_CTX md; + unsigned int i; + int plen, len; + + encrypted_challenge = BN_new(); + challenge = BN_new(); + aux = BN_new(); + + /* Generate a random challenge. */ + BN_rand(challenge, 256, 0, 0); + BN_mod(challenge, challenge, n, ctx); + + /* Create the public key data structure. */ + pk = RSA_new(); + pk->e = BN_new(); + BN_copy(pk->e, e); + pk->n = BN_new(); + BN_copy(pk->n, n); + + /* Encrypt the challenge with the public key. */ + rsa_public_encrypt(encrypted_challenge, challenge, pk); + RSA_free(pk); + + /* Send the encrypted challenge to the client. */ + packet_start(SSH_SMSG_AUTH_RSA_CHALLENGE); + packet_put_bignum(encrypted_challenge); + packet_send(); + packet_write_wait(); + + /* The response is MD5 of decrypted challenge plus session id. */ + len = BN_num_bytes(challenge); + assert(len <= 32 && len); + memset(buf, 0, 32); + BN_bn2bin(challenge, buf + 32 - len); + MD5_Init(&md); + MD5_Update(&md, buf, 32); + MD5_Update(&md, session_id, 16); + MD5_Final(mdbuf, &md); + + /* We will no longer need these. */ + BN_clear_free(encrypted_challenge); + BN_clear_free(challenge); + BN_clear_free(aux); + BN_CTX_free(ctx); + + /* Wait for a response. */ + packet_read_expect(&plen, SSH_CMSG_AUTH_RSA_RESPONSE); + packet_integrity_check(plen, 16, SSH_CMSG_AUTH_RSA_RESPONSE); + for (i = 0; i < 16; i++) + response[i] = packet_get_char(); + + /* Verify that the response is the original challenge. */ + if (memcmp(response, mdbuf, 16) != 0) + { + /* Wrong answer. */ + return 0; + } + + /* Correct answer. */ + return 1; +} + +/* Performs the RSA authentication dialog with the client. This returns + 0 if the client could not be authenticated, and 1 if authentication was + successful. This may exit if there is a serious protocol violation. */ + +int +auth_rsa(struct passwd *pw, BIGNUM *client_n, int strict_modes) +{ + char line[8192]; + int authenticated; + unsigned int bits; + FILE *f; + unsigned long linenum = 0; + struct stat st; + BIGNUM *e, *n; + + /* Temporarily use the user's uid. */ + temporarily_use_uid(pw->pw_uid); + + /* The authorized keys. */ + snprintf(line, sizeof line, "%.500s/%.100s", pw->pw_dir, + SSH_USER_PERMITTED_KEYS); + + /* Fail quietly if file does not exist */ + if (stat(line, &st) < 0) + { + /* Restore the privileged uid. */ + restore_uid(); + return 0; + } + + /* Open the file containing the authorized keys. */ + f = fopen(line, "r"); + if (!f) + { + /* Restore the privileged uid. */ + restore_uid(); + packet_send_debug("Could not open %.900s for reading.", line); + packet_send_debug("If your home is on an NFS volume, it may need to be world-readable."); + return 0; + } + + if (strict_modes) { + int fail=0; + char buf[1024]; + /* Check open file in order to avoid open/stat races */ + if (fstat(fileno(f), &st) < 0 || + (st.st_uid != 0 && st.st_uid != pw->pw_uid) || + (st.st_mode & 022) != 0) { + snprintf(buf, sizeof buf, "RSA authentication refused for %.100s: " + "bad ownership or modes for '%s'.", pw->pw_name, line); + fail=1; + }else{ + /* Check path to SSH_USER_PERMITTED_KEYS */ + int i; + static const char *check[] = { + "", SSH_USER_DIR, NULL + }; + for (i=0; check[i]; i++) { + snprintf(line, sizeof line, "%.500s/%.100s", pw->pw_dir, check[i]); + if (stat(line, &st) < 0 || + (st.st_uid != 0 && st.st_uid != pw->pw_uid) || + (st.st_mode & 022) != 0) { + snprintf(buf, sizeof buf, "RSA authentication refused for %.100s: " + "bad ownership or modes for '%s'.", pw->pw_name, line); + fail=1; + break; + } + } + } + if (fail) { + log(buf); + packet_send_debug(buf); + restore_uid(); + return 0; + } + } + + /* Flag indicating whether authentication has succeeded. */ + authenticated = 0; + + /* Initialize mp-int variables. */ + e = BN_new(); + n = BN_new(); + + /* Go though the accepted keys, looking for the current key. If found, + perform a challenge-response dialog to verify that the user really has + the corresponding private key. */ + while (fgets(line, sizeof(line), f)) + { + char *cp; + char *options; + + linenum++; + + /* Skip leading whitespace. */ + for (cp = line; *cp == ' ' || *cp == '\t'; cp++) + ; + + /* Skip empty and comment lines. */ + if (!*cp || *cp == '\n' || *cp == '#') + continue; + + /* Check if there are options for this key, and if so, save their + starting address and skip the option part for now. If there are no + options, set the starting address to NULL. */ + if (*cp < '0' || *cp > '9') + { + int quoted = 0; + options = cp; + for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) + { + if (*cp == '\\' && cp[1] == '"') + cp++; /* Skip both */ + else + if (*cp == '"') + quoted = !quoted; + } + } + else + options = NULL; + + /* Parse the key from the line. */ + if (!auth_rsa_read_key(&cp, &bits, e, n)) + { + debug("%.100s, line %lu: bad key syntax", + SSH_USER_PERMITTED_KEYS, linenum); + packet_send_debug("%.100s, line %lu: bad key syntax", + SSH_USER_PERMITTED_KEYS, linenum); + continue; + } + /* cp now points to the comment part. */ + + /* Check if the we have found the desired key (identified by its + modulus). */ + if (BN_cmp(n, client_n) != 0) + continue; /* Wrong key. */ + + /* We have found the desired key. */ + + /* Perform the challenge-response dialog for this key. */ + if (!auth_rsa_challenge_dialog(bits, e, n)) + { + /* Wrong response. */ + log("Wrong response to RSA authentication challenge."); + packet_send_debug("Wrong response to RSA authentication challenge."); + continue; + } + + /* Correct response. The client has been successfully authenticated. + Note that we have not yet processed the options; this will be reset + if the options cause the authentication to be rejected. */ + authenticated = 1; + + /* RSA part of authentication was accepted. Now process the options. */ + if (options) + { + while (*options && *options != ' ' && *options != '\t') + { + cp = "no-port-forwarding"; + if (strncmp(options, cp, strlen(cp)) == 0) + { + packet_send_debug("Port forwarding disabled."); + no_port_forwarding_flag = 1; + options += strlen(cp); + goto next_option; + } + cp = "no-agent-forwarding"; + if (strncmp(options, cp, strlen(cp)) == 0) + { + packet_send_debug("Agent forwarding disabled."); + no_agent_forwarding_flag = 1; + options += strlen(cp); + goto next_option; + } + cp = "no-X11-forwarding"; + if (strncmp(options, cp, strlen(cp)) == 0) + { + packet_send_debug("X11 forwarding disabled."); + no_x11_forwarding_flag = 1; + options += strlen(cp); + goto next_option; + } + cp = "no-pty"; + if (strncmp(options, cp, strlen(cp)) == 0) + { + packet_send_debug("Pty allocation disabled."); + no_pty_flag = 1; + options += strlen(cp); + goto next_option; + } + cp = "command=\""; + if (strncmp(options, cp, strlen(cp)) == 0) + { + int i; + options += strlen(cp); + forced_command = xmalloc(strlen(options) + 1); + i = 0; + while (*options) + { + if (*options == '"') + break; + if (*options == '\\' && options[1] == '"') + { + options += 2; + forced_command[i++] = '"'; + continue; + } + forced_command[i++] = *options++; + } + if (!*options) + { + debug("%.100s, line %lu: missing end quote", + SSH_USER_PERMITTED_KEYS, linenum); + packet_send_debug("%.100s, line %lu: missing end quote", + SSH_USER_PERMITTED_KEYS, linenum); + continue; + } + forced_command[i] = 0; + packet_send_debug("Forced command: %.900s", forced_command); + options++; + goto next_option; + } + cp = "environment=\""; + if (strncmp(options, cp, strlen(cp)) == 0) + { + int i; + char *s; + struct envstring *new_envstring; + options += strlen(cp); + s = xmalloc(strlen(options) + 1); + i = 0; + while (*options) + { + if (*options == '"') + break; + if (*options == '\\' && options[1] == '"') + { + options += 2; + s[i++] = '"'; + continue; + } + s[i++] = *options++; + } + if (!*options) + { + debug("%.100s, line %lu: missing end quote", + SSH_USER_PERMITTED_KEYS, linenum); + packet_send_debug("%.100s, line %lu: missing end quote", + SSH_USER_PERMITTED_KEYS, linenum); + continue; + } + s[i] = 0; + packet_send_debug("Adding to environment: %.900s", s); + debug("Adding to environment: %.900s", s); + options++; + new_envstring = xmalloc(sizeof(struct envstring)); + new_envstring->s = s; + new_envstring->next = custom_environment; + custom_environment = new_envstring; + goto next_option; + } + cp = "from=\""; + if (strncmp(options, cp, strlen(cp)) == 0) + { + char *patterns = xmalloc(strlen(options) + 1); + int i; + options += strlen(cp); + i = 0; + while (*options) + { + if (*options == '"') + break; + if (*options == '\\' && options[1] == '"') + { + options += 2; + patterns[i++] = '"'; + continue; + } + patterns[i++] = *options++; + } + if (!*options) + { + debug("%.100s, line %lu: missing end quote", + SSH_USER_PERMITTED_KEYS, linenum); + packet_send_debug("%.100s, line %lu: missing end quote", + SSH_USER_PERMITTED_KEYS, linenum); + continue; + } + patterns[i] = 0; + options++; + if (!match_hostname(get_canonical_hostname(), patterns, + strlen(patterns)) && + !match_hostname(get_remote_ipaddr(), patterns, + strlen(patterns))) + { + log("RSA authentication tried for %.100s with correct key but not from a permitted host (host=%.200s, ip=%.200s).", + pw->pw_name, get_canonical_hostname(), + get_remote_ipaddr()); + packet_send_debug("Your host '%.200s' is not permitted to use this key for login.", + get_canonical_hostname()); + xfree(patterns); + authenticated = 0; + break; + } + xfree(patterns); + /* Host name matches. */ + goto next_option; + } + bad_option: + /* Unknown option. */ + log("Bad options in %.100s file, line %lu: %.50s", + SSH_USER_PERMITTED_KEYS, linenum, options); + packet_send_debug("Bad options in %.100s file, line %lu: %.50s", + SSH_USER_PERMITTED_KEYS, linenum, options); + authenticated = 0; + break; + + next_option: + /* Skip the comma, and move to the next option (or break out + if there are no more). */ + if (!*options) + fatal("Bugs in auth-rsa.c option processing."); + if (*options == ' ' || *options == '\t') + break; /* End of options. */ + if (*options != ',') + goto bad_option; + options++; + /* Process the next option. */ + continue; + } + } + + /* Break out of the loop if authentication was successful; otherwise + continue searching. */ + if (authenticated) + break; + } + + /* Restore the privileged uid. */ + restore_uid(); + + /* Close the file. */ + fclose(f); + + /* Clear any mp-int variables. */ + BN_clear_free(n); + BN_clear_free(e); + + if (authenticated) + packet_send_debug("RSA authentication accepted."); + + /* Return authentication result. */ + return authenticated; +} diff --git a/auth-skey.c b/auth-skey.c new file mode 100644 index 00000000..9ec17049 --- /dev/null +++ b/auth-skey.c @@ -0,0 +1,149 @@ +#include "includes.h" +RCSID("$Id: auth-skey.c,v 1.2 1999/10/16 20:57:52 deraadt Exp $"); + +#include "ssh.h" +#include <sha1.h> + +/* from %OpenBSD: skeylogin.c,v 1.32 1999/08/16 14:46:56 millert Exp % */ + + +#define ROUND(x) (((x)[0] << 24) + (((x)[1]) << 16) + (((x)[2]) << 8) + \ + ((x)[3])) + +/* + * hash_collapse() + */ +static u_int32_t +hash_collapse(s) + u_char *s; +{ + int len, target; + u_int32_t i; + + if ((strlen(s) % sizeof(u_int32_t)) == 0) + target = strlen(s); /* Multiple of 4 */ + else + target = strlen(s) - (strlen(s) % sizeof(u_int32_t)); + + for (i = 0, len = 0; len < target; len += 4) + i ^= ROUND(s + len); + + return i; +} +char * +skey_fake_keyinfo(char *username) +{ + int i; + u_int ptr; + u_char hseed[SKEY_MAX_SEED_LEN], flg = 1, *up; + char pbuf[SKEY_MAX_PW_LEN+1]; + static char skeyprompt[SKEY_MAX_CHALLENGE+1]; + char *secret = NULL; + size_t secretlen = 0; + SHA1_CTX ctx; + char *p, *u; + + /* + * Base first 4 chars of seed on hostname. + * Add some filler for short hostnames if necessary. + */ + if (gethostname(pbuf, sizeof(pbuf)) == -1) + *(p = pbuf) = '.'; + else + for (p = pbuf; *p && isalnum(*p); p++) + if (isalpha(*p) && isupper(*p)) + *p = tolower(*p); + if (*p && pbuf - p < 4) + (void)strncpy(p, "asjd", 4 - (pbuf - p)); + pbuf[4] = '\0'; + + /* Hash the username if possible */ + if ((up = SHA1Data(username, strlen(username), NULL)) != NULL) { + struct stat sb; + time_t t; + int fd; + + /* Collapse the hash */ + ptr = hash_collapse(up); + memset(up, 0, strlen(up)); + + /* See if the random file's there, else use ctime */ + if ((fd = open(_SKEY_RAND_FILE_PATH_, O_RDONLY)) != -1 + && fstat(fd, &sb) == 0 && + sb.st_size > (off_t)SKEY_MAX_SEED_LEN && + lseek(fd, ptr % (sb.st_size - SKEY_MAX_SEED_LEN), + SEEK_SET) != -1 && read(fd, hseed, + SKEY_MAX_SEED_LEN) == SKEY_MAX_SEED_LEN) { + close(fd); + secret = hseed; + secretlen = SKEY_MAX_SEED_LEN; + flg = 0; + } else if (!stat(_PATH_MEM, &sb) || !stat("/", &sb)) { + t = sb.st_ctime; + secret = ctime(&t); + secretlen = strlen(secret); + flg = 0; + } + } + + /* Put that in your pipe and smoke it */ + if (flg == 0) { + /* Hash secret value with username */ + SHA1Init(&ctx); + SHA1Update(&ctx, secret, secretlen); + SHA1Update(&ctx, username, strlen(username)); + SHA1End(&ctx, up); + + /* Zero out */ + memset(secret, 0, secretlen); + + /* Now hash the hash */ + SHA1Init(&ctx); + SHA1Update(&ctx, up, strlen(up)); + SHA1End(&ctx, up); + + ptr = hash_collapse(up + 4); + + for (i = 4; i < 9; i++) { + pbuf[i] = (ptr % 10) + '0'; + ptr /= 10; + } + pbuf[i] = '\0'; + + /* Sequence number */ + ptr = ((up[2] + up[3]) % 99) + 1; + + memset(up, 0, 20); /* SHA1 specific */ + free(up); + + (void)snprintf(skeyprompt, sizeof skeyprompt, + "otp-%.*s %d %.*s", + SKEY_MAX_HASHNAME_LEN, + skey_get_algorithm(), + ptr, SKEY_MAX_SEED_LEN, + pbuf); + } else { + /* Base last 8 chars of seed on username */ + u = username; + i = 8; + p = &pbuf[4]; + do { + if (*u == 0) { + /* Pad remainder with zeros */ + while (--i >= 0) + *p++ = '0'; + break; + } + + *p++ = (*u++ % 10) + '0'; + } while (--i != 0); + pbuf[12] = '\0'; + + (void)snprintf(skeyprompt, sizeof skeyprompt, + "otp-%.*s %d %.*s", + SKEY_MAX_HASHNAME_LEN, + skey_get_algorithm(), + 99, SKEY_MAX_SEED_LEN, pbuf); + } + return skeyprompt; +} diff --git a/authfd.c b/authfd.c new file mode 100644 index 00000000..07893caf --- /dev/null +++ b/authfd.c @@ -0,0 +1,565 @@ +/* + +authfd.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Wed Mar 29 01:30:28 1995 ylo + +Functions for connecting the local authentication agent. + +*/ + +#include "includes.h" +RCSID("$Id: authfd.c,v 1.1 1999/10/27 03:42:43 damien Exp $"); + +#include "ssh.h" +#include "rsa.h" +#include "authfd.h" +#include "buffer.h" +#include "bufaux.h" +#include "xmalloc.h" +#include "getput.h" + +#include <openssl/rsa.h> + +/* Returns the number of the authentication fd, or -1 if there is none. */ + +int +ssh_get_authentication_socket() +{ + const char *authsocket; + int sock; + struct sockaddr_un sunaddr; + + authsocket = getenv(SSH_AUTHSOCKET_ENV_NAME); + if (!authsocket) + return -1; + + sunaddr.sun_family = AF_UNIX; + strlcpy(sunaddr.sun_path, authsocket, sizeof(sunaddr.sun_path)); + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) + return -1; + + if (connect(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) < 0) + { + close(sock); + return -1; + } + + return sock; +} + +/* Closes the agent socket if it should be closed (depends on how it was + obtained). The argument must have been returned by + ssh_get_authentication_socket(). */ + +void ssh_close_authentication_socket(int sock) +{ + if (getenv(SSH_AUTHSOCKET_ENV_NAME)) + close(sock); +} + +/* Opens and connects a private socket for communication with the + authentication agent. Returns the file descriptor (which must be + shut down and closed by the caller when no longer needed). + Returns NULL if an error occurred and the connection could not be + opened. */ + +AuthenticationConnection *ssh_get_authentication_connection() +{ + AuthenticationConnection *auth; + int sock; + + sock = ssh_get_authentication_socket(); + + /* Fail if we couldn't obtain a connection. This happens if we exited + due to a timeout. */ + if (sock < 0) + return NULL; + + /* Applocate the connection structure and initialize it. */ + auth = xmalloc(sizeof(*auth)); + auth->fd = sock; + buffer_init(&auth->packet); + buffer_init(&auth->identities); + auth->howmany = 0; + + return auth; +} + +/* Closes the connection to the authentication agent and frees any associated + memory. */ + +void ssh_close_authentication_connection(AuthenticationConnection *ac) +{ + buffer_free(&ac->packet); + buffer_free(&ac->identities); + close(ac->fd); + /* Free the connection data structure. */ + xfree(ac); +} + +/* Returns the first authentication identity held by the agent. + Returns true if an identity is available, 0 otherwise. + The caller must initialize the integers before the call, and free the + comment after a successful call (before calling ssh_get_next_identity). */ + +int +ssh_get_first_identity(AuthenticationConnection *auth, + int *bitsp, BIGNUM *e, BIGNUM *n, char **comment) +{ + unsigned char msg[8192]; + int len, l; + + /* Send a message to the agent requesting for a list of the identities + it can represent. */ + msg[0] = 0; + msg[1] = 0; + msg[2] = 0; + msg[3] = 1; + msg[4] = SSH_AGENTC_REQUEST_RSA_IDENTITIES; + if (write(auth->fd, msg, 5) != 5) + { + error("write auth->fd: %.100s", strerror(errno)); + return 0; + } + + /* Read the length of the response. XXX implement timeouts here. */ + len = 4; + while (len > 0) + { + l = read(auth->fd, msg + 4 - len, len); + if (l <= 0) + { + error("read auth->fd: %.100s", strerror(errno)); + return 0; + } + len -= l; + } + + /* Extract the length, and check it for sanity. (We cannot trust + authentication agents). */ + len = GET_32BIT(msg); + if (len < 1 || len > 256*1024) + fatal("Authentication reply message too long: %d\n", len); + + /* Read the packet itself. */ + buffer_clear(&auth->identities); + while (len > 0) + { + l = len; + if (l > sizeof(msg)) + l = sizeof(msg); + l = read(auth->fd, msg, l); + if (l <= 0) + fatal("Incomplete authentication reply."); + buffer_append(&auth->identities, (char *)msg, l); + len -= l; + } + + /* Get message type, and verify that we got a proper answer. */ + buffer_get(&auth->identities, (char *)msg, 1); + if (msg[0] != SSH_AGENT_RSA_IDENTITIES_ANSWER) + fatal("Bad authentication reply message type: %d", msg[0]); + + /* Get the number of entries in the response and check it for sanity. */ + auth->howmany = buffer_get_int(&auth->identities); + if (auth->howmany > 1024) + fatal("Too many identities in authentication reply: %d\n", auth->howmany); + + /* Return the first entry (if any). */ + return ssh_get_next_identity(auth, bitsp, e, n, comment); +} + +/* Returns the next authentication identity for the agent. Other functions + can be called between this and ssh_get_first_identity or two calls of this + function. This returns 0 if there are no more identities. The caller + must free comment after a successful return. */ + +int +ssh_get_next_identity(AuthenticationConnection *auth, + int *bitsp, BIGNUM *e, BIGNUM *n, char **comment) +{ + /* Return failure if no more entries. */ + if (auth->howmany <= 0) + return 0; + + /* Get the next entry from the packet. These will abort with a fatal + error if the packet is too short or contains corrupt data. */ + *bitsp = buffer_get_int(&auth->identities); + buffer_get_bignum(&auth->identities, e); + buffer_get_bignum(&auth->identities, n); + *comment = buffer_get_string(&auth->identities, NULL); + + /* Decrement the number of remaining entries. */ + auth->howmany--; + + return 1; +} + +/* Generates a random challenge, sends it to the agent, and waits for response + from the agent. Returns true (non-zero) if the agent gave the correct + answer, zero otherwise. Response type selects the style of response + desired, with 0 corresponding to protocol version 1.0 (no longer supported) + and 1 corresponding to protocol version 1.1. */ + +int +ssh_decrypt_challenge(AuthenticationConnection *auth, + int bits, BIGNUM *e, BIGNUM *n, BIGNUM *challenge, + unsigned char session_id[16], + unsigned int response_type, + unsigned char response[16]) +{ + Buffer buffer; + unsigned char buf[8192]; + int len, l, i; + + /* Response type 0 is no longer supported. */ + if (response_type == 0) + fatal("Compatibility with ssh protocol version 1.0 no longer supported."); + + /* Format a message to the agent. */ + buf[0] = SSH_AGENTC_RSA_CHALLENGE; + buffer_init(&buffer); + buffer_append(&buffer, (char *)buf, 1); + buffer_put_int(&buffer, bits); + buffer_put_bignum(&buffer, e); + buffer_put_bignum(&buffer, n); + buffer_put_bignum(&buffer, challenge); + buffer_append(&buffer, (char *)session_id, 16); + buffer_put_int(&buffer, response_type); + + /* Get the length of the message, and format it in the buffer. */ + len = buffer_len(&buffer); + PUT_32BIT(buf, len); + + /* Send the length and then the packet to the agent. */ + if (write(auth->fd, buf, 4) != 4 || + write(auth->fd, buffer_ptr(&buffer), buffer_len(&buffer)) != + buffer_len(&buffer)) + { + error("Error writing to authentication socket."); + error_cleanup: + buffer_free(&buffer); + return 0; + } + + /* Wait for response from the agent. First read the length of the + response packet. */ + len = 4; + while (len > 0) + { + l = read(auth->fd, buf + 4 - len, len); + if (l <= 0) + { + error("Error reading response length from authentication socket."); + goto error_cleanup; + } + len -= l; + } + + /* Extract the length, and check it for sanity. */ + len = GET_32BIT(buf); + if (len > 256*1024) + fatal("Authentication response too long: %d", len); + + /* Read the rest of the response in tothe buffer. */ + buffer_clear(&buffer); + while (len > 0) + { + l = len; + if (l > sizeof(buf)) + l = sizeof(buf); + l = read(auth->fd, buf, l); + if (l <= 0) + { + error("Error reading response from authentication socket."); + goto error_cleanup; + } + buffer_append(&buffer, (char *)buf, l); + len -= l; + } + + /* Get the type of the packet. */ + buffer_get(&buffer, (char *)buf, 1); + + /* Check for agent failure message. */ + if (buf[0] == SSH_AGENT_FAILURE) + { + log("Agent admitted failure to authenticate using the key."); + goto error_cleanup; + } + + /* Now it must be an authentication response packet. */ + if (buf[0] != SSH_AGENT_RSA_RESPONSE) + fatal("Bad authentication response: %d", buf[0]); + + /* Get the response from the packet. This will abort with a fatal error + if the packet is corrupt. */ + for (i = 0; i < 16; i++) + response[i] = buffer_get_char(&buffer); + + /* The buffer containing the packet is no longer needed. */ + buffer_free(&buffer); + + /* Correct answer. */ + return 1; +} + +/* Adds an identity to the authentication server. This call is not meant to + be used by normal applications. */ + +int ssh_add_identity(AuthenticationConnection *auth, + RSA *key, const char *comment) +{ + Buffer buffer; + unsigned char buf[8192]; + int len, l, type; + + /* Format a message to the agent. */ + buffer_init(&buffer); + buffer_put_char(&buffer, SSH_AGENTC_ADD_RSA_IDENTITY); + buffer_put_int(&buffer, BN_num_bits(key->n)); + buffer_put_bignum(&buffer, key->n); + buffer_put_bignum(&buffer, key->e); + buffer_put_bignum(&buffer, key->d); + /* To keep within the protocol: p < q for ssh. in SSL p > q */ + buffer_put_bignum(&buffer, key->iqmp); /* ssh key->u */ + buffer_put_bignum(&buffer, key->q); /* ssh key->p, SSL key->q */ + buffer_put_bignum(&buffer, key->p); /* ssh key->q, SSL key->p */ + buffer_put_string(&buffer, comment, strlen(comment)); + + /* Get the length of the message, and format it in the buffer. */ + len = buffer_len(&buffer); + PUT_32BIT(buf, len); + + /* Send the length and then the packet to the agent. */ + if (write(auth->fd, buf, 4) != 4 || + write(auth->fd, buffer_ptr(&buffer), buffer_len(&buffer)) != + buffer_len(&buffer)) + { + error("Error writing to authentication socket."); + error_cleanup: + buffer_free(&buffer); + return 0; + } + + /* Wait for response from the agent. First read the length of the + response packet. */ + len = 4; + while (len > 0) + { + l = read(auth->fd, buf + 4 - len, len); + if (l <= 0) + { + error("Error reading response length from authentication socket."); + goto error_cleanup; + } + len -= l; + } + + /* Extract the length, and check it for sanity. */ + len = GET_32BIT(buf); + if (len > 256*1024) + fatal("Add identity response too long: %d", len); + + /* Read the rest of the response in tothe buffer. */ + buffer_clear(&buffer); + while (len > 0) + { + l = len; + if (l > sizeof(buf)) + l = sizeof(buf); + l = read(auth->fd, buf, l); + if (l <= 0) + { + error("Error reading response from authentication socket."); + goto error_cleanup; + } + buffer_append(&buffer, (char *)buf, l); + len -= l; + } + + /* Get the type of the packet. */ + type = buffer_get_char(&buffer); + switch (type) + { + case SSH_AGENT_FAILURE: + buffer_free(&buffer); + return 0; + case SSH_AGENT_SUCCESS: + buffer_free(&buffer); + return 1; + default: + fatal("Bad response to add identity from authentication agent: %d", + type); + } + /*NOTREACHED*/ + return 0; +} + +/* Removes an identity from the authentication server. This call is not meant + to be used by normal applications. */ + +int ssh_remove_identity(AuthenticationConnection *auth, RSA *key) +{ + Buffer buffer; + unsigned char buf[8192]; + int len, l, type; + + /* Format a message to the agent. */ + buffer_init(&buffer); + buffer_put_char(&buffer, SSH_AGENTC_REMOVE_RSA_IDENTITY); + buffer_put_int(&buffer, BN_num_bits(key->n)); + buffer_put_bignum(&buffer, key->e); + buffer_put_bignum(&buffer, key->n); + + /* Get the length of the message, and format it in the buffer. */ + len = buffer_len(&buffer); + PUT_32BIT(buf, len); + + /* Send the length and then the packet to the agent. */ + if (write(auth->fd, buf, 4) != 4 || + write(auth->fd, buffer_ptr(&buffer), buffer_len(&buffer)) != + buffer_len(&buffer)) + { + error("Error writing to authentication socket."); + error_cleanup: + buffer_free(&buffer); + return 0; + } + + /* Wait for response from the agent. First read the length of the + response packet. */ + len = 4; + while (len > 0) + { + l = read(auth->fd, buf + 4 - len, len); + if (l <= 0) + { + error("Error reading response length from authentication socket."); + goto error_cleanup; + } + len -= l; + } + + /* Extract the length, and check it for sanity. */ + len = GET_32BIT(buf); + if (len > 256*1024) + fatal("Remove identity response too long: %d", len); + + /* Read the rest of the response in tothe buffer. */ + buffer_clear(&buffer); + while (len > 0) + { + l = len; + if (l > sizeof(buf)) + l = sizeof(buf); + l = read(auth->fd, buf, l); + if (l <= 0) + { + error("Error reading response from authentication socket."); + goto error_cleanup; + } + buffer_append(&buffer, (char *)buf, l); + len -= l; + } + + /* Get the type of the packet. */ + type = buffer_get_char(&buffer); + switch (type) + { + case SSH_AGENT_FAILURE: + buffer_free(&buffer); + return 0; + case SSH_AGENT_SUCCESS: + buffer_free(&buffer); + return 1; + default: + fatal("Bad response to remove identity from authentication agent: %d", + type); + } + /*NOTREACHED*/ + return 0; +} + +/* Removes all identities from the agent. This call is not meant + to be used by normal applications. */ + +int ssh_remove_all_identities(AuthenticationConnection *auth) +{ + Buffer buffer; + unsigned char buf[8192]; + int len, l, type; + + /* Get the length of the message, and format it in the buffer. */ + PUT_32BIT(buf, 1); + buf[4] = SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES; + + /* Send the length and then the packet to the agent. */ + if (write(auth->fd, buf, 5) != 5) + { + error("Error writing to authentication socket."); + return 0; + } + + /* Wait for response from the agent. First read the length of the + response packet. */ + len = 4; + while (len > 0) + { + l = read(auth->fd, buf + 4 - len, len); + if (l <= 0) + { + error("Error reading response length from authentication socket."); + return 0; + } + len -= l; + } + + /* Extract the length, and check it for sanity. */ + len = GET_32BIT(buf); + if (len > 256*1024) + fatal("Remove identity response too long: %d", len); + + /* Read the rest of the response into the buffer. */ + buffer_init(&buffer); + while (len > 0) + { + l = len; + if (l > sizeof(buf)) + l = sizeof(buf); + l = read(auth->fd, buf, l); + if (l <= 0) + { + error("Error reading response from authentication socket."); + buffer_free(&buffer); + return 0; + } + buffer_append(&buffer, (char *)buf, l); + len -= l; + } + + /* Get the type of the packet. */ + type = buffer_get_char(&buffer); + switch (type) + { + case SSH_AGENT_FAILURE: + buffer_free(&buffer); + return 0; + case SSH_AGENT_SUCCESS: + buffer_free(&buffer); + return 1; + default: + fatal("Bad response to remove identity from authentication agent: %d", + type); + } + /*NOTREACHED*/ + return 0; +} diff --git a/authfd.h b/authfd.h new file mode 100644 index 00000000..1def920e --- /dev/null +++ b/authfd.h @@ -0,0 +1,102 @@ +/* + +authfd.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Wed Mar 29 01:17:41 1995 ylo + +Functions to interface with the SSH_AUTHENTICATION_FD socket. + +*/ + +/* RCSID("$Id: authfd.h,v 1.1 1999/10/27 03:42:43 damien Exp $"); */ + +#ifndef AUTHFD_H +#define AUTHFD_H + +#include "buffer.h" + +/* Messages for the authentication agent connection. */ +#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH_AGENTC_RSA_CHALLENGE 3 +#define SSH_AGENT_RSA_RESPONSE 4 +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 +#define SSH_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8 +#define SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 + +typedef struct +{ + int fd; + Buffer packet; + Buffer identities; + int howmany; +} AuthenticationConnection; + +/* Returns the number of the authentication fd, or -1 if there is none. */ +int ssh_get_authentication_socket(); + +/* This should be called for any descriptor returned by + ssh_get_authentication_socket(). Depending on the way the descriptor was + obtained, this may close the descriptor. */ +void ssh_close_authentication_socket(int authfd); + +/* Opens and connects a private socket for communication with the + authentication agent. Returns NULL if an error occurred and the + connection could not be opened. The connection should be closed by + the caller by calling ssh_close_authentication_connection(). */ +AuthenticationConnection *ssh_get_authentication_connection(); + +/* Closes the connection to the authentication agent and frees any associated + memory. */ +void ssh_close_authentication_connection(AuthenticationConnection *ac); + +/* Returns the first authentication identity held by the agent. + Returns true if an identity is available, 0 otherwise. + The caller must initialize the integers before the call, and free the + comment after a successful call (before calling ssh_get_next_identity). */ +int ssh_get_first_identity(AuthenticationConnection *connection, + int *bitsp, BIGNUM *e, BIGNUM *n, char **comment); + +/* Returns the next authentication identity for the agent. Other functions + can be called between this and ssh_get_first_identity or two calls of this + function. This returns 0 if there are no more identities. The caller + must free comment after a successful return. */ +int ssh_get_next_identity(AuthenticationConnection *connection, + int *bitsp, BIGNUM *e, BIGNUM *n, char **comment); + +/* Requests the agent to decrypt the given challenge. Returns true if + the agent claims it was able to decrypt it. */ +int ssh_decrypt_challenge(AuthenticationConnection *auth, + int bits, BIGNUM *e, BIGNUM *n, BIGNUM *challenge, + unsigned char session_id[16], + unsigned int response_type, + unsigned char response[16]); + +/* Adds an identity to the authentication server. This call is not meant to + be used by normal applications. This returns true if the identity + was successfully added. */ +int ssh_add_identity(AuthenticationConnection *connection, + RSA *key, const char *comment); + +/* Removes the identity from the authentication server. This call is + not meant to be used by normal applications. This returns true if the + identity was successfully added. */ +int ssh_remove_identity(AuthenticationConnection *connection, + RSA *key); + +/* Removes all identities from the authentication agent. This call is not + meant to be used by normal applications. This returns true if the + operation was successful. */ +int ssh_remove_all_identities(AuthenticationConnection *connection); + +/* Closes the connection to the authentication agent. */ +void ssh_close_authentication(AuthenticationConnection *connection); + +#endif /* AUTHFD_H */ diff --git a/authfile.c b/authfile.c new file mode 100644 index 00000000..49390e08 --- /dev/null +++ b/authfile.c @@ -0,0 +1,350 @@ +/* + +authfile.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Mon Mar 27 03:52:05 1995 ylo + +This file contains functions for reading and writing identity files, and +for reading the passphrase from the user. + +*/ + +#include "includes.h" +RCSID("$Id: authfile.c,v 1.1 1999/10/27 03:42:43 damien Exp $"); + +#include <openssl/bn.h> +#include "xmalloc.h" +#include "buffer.h" +#include "bufaux.h" +#include "cipher.h" +#include "ssh.h" + +/* Version identification string for identity files. */ +#define AUTHFILE_ID_STRING "SSH PRIVATE KEY FILE FORMAT 1.1\n" + +/* Saves the authentication (private) key in a file, encrypting it with + passphrase. The identification of the file (lowest 64 bits of n) + will precede the key to provide identification of the key without + needing a passphrase. */ + +int +save_private_key(const char *filename, const char *passphrase, + RSA *key, const char *comment) +{ + Buffer buffer, encrypted; + char buf[100], *cp; + int f, i; + CipherContext cipher; + int cipher_type; + u_int32_t rand; + + /* If the passphrase is empty, use SSH_CIPHER_NONE to ease converting to + another cipher; otherwise use SSH_AUTHFILE_CIPHER. */ + if (strcmp(passphrase, "") == 0) + cipher_type = SSH_CIPHER_NONE; + else + cipher_type = SSH_AUTHFILE_CIPHER; + + /* This buffer is used to built the secret part of the private key. */ + buffer_init(&buffer); + + /* Put checkbytes for checking passphrase validity. */ + rand = arc4random(); + buf[0] = rand & 0xff; + buf[1] = (rand >> 8) & 0xff; + buf[2] = buf[0]; + buf[3] = buf[1]; + buffer_append(&buffer, buf, 4); + + /* Store the private key (n and e will not be stored because they will + be stored in plain text, and storing them also in encrypted format + would just give known plaintext). */ + buffer_put_bignum(&buffer, key->d); + buffer_put_bignum(&buffer, key->iqmp); + buffer_put_bignum(&buffer, key->q); /* reverse from SSL p */ + buffer_put_bignum(&buffer, key->p); /* reverse from SSL q */ + + /* Pad the part to be encrypted until its size is a multiple of 8. */ + while (buffer_len(&buffer) % 8 != 0) + buffer_put_char(&buffer, 0); + + /* This buffer will be used to contain the data in the file. */ + buffer_init(&encrypted); + + /* First store keyfile id string. */ + cp = AUTHFILE_ID_STRING; + for (i = 0; cp[i]; i++) + buffer_put_char(&encrypted, cp[i]); + buffer_put_char(&encrypted, 0); + + /* Store cipher type. */ + buffer_put_char(&encrypted, cipher_type); + buffer_put_int(&encrypted, 0); /* For future extension */ + + /* Store public key. This will be in plain text. */ + buffer_put_int(&encrypted, BN_num_bits(key->n)); + buffer_put_bignum(&encrypted, key->n); + buffer_put_bignum(&encrypted, key->e); + buffer_put_string(&encrypted, comment, strlen(comment)); + + /* Allocate space for the private part of the key in the buffer. */ + buffer_append_space(&encrypted, &cp, buffer_len(&buffer)); + + cipher_set_key_string(&cipher, cipher_type, passphrase, 1); + cipher_encrypt(&cipher, (unsigned char *)cp, + (unsigned char *)buffer_ptr(&buffer), + buffer_len(&buffer)); + memset(&cipher, 0, sizeof(cipher)); + + /* Destroy temporary data. */ + memset(buf, 0, sizeof(buf)); + buffer_free(&buffer); + + /* Write to a file. */ + f = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0600); + if (f < 0) + return 0; + + if (write(f, buffer_ptr(&encrypted), buffer_len(&encrypted)) != + buffer_len(&encrypted)) + { + debug("Write to key file %.200s failed: %.100s", filename, + strerror(errno)); + buffer_free(&encrypted); + close(f); + remove(filename); + return 0; + } + close(f); + buffer_free(&encrypted); + return 1; +} + +/* Loads the public part of the key file. Returns 0 if an error + was encountered (the file does not exist or is not readable), and + non-zero otherwise. */ + +int +load_public_key(const char *filename, RSA *pub, + char **comment_return) +{ + int f, i; + off_t len; + Buffer buffer; + char *cp; + + /* Read data from the file into the buffer. */ + f = open(filename, O_RDONLY); + if (f < 0) + return 0; + + len = lseek(f, (off_t)0, SEEK_END); + lseek(f, (off_t)0, SEEK_SET); + + buffer_init(&buffer); + buffer_append_space(&buffer, &cp, len); + + if (read(f, cp, (size_t)len) != (size_t)len) + { + debug("Read from key file %.200s failed: %.100s", filename, + strerror(errno)); + buffer_free(&buffer); + close(f); + return 0; + } + close(f); + + /* Check that it is at least big enought to contain the ID string. */ + if (len < strlen(AUTHFILE_ID_STRING) + 1) + { + debug("Bad key file %.200s.", filename); + buffer_free(&buffer); + return 0; + } + + /* Make sure it begins with the id string. Consume the id string from + the buffer. */ + for (i = 0; i < (unsigned int)strlen(AUTHFILE_ID_STRING) + 1; i++) + if (buffer_get_char(&buffer) != (unsigned char)AUTHFILE_ID_STRING[i]) + { + debug("Bad key file %.200s.", filename); + buffer_free(&buffer); + return 0; + } + + /* Skip cipher type and reserved data. */ + (void)buffer_get_char(&buffer); /* cipher type */ + (void)buffer_get_int(&buffer); /* reserved */ + + /* Read the public key from the buffer. */ + buffer_get_int(&buffer); + pub->n = BN_new(); + buffer_get_bignum(&buffer, pub->n); + pub->e = BN_new(); + buffer_get_bignum(&buffer, pub->e); + if (comment_return) + *comment_return = buffer_get_string(&buffer, NULL); + /* The encrypted private part is not parsed by this function. */ + + buffer_free(&buffer); + + return 1; +} + +/* Loads the private key from the file. Returns 0 if an error is encountered + (file does not exist or is not readable, or passphrase is bad). + This initializes the private key. */ + +int +load_private_key(const char *filename, const char *passphrase, + RSA *prv, char **comment_return) +{ + int f, i, check1, check2, cipher_type; + off_t len; + Buffer buffer, decrypted; + char *cp; + CipherContext cipher; + BN_CTX *ctx; + BIGNUM *aux; + struct stat st; + + /* Read the file into the buffer. */ + f = open(filename, O_RDONLY); + if (f < 0) + return 0; + + /* We assume we are called under uid of the owner of the file */ + if (fstat(f, &st) < 0 || + (st.st_uid != 0 && st.st_uid != getuid()) || + (st.st_mode & 077) != 0) { + error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + error("@ WARNING: UNPROTECTED PRIVATE KEY FILE! @"); + error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + error("Bad ownership or mode(0%3.3o) for '%s'.", + st.st_mode & 0777, filename); + error("It is recommended that your private key files are NOT accessible by others."); + return 0; + } + + len = lseek(f, (off_t)0, SEEK_END); + lseek(f, (off_t)0, SEEK_SET); + + buffer_init(&buffer); + buffer_append_space(&buffer, &cp, len); + + if (read(f, cp, (size_t)len) != (size_t)len) + { + debug("Read from key file %.200s failed: %.100s", filename, + strerror(errno)); + buffer_free(&buffer); + close(f); + return 0; + } + close(f); + + /* Check that it is at least big enought to contain the ID string. */ + if (len < strlen(AUTHFILE_ID_STRING) + 1) + { + debug("Bad key file %.200s.", filename); + buffer_free(&buffer); + return 0; + } + + /* Make sure it begins with the id string. Consume the id string from + the buffer. */ + for (i = 0; i < (unsigned int)strlen(AUTHFILE_ID_STRING) + 1; i++) + if (buffer_get_char(&buffer) != (unsigned char)AUTHFILE_ID_STRING[i]) + { + debug("Bad key file %.200s.", filename); + buffer_free(&buffer); + return 0; + } + + /* Read cipher type. */ + cipher_type = buffer_get_char(&buffer); + (void)buffer_get_int(&buffer); /* Reserved data. */ + + /* Read the public key from the buffer. */ + buffer_get_int(&buffer); + prv->n = BN_new(); + buffer_get_bignum(&buffer, prv->n); + prv->e = BN_new(); + buffer_get_bignum(&buffer, prv->e); + if (comment_return) + *comment_return = buffer_get_string(&buffer, NULL); + else + xfree(buffer_get_string(&buffer, NULL)); + + /* Check that it is a supported cipher. */ + if (((cipher_mask() | SSH_CIPHER_NONE | SSH_AUTHFILE_CIPHER) & + (1 << cipher_type)) == 0) + { + debug("Unsupported cipher %.100s used in key file %.200s.", + cipher_name(cipher_type), filename); + buffer_free(&buffer); + goto fail; + } + + /* Initialize space for decrypted data. */ + buffer_init(&decrypted); + buffer_append_space(&decrypted, &cp, buffer_len(&buffer)); + + /* Rest of the buffer is encrypted. Decrypt it using the passphrase. */ + cipher_set_key_string(&cipher, cipher_type, passphrase, 0); + cipher_decrypt(&cipher, (unsigned char *)cp, + (unsigned char *)buffer_ptr(&buffer), + buffer_len(&buffer)); + + buffer_free(&buffer); + + check1 = buffer_get_char(&decrypted); + check2 = buffer_get_char(&decrypted); + if (check1 != buffer_get_char(&decrypted) || + check2 != buffer_get_char(&decrypted)) + { + if (strcmp(passphrase, "") != 0) + debug("Bad passphrase supplied for key file %.200s.", filename); + /* Bad passphrase. */ + buffer_free(&decrypted); + fail: + BN_clear_free(prv->n); + BN_clear_free(prv->e); + if (comment_return) + xfree(*comment_return); + return 0; + } + + /* Read the rest of the private key. */ + prv->d = BN_new(); + buffer_get_bignum(&decrypted, prv->d); + prv->iqmp = BN_new(); + buffer_get_bignum(&decrypted, prv->iqmp); /* u */ + /* in SSL and SSH p and q are exchanged */ + prv->q = BN_new(); + buffer_get_bignum(&decrypted, prv->q); /* p */ + prv->p = BN_new(); + buffer_get_bignum(&decrypted, prv->p); /* q */ + + ctx = BN_CTX_new(); + aux = BN_new(); + + BN_sub(aux, prv->q, BN_value_one()); + prv->dmq1 = BN_new(); + BN_mod(prv->dmq1, prv->d, aux, ctx); + + BN_sub(aux, prv->p, BN_value_one()); + prv->dmp1 = BN_new(); + BN_mod(prv->dmp1, prv->d, aux, ctx); + + BN_clear_free(aux); + BN_CTX_free(ctx); + + buffer_free(&decrypted); + + return 1; +} diff --git a/bufaux.c b/bufaux.c new file mode 100644 index 00000000..1ae39d67 --- /dev/null +++ b/bufaux.c @@ -0,0 +1,141 @@ +/* + +bufaux.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Wed Mar 29 02:24:47 1995 ylo + +Auxiliary functions for storing and retrieving various data types to/from +Buffers. + +*/ + +#include "includes.h" +RCSID("$Id: bufaux.c,v 1.1 1999/10/27 03:42:43 damien Exp $"); + +#include "ssh.h" +#include <openssl/bn.h> +#include "bufaux.h" +#include "xmalloc.h" +#include "getput.h" + +/* Stores an BIGNUM in the buffer with a 2-byte msb first bit count, followed + by (bits+7)/8 bytes of binary data, msb first. */ + +void +buffer_put_bignum(Buffer *buffer, BIGNUM *value) +{ + int bits = BN_num_bits(value); + int bin_size = (bits + 7) / 8; + char *buf = xmalloc(bin_size); + int oi; + char msg[2]; + + /* Get the value of in binary */ + oi = BN_bn2bin(value, buf); + assert(oi == bin_size); + + /* Store the number of bits in the buffer in two bytes, msb first. */ + PUT_16BIT(msg, bits); + buffer_append(buffer, msg, 2); + /* Store the binary data. */ + buffer_append(buffer, buf, oi); + /* Clear the temporary data. */ + memset(buf, 0, bin_size); + xfree(buf); +} + +/* Retrieves an BIGNUM from the buffer. */ + +int +buffer_get_bignum(Buffer *buffer, BIGNUM *value) +{ + int bits, bytes; + unsigned char buf[2], *bin; + + /* Get the number for bits. */ + buffer_get(buffer, (char *)buf, 2); + bits = GET_16BIT(buf); + /* Compute the number of binary bytes that follow. */ + bytes = (bits + 7) / 8; + bin = xmalloc(bytes); + buffer_get(buffer, bin, bytes); + BN_bin2bn(bin, bytes, value); + xfree(bin); + + return 2 + bytes; +} + +/* Returns an integer from the buffer (4 bytes, msb first). */ + +unsigned int buffer_get_int(Buffer *buffer) +{ + unsigned char buf[4]; + buffer_get(buffer, (char *)buf, 4); + return GET_32BIT(buf); +} + +/* Stores an integer in the buffer in 4 bytes, msb first. */ + +void buffer_put_int(Buffer *buffer, unsigned int value) +{ + char buf[4]; + PUT_32BIT(buf, value); + buffer_append(buffer, buf, 4); +} + +/* Returns an arbitrary binary string from the buffer. The string cannot + be longer than 256k. The returned value points to memory allocated + with xmalloc; it is the responsibility of the calling function to free + the data. If length_ptr is non-NULL, the length of the returned data + will be stored there. A null character will be automatically appended + to the returned string, and is not counted in length. */ + +char *buffer_get_string(Buffer *buffer, unsigned int *length_ptr) +{ + unsigned int len; + char *value; + /* Get the length. */ + len = buffer_get_int(buffer); + if (len > 256*1024) + fatal("Received packet with bad string length %d", len); + /* Allocate space for the string. Add one byte for a null character. */ + value = xmalloc(len + 1); + /* Get the string. */ + buffer_get(buffer, value, len); + /* Append a null character to make processing easier. */ + value[len] = 0; + /* Optionally return the length of the string. */ + if (length_ptr) + *length_ptr = len; + return value; +} + +/* Stores and arbitrary binary string in the buffer. */ + +void buffer_put_string(Buffer *buffer, const void *buf, unsigned int len) +{ + buffer_put_int(buffer, len); + buffer_append(buffer, buf, len); +} + +/* Returns a character from the buffer (0 - 255). */ + +int buffer_get_char(Buffer *buffer) +{ + char ch; + buffer_get(buffer, &ch, 1); + return (unsigned char)ch; +} + +/* Stores a character in the buffer. */ + +void buffer_put_char(Buffer *buffer, int value) +{ + char ch = value; + buffer_append(buffer, &ch, 1); +} diff --git a/bufaux.h b/bufaux.h new file mode 100644 index 00000000..bfc66848 --- /dev/null +++ b/bufaux.h @@ -0,0 +1,51 @@ +/* + +bufaux.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Wed Mar 29 02:18:23 1995 ylo + +*/ + +/* RCSID("$Id: bufaux.h,v 1.1 1999/10/27 03:42:43 damien Exp $"); */ + +#ifndef BUFAUX_H +#define BUFAUX_H + +#include "buffer.h" + +/* Stores an BIGNUM in the buffer with a 2-byte msb first bit count, followed + by (bits+7)/8 bytes of binary data, msb first. */ +void buffer_put_bignum(Buffer *buffer, BIGNUM *value); + +/* Retrieves an BIGNUM from the buffer. */ +int buffer_get_bignum(Buffer *buffer, BIGNUM *value); + +/* Returns an integer from the buffer (4 bytes, msb first). */ +unsigned int buffer_get_int(Buffer *buffer); + +/* Stores an integer in the buffer in 4 bytes, msb first. */ +void buffer_put_int(Buffer *buffer, unsigned int value); + +/* Returns a character from the buffer (0 - 255). */ +int buffer_get_char(Buffer *buffer); + +/* Stores a character in the buffer. */ +void buffer_put_char(Buffer *buffer, int value); + +/* Returns an arbitrary binary string from the buffer. The string cannot + be longer than 256k. The returned value points to memory allocated + with xmalloc; it is the responsibility of the calling function to free + the data. If length_ptr is non-NULL, the length of the returned data + will be stored there. A null character will be automatically appended + to the returned string, and is not counted in length. */ +char *buffer_get_string(Buffer *buffer, unsigned int *length_ptr); + +/* Stores and arbitrary binary string in the buffer. */ +void buffer_put_string(Buffer *buffer, const void *buf, unsigned int len); + +#endif /* BUFAUX_H */ diff --git a/buffer.c b/buffer.c new file mode 100644 index 00000000..e183d101 --- /dev/null +++ b/buffer.c @@ -0,0 +1,150 @@ +/* + +buffer.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sat Mar 18 04:15:33 1995 ylo + +Functions for manipulating fifo buffers (that can grow if needed). + +*/ + +#include "includes.h" +RCSID("$Id: buffer.c,v 1.1 1999/10/27 03:42:43 damien Exp $"); + +#include "xmalloc.h" +#include "buffer.h" +#include "ssh.h" + +/* Initializes the buffer structure. */ + +void buffer_init(Buffer *buffer) +{ + buffer->alloc = 4096; + buffer->buf = xmalloc(buffer->alloc); + buffer->offset = 0; + buffer->end = 0; +} + +/* Frees any memory used for the buffer. */ + +void buffer_free(Buffer *buffer) +{ + memset(buffer->buf, 0, buffer->alloc); + xfree(buffer->buf); +} + +/* Clears any data from the buffer, making it empty. This does not actually + zero the memory. */ + +void buffer_clear(Buffer *buffer) +{ + buffer->offset = 0; + buffer->end = 0; +} + +/* Appends data to the buffer, expanding it if necessary. */ + +void buffer_append(Buffer *buffer, const char *data, unsigned int len) +{ + char *cp; + buffer_append_space(buffer, &cp, len); + memcpy(cp, data, len); +} + +/* Appends space to the buffer, expanding the buffer if necessary. + This does not actually copy the data into the buffer, but instead + returns a pointer to the allocated region. */ + +void buffer_append_space(Buffer *buffer, char **datap, unsigned int len) +{ + /* If the buffer is empty, start using it from the beginning. */ + if (buffer->offset == buffer->end) + { + buffer->offset = 0; + buffer->end = 0; + } + + restart: + /* If there is enough space to store all data, store it now. */ + if (buffer->end + len < buffer->alloc) + { + *datap = buffer->buf + buffer->end; + buffer->end += len; + return; + } + + /* If the buffer is quite empty, but all data is at the end, move the + data to the beginning and retry. */ + if (buffer->offset > buffer->alloc / 2) + { + memmove(buffer->buf, buffer->buf + buffer->offset, + buffer->end - buffer->offset); + buffer->end -= buffer->offset; + buffer->offset = 0; + goto restart; + } + + /* Increase the size of the buffer and retry. */ + buffer->alloc += len + 32768; + buffer->buf = xrealloc(buffer->buf, buffer->alloc); + goto restart; +} + +/* Returns the number of bytes of data in the buffer. */ + +unsigned int buffer_len(Buffer *buffer) +{ + return buffer->end - buffer->offset; +} + +/* Gets data from the beginning of the buffer. */ + +void buffer_get(Buffer *buffer, char *buf, unsigned int len) +{ + if (len > buffer->end - buffer->offset) + fatal("buffer_get trying to get more bytes than in buffer"); + memcpy(buf, buffer->buf + buffer->offset, len); + buffer->offset += len; +} + +/* Consumes the given number of bytes from the beginning of the buffer. */ + +void buffer_consume(Buffer *buffer, unsigned int bytes) +{ + if (bytes > buffer->end - buffer->offset) + fatal("buffer_get trying to get more bytes than in buffer"); + buffer->offset += bytes; +} + +/* Consumes the given number of bytes from the end of the buffer. */ + +void buffer_consume_end(Buffer *buffer, unsigned int bytes) +{ + if (bytes > buffer->end - buffer->offset) + fatal("buffer_get trying to get more bytes than in buffer"); + buffer->end -= bytes; +} + +/* Returns a pointer to the first used byte in the buffer. */ + +char *buffer_ptr(Buffer *buffer) +{ + return buffer->buf + buffer->offset; +} + +/* Dumps the contents of the buffer to stderr. */ + +void buffer_dump(Buffer *buffer) +{ + int i; + unsigned char *ucp = (unsigned char *)buffer->buf; + + for (i = buffer->offset; i < buffer->end; i++) + fprintf(stderr, " %02x", ucp[i]); + fprintf(stderr, "\n"); +} diff --git a/buffer.h b/buffer.h new file mode 100644 index 00000000..d0369dc3 --- /dev/null +++ b/buffer.h @@ -0,0 +1,66 @@ +/* + +buffer.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sat Mar 18 04:12:25 1995 ylo + +Code for manipulating FIFO buffers. + +*/ + +/* RCSID("$Id: buffer.h,v 1.1 1999/10/27 03:42:43 damien Exp $"); */ + +#ifndef BUFFER_H +#define BUFFER_H + +typedef struct +{ + char *buf; /* Buffer for data. */ + unsigned int alloc; /* Number of bytes allocated for data. */ + unsigned int offset; /* Offset of first byte containing data. */ + unsigned int end; /* Offset of last byte containing data. */ +} Buffer; + +/* Initializes the buffer structure. */ +void buffer_init(Buffer *buffer); + +/* Frees any memory used for the buffer. */ +void buffer_free(Buffer *buffer); + +/* Clears any data from the buffer, making it empty. This does not actually + zero the memory. */ +void buffer_clear(Buffer *buffer); + +/* Appends data to the buffer, expanding it if necessary. */ +void buffer_append(Buffer *buffer, const char *data, unsigned int len); + +/* Appends space to the buffer, expanding the buffer if necessary. + This does not actually copy the data into the buffer, but instead + returns a pointer to the allocated region. */ +void buffer_append_space(Buffer *buffer, char **datap, unsigned int len); + +/* Returns the number of bytes of data in the buffer. */ +unsigned int buffer_len(Buffer *buffer); + +/* Gets data from the beginning of the buffer. */ +void buffer_get(Buffer *buffer, char *buf, unsigned int len); + +/* Consumes the given number of bytes from the beginning of the buffer. */ +void buffer_consume(Buffer *buffer, unsigned int bytes); + +/* Consumes the given number of bytes from the end of the buffer. */ +void buffer_consume_end(Buffer *buffer, unsigned int bytes); + +/* Returns a pointer to the first used byte in the buffer. */ +char *buffer_ptr(Buffer *buffer); + +/* Dumps the contents of the buffer to stderr in hex. This intended for + debugging purposes only. */ +void buffer_dump(Buffer *buffer); + +#endif /* BUFFER_H */ diff --git a/canohost.c b/canohost.c new file mode 100644 index 00000000..85d97292 --- /dev/null +++ b/canohost.c @@ -0,0 +1,234 @@ +/* + +canohost.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sun Jul 2 17:52:22 1995 ylo + +Functions for returning the canonical host name of the remote site. + +*/ + +#include "includes.h" +RCSID("$Id: canohost.c,v 1.1 1999/10/27 03:42:43 damien Exp $"); + +#include "packet.h" +#include "xmalloc.h" +#include "ssh.h" + +/* Return the canonical name of the host at the other end of the socket. + The caller should free the returned string with xfree. */ + +char *get_remote_hostname(int socket) +{ + struct sockaddr_in from; + int fromlen, i; + struct hostent *hp; + char name[MAXHOSTNAMELEN]; + + /* Get IP address of client. */ + fromlen = sizeof(from); + memset(&from, 0, sizeof(from)); + if (getpeername(socket, (struct sockaddr *)&from, &fromlen) < 0) + { + error("getpeername failed: %.100s", strerror(errno)); + strlcpy(name, "UNKNOWN", sizeof name); + goto check_ip_options; + } + + /* Map the IP address to a host name. */ + hp = gethostbyaddr((char *)&from.sin_addr, sizeof(struct in_addr), + from.sin_family); + if (hp) + { + /* Got host name, find canonic host name. */ + if (strchr(hp->h_name, '.') != 0) + strlcpy(name, hp->h_name, sizeof(name)); + else if (hp->h_aliases != 0 + && hp->h_aliases[0] != 0 + && strchr(hp->h_aliases[0], '.') != 0) + strlcpy(name, hp->h_aliases[0], sizeof(name)); + else + strlcpy(name, hp->h_name, sizeof(name)); + + /* Convert it to all lowercase (which is expected by the rest of this + software). */ + for (i = 0; name[i]; i++) + if (isupper(name[i])) + name[i] = tolower(name[i]); + + /* Map it back to an IP address and check that the given address actually + is an address of this host. This is necessary because anyone with + access to a name server can define arbitrary names for an IP address. + Mapping from name to IP address can be trusted better (but can still + be fooled if the intruder has access to the name server of the + domain). */ + hp = gethostbyname(name); + if (!hp) + { + log("reverse mapping checking gethostbyname for %.700s failed - POSSIBLE BREAKIN ATTEMPT!", name); + strlcpy(name, inet_ntoa(from.sin_addr), sizeof name); + goto check_ip_options; + } + /* Look for the address from the list of addresses. */ + for (i = 0; hp->h_addr_list[i]; i++) + if (memcmp(hp->h_addr_list[i], &from.sin_addr, sizeof(from.sin_addr)) + == 0) + break; + /* If we reached the end of the list, the address was not there. */ + if (!hp->h_addr_list[i]) + { + /* Address not found for the host name. */ + log("Address %.100s maps to %.600s, but this does not map back to the address - POSSIBLE BREAKIN ATTEMPT!", + inet_ntoa(from.sin_addr), name); + strlcpy(name, inet_ntoa(from.sin_addr), sizeof name); + goto check_ip_options; + } + /* Address was found for the host name. We accept the host name. */ + } + else + { + /* Host name not found. Use ascii representation of the address. */ + strlcpy(name, inet_ntoa(from.sin_addr), sizeof name); + log("Could not reverse map address %.100s.", name); + } + + check_ip_options: + + /* If IP options are supported, make sure there are none (log and clear + them if any are found). Basically we are worried about source routing; + it can be used to pretend you are somebody (ip-address) you are not. + That itself may be "almost acceptable" under certain circumstances, + but rhosts autentication is useless if source routing is accepted. + Notice also that if we just dropped source routing here, the other + side could use IP spoofing to do rest of the interaction and could still + bypass security. So we exit here if we detect any IP options. */ + { + unsigned char options[200], *ucp; + char text[1024], *cp; + int option_size, ipproto; + struct protoent *ip; + + if ((ip = getprotobyname("ip")) != NULL) + ipproto = ip->p_proto; + else + ipproto = IPPROTO_IP; + option_size = sizeof(options); + if (getsockopt(0, ipproto, IP_OPTIONS, (char *)options, + &option_size) >= 0 && option_size != 0) + { + cp = text; + /* Note: "text" buffer must be at least 3x as big as options. */ + for (ucp = options; option_size > 0; ucp++, option_size--, cp += 3) + sprintf(cp, " %2.2x", *ucp); + log("Connection from %.100s with IP options:%.800s", + inet_ntoa(from.sin_addr), text); + packet_disconnect("Connection from %.100s with IP options:%.800s", + inet_ntoa(from.sin_addr), text); + } + } + + return xstrdup(name); +} + +static char *canonical_host_name = NULL; +static char *canonical_host_ip = NULL; + +/* Return the canonical name of the host in the other side of the current + connection. The host name is cached, so it is efficient to call this + several times. */ + +const char *get_canonical_hostname() +{ + /* Check if we have previously retrieved this same name. */ + if (canonical_host_name != NULL) + return canonical_host_name; + + /* Get the real hostname if socket; otherwise return UNKNOWN. */ + if (packet_get_connection_in() == packet_get_connection_out()) + canonical_host_name = get_remote_hostname(packet_get_connection_in()); + else + canonical_host_name = xstrdup("UNKNOWN"); + + return canonical_host_name; +} + +/* Returns the IP-address of the remote host as a string. The returned + string need not be freed. */ + +const char *get_remote_ipaddr() +{ + struct sockaddr_in from; + int fromlen, socket; + + /* Check if we have previously retrieved this same name. */ + if (canonical_host_ip != NULL) + return canonical_host_ip; + + /* If not a socket, return UNKNOWN. */ + if (packet_get_connection_in() != packet_get_connection_out()) + { + canonical_host_ip = xstrdup("UNKNOWN"); + return canonical_host_ip; + } + + /* Get client socket. */ + socket = packet_get_connection_in(); + + /* Get IP address of client. */ + fromlen = sizeof(from); + memset(&from, 0, sizeof(from)); + if (getpeername(socket, (struct sockaddr *)&from, &fromlen) < 0) + { + error("getpeername failed: %.100s", strerror(errno)); + return NULL; + } + + /* Get the IP address in ascii. */ + canonical_host_ip = xstrdup(inet_ntoa(from.sin_addr)); + + /* Return ip address string. */ + return canonical_host_ip; +} + +/* Returns the port of the peer of the socket. */ + +int get_peer_port(int sock) +{ + struct sockaddr_in from; + int fromlen; + + /* Get IP address of client. */ + fromlen = sizeof(from); + memset(&from, 0, sizeof(from)); + if (getpeername(sock, (struct sockaddr *)&from, &fromlen) < 0) + { + error("getpeername failed: %.100s", strerror(errno)); + return 0; + } + + /* Return port number. */ + return ntohs(from.sin_port); +} + +/* Returns the port number of the remote host. */ + +int get_remote_port() +{ + int socket; + + /* If the connection is not a socket, return 65535. This is intentionally + chosen to be an unprivileged port number. */ + if (packet_get_connection_in() != packet_get_connection_out()) + return 65535; + + /* Get client socket. */ + socket = packet_get_connection_in(); + + /* Get and return the peer port number. */ + return get_peer_port(socket); +} diff --git a/channels.c b/channels.c new file mode 100644 index 00000000..38a65a07 --- /dev/null +++ b/channels.c @@ -0,0 +1,1500 @@ +/* + +channels.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Fri Mar 24 16:35:24 1995 ylo + +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. + +*/ + +#include "includes.h" +RCSID("$Id: channels.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "ssh.h" +#include "packet.h" +#include "xmalloc.h" +#include "buffer.h" +#include "authfd.h" +#include "uidswap.h" +#include "servconf.h" + +#include "channels.h" +#include "nchan.h" +#include "compat.h" + +/* Maximum number of fake X11 displays to try. */ +#define MAX_DISPLAYS 1000 + +/* Max len of agent socket */ +#define MAX_SOCKET_NAME 100 + +/* 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 are marked with + type SSH_CHANNEL_FREE. */ +static int channels_alloc = 0; + +/* Maximum file descriptor value used in any of the channels. This is updated + in channel_allocate. */ +static int channel_max_fd_value = 0; + +/* Name and directory of socket for authentication agent forwarding. */ +static char *channel_forwarded_auth_socket_name = NULL; +static char *channel_forwarded_auth_socket_dir = NULL; + +/* Saved X11 authentication protocol name. */ +char *x11_saved_proto = NULL; + +/* Saved X11 authentication data. This is the real data. */ +char *x11_saved_data = NULL; +unsigned 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. */ +char *x11_fake_data = NULL; +unsigned int x11_fake_data_len; + +/* 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; /* Host name. */ + int port; /* Port number. */ +} ForwardPermission; + +/* List of all permitted host/port pairs to connect. */ +static ForwardPermission permitted_opens[SSH_MAX_FORWARDS_PER_DIRECTION]; +/* Number of permitted host/port pairs in the array. */ +static int num_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; + +/* This is set to true if both sides support SSH_PROTOFLAG_HOST_IN_FWD_OPEN. */ +static int have_hostname_in_open = 0; + +/* Sets specific protocol options. */ + +void channel_set_options(int hostname_in_open) +{ + have_hostname_in_open = hostname_in_open; +} + +/* Permits opening to any host/port in SSH_MSG_PORT_OPEN. 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() +{ + all_opens_permitted = 1; +} + +/* Allocate a new channel object and set its type and socket. + This will cause remote_name to be freed. */ + +int channel_allocate(int type, int sock, char *remote_name) +{ + int i, old_channels; + + /* Update the maximum file descriptor value. */ + if (sock > channel_max_fd_value) + channel_max_fd_value = sock; + + /* Do initial allocation if this is the first call. */ + if (channels_alloc == 0) + { + channels_alloc = 10; + channels = xmalloc(channels_alloc * sizeof(Channel)); + for (i = 0; i < channels_alloc; i++) + channels[i].type = SSH_CHANNEL_FREE; + + /* Kludge: arrange a call to channel_stop_listening if we terminate + with fatal(). */ + fatal_add_cleanup((void (*)(void *))channel_stop_listening, NULL); + } + + /* Try to find a free slot where to put the new channel. */ + for (i = 0; i < channels_alloc; i++) + if (channels[i].type == SSH_CHANNEL_FREE) + { + /* Found a free slot. Initialize the fields and return its number. */ + buffer_init(&channels[i].input); + buffer_init(&channels[i].output); + channels[i].self = i; + channels[i].type = type; + channels[i].x11 = 0; + channels[i].sock = sock; + channels[i].remote_id = -1; + channels[i].remote_name = remote_name; + chan_init_iostates(&channels[i]); + return i; + } + + /* There are no free slots. Must expand the array. */ + old_channels = channels_alloc; + channels_alloc += 10; + channels = xrealloc(channels, channels_alloc * sizeof(Channel)); + for (i = old_channels; i < channels_alloc; i++) + channels[i].type = SSH_CHANNEL_FREE; + + /* We know that the next one after the old maximum channel number is now + available. Initialize and return its number. */ + buffer_init(&channels[old_channels].input); + buffer_init(&channels[old_channels].output); + channels[old_channels].self = old_channels; + channels[old_channels].type = type; + channels[old_channels].x11 = 0; + channels[old_channels].sock = sock; + channels[old_channels].remote_id = -1; + channels[old_channels].remote_name = remote_name; + chan_init_iostates(&channels[old_channels]); + return old_channels; +} + +/* Free the channel and close its socket. */ + +void channel_free(int channel) +{ + assert(channel >= 0 && channel < channels_alloc && + channels[channel].type != SSH_CHANNEL_FREE); + if(compat13) + shutdown(channels[channel].sock, SHUT_RDWR); + close(channels[channel].sock); + buffer_free(&channels[channel].input); + buffer_free(&channels[channel].output); + channels[channel].type = SSH_CHANNEL_FREE; + if (channels[channel].remote_name) + { + xfree(channels[channel].remote_name); + channels[channel].remote_name = NULL; + } +} + +/* This is called just before select() to add any bits relevant to + channels in the select bitmasks. */ + +void channel_prepare_select(fd_set *readset, fd_set *writeset) +{ + int i; + Channel *ch; + unsigned char *ucp; + unsigned int proto_len, data_len; + + for (i = 0; i < channels_alloc; i++) + { + ch = &channels[i]; + redo: + switch (ch->type) + { + case SSH_CHANNEL_X11_LISTENER: + case SSH_CHANNEL_PORT_LISTENER: + case SSH_CHANNEL_AUTH_SOCKET: + FD_SET(ch->sock, readset); + break; + + case SSH_CHANNEL_OPEN: + if(compat13){ + if (buffer_len(&ch->input) < 32768) + FD_SET(ch->sock, readset); + if (buffer_len(&ch->output) > 0) + FD_SET(ch->sock, writeset); + break; + } + /* test whether sockets are 'alive' for read/write */ + if (ch->istate == CHAN_INPUT_OPEN) + if (buffer_len(&ch->input) < 32768) + FD_SET(ch->sock, readset); + if (ch->ostate == CHAN_OUTPUT_OPEN || ch->ostate == CHAN_OUTPUT_WAIT_DRAIN){ + if (buffer_len(&ch->output) > 0){ + FD_SET(ch->sock, writeset); + }else if(ch->ostate == CHAN_OUTPUT_WAIT_DRAIN) { + chan_obuf_empty(ch); + } + } + break; + + case SSH_CHANNEL_INPUT_DRAINING: + if (!compat13) + fatal("cannot happen: IN_DRAIN"); + if (buffer_len(&ch->input) == 0) + { + packet_start(SSH_MSG_CHANNEL_CLOSE); + packet_put_int(ch->remote_id); + packet_send(); + ch->type = SSH_CHANNEL_CLOSED; + debug("Closing channel %d after input drain.", i); + break; + } + break; + + case SSH_CHANNEL_OUTPUT_DRAINING: + if (!compat13) + fatal("cannot happen: OUT_DRAIN"); + if (buffer_len(&ch->output) == 0) + { + /* debug("Freeing channel %d after output drain.", i); */ + channel_free(i); + break; + } + FD_SET(ch->sock, writeset); + break; + + case SSH_CHANNEL_X11_OPEN: + /* 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. */ + + /* Check if the fixed size part of the packet is in buffer. */ + if (buffer_len(&ch->output) < 12) + break; + + /* Parse the lengths of variable-length fields. */ + ucp = (unsigned char *)buffer_ptr(&ch->output); + 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 + { + debug("Initial X11 packet contains bad byte order byte: 0x%x", + ucp[0]); + ch->type = SSH_CHANNEL_OPEN; + goto reject; + } + + /* Check if the whole packet is in buffer. */ + if (buffer_len(&ch->output) < + 12 + ((proto_len + 3) & ~3) + ((data_len + 3) & ~3)) + break; + + /* Check if authentication protocol matches. */ + if (proto_len != strlen(x11_saved_proto) || + memcmp(ucp + 12, x11_saved_proto, proto_len) != 0) + { + debug("X11 connection uses different authentication protocol."); + ch->type = SSH_CHANNEL_OPEN; + goto reject; + } + + /* 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) + { + debug("X11 auth data does not match fake data."); + ch->type = SSH_CHANNEL_OPEN; + goto reject; + } + + /* Received authentication protocol and data match our fake data. + Substitute the fake data with real data. */ + assert(x11_fake_data_len == x11_saved_data_len); + memcpy(ucp + 12 + ((proto_len + 3) & ~3), + x11_saved_data, x11_saved_data_len); + + /* Start normal processing for the channel. */ + ch->type = SSH_CHANNEL_OPEN; + /* Enable X11 Problem FIX */ + ch->x11 = 1; + goto redo; + + reject: + /* We have received an X11 connection that has bad authentication + information. */ + log("X11 connection rejected because of wrong authentication.\r\n"); + buffer_clear(&ch->input); + buffer_clear(&ch->output); + if (compat13) { + close(ch->sock); + ch->sock = -1; + ch->type = SSH_CHANNEL_CLOSED; + packet_start(SSH_MSG_CHANNEL_CLOSE); + packet_put_int(ch->remote_id); + packet_send(); + }else{ + debug("X11 rejected %d 0x%x 0x%x", ch->self, ch->istate, ch->ostate); + chan_read_failed(ch); + chan_write_failed(ch); + debug("X11 rejected %d 0x%x 0x%x", ch->self, ch->istate, ch->ostate); + } + break; + + case SSH_CHANNEL_FREE: + default: + continue; + } + } +} + +/* After select, perform any appropriate operations for channels which + have events pending. */ + +void channel_after_select(fd_set *readset, fd_set *writeset) +{ + struct sockaddr addr; + int addrlen, newsock, i, newch, len; + Channel *ch; + char buf[16384], *remote_hostname; + + /* Loop over all channels... */ + for (i = 0; i < channels_alloc; i++) + { + ch = &channels[i]; + switch (ch->type) + { + case SSH_CHANNEL_X11_LISTENER: + /* This is our fake X11 server socket. */ + if (FD_ISSET(ch->sock, readset)) + { + debug("X11 connection requested."); + addrlen = sizeof(addr); + newsock = accept(ch->sock, &addr, &addrlen); + if (newsock < 0) + { + error("accept: %.100s", strerror(errno)); + break; + } + remote_hostname = get_remote_hostname(newsock); + snprintf(buf, sizeof buf, "X11 connection from %.200s port %d", + remote_hostname, get_peer_port(newsock)); + xfree(remote_hostname); + newch = channel_allocate(SSH_CHANNEL_OPENING, newsock, + xstrdup(buf)); + packet_start(SSH_SMSG_X11_OPEN); + packet_put_int(newch); + if (have_hostname_in_open) + packet_put_string(buf, strlen(buf)); + packet_send(); + } + break; + + case SSH_CHANNEL_PORT_LISTENER: + /* This socket is listening for connections to a forwarded TCP/IP + port. */ + if (FD_ISSET(ch->sock, readset)) + { + debug("Connection to port %d forwarding to %.100s:%d requested.", + ch->listening_port, ch->path, ch->host_port); + addrlen = sizeof(addr); + newsock = accept(ch->sock, &addr, &addrlen); + if (newsock < 0) + { + error("accept: %.100s", strerror(errno)); + break; + } + remote_hostname = get_remote_hostname(newsock); + snprintf(buf, sizeof buf, "port %d, connection from %.200s port %d", + ch->listening_port, remote_hostname, + get_peer_port(newsock)); + xfree(remote_hostname); + newch = channel_allocate(SSH_CHANNEL_OPENING, newsock, + xstrdup(buf)); + packet_start(SSH_MSG_PORT_OPEN); + packet_put_int(newch); + packet_put_string(ch->path, strlen(ch->path)); + packet_put_int(ch->host_port); + if (have_hostname_in_open) + packet_put_string(buf, strlen(buf)); + packet_send(); + } + break; + + case SSH_CHANNEL_AUTH_SOCKET: + /* This is the authentication agent socket listening for connections + from clients. */ + if (FD_ISSET(ch->sock, readset)) + { + int nchan; + len = sizeof(addr); + newsock = accept(ch->sock, &addr, &len); + if (newsock < 0) + { + error("accept from auth socket: %.100s", strerror(errno)); + break; + } + + nchan = channel_allocate(SSH_CHANNEL_OPENING, newsock, + xstrdup("accepted auth socket")); + packet_start(SSH_SMSG_AGENT_OPEN); + packet_put_int(nchan); + packet_send(); + } + break; + + case SSH_CHANNEL_OPEN: + /* This is an open two-way communication channel. It is not of + interest to us at this point what kind of data is being + transmitted. */ + + /* Read available incoming data and append it to buffer; + shutdown socket, if read or write failes */ + if (FD_ISSET(ch->sock, readset)) + { + len = read(ch->sock, buf, sizeof(buf)); + if (len <= 0) + { + if (compat13) { + buffer_consume(&ch->output, buffer_len(&ch->output)); + ch->type = SSH_CHANNEL_INPUT_DRAINING; + debug("Channel %d status set to input draining.", i); + }else{ + chan_read_failed(ch); + } + break; + } + buffer_append(&ch->input, buf, len); + } + /* Send buffered output data to the socket. */ + if (FD_ISSET(ch->sock, writeset) && buffer_len(&ch->output) > 0) + { + len = write(ch->sock, buffer_ptr(&ch->output), + buffer_len(&ch->output)); + if (len <= 0) + { + if (compat13) { + buffer_consume(&ch->output, buffer_len(&ch->output)); + debug("Channel %d status set to input draining.", i); + ch->type = SSH_CHANNEL_INPUT_DRAINING; + }else{ + chan_write_failed(ch); + } + break; + } + buffer_consume(&ch->output, len); + } + break; + + case SSH_CHANNEL_OUTPUT_DRAINING: + if (!compat13) + fatal("cannot happen: OUT_DRAIN"); + /* Send buffered output data to the socket. */ + if (FD_ISSET(ch->sock, writeset) && buffer_len(&ch->output) > 0) + { + len = write(ch->sock, buffer_ptr(&ch->output), + buffer_len(&ch->output)); + if (len <= 0) + buffer_consume(&ch->output, buffer_len(&ch->output)); + else + buffer_consume(&ch->output, len); + } + break; + + case SSH_CHANNEL_X11_OPEN: + case SSH_CHANNEL_FREE: + default: + continue; + } + } +} + +/* If there is data to send to the connection, send some of it now. */ + +void channel_output_poll() +{ + int len, i; + Channel *ch; + + for (i = 0; i < channels_alloc; i++) + { + ch = &channels[i]; + /* We are only interested in channels that can have buffered incoming + data. */ + if (ch->type != SSH_CHANNEL_OPEN && + ch->type != SSH_CHANNEL_INPUT_DRAINING) + continue; + + /* Get the amount of buffered data for this channel. */ + len = buffer_len(&ch->input); + if (len > 0) + { + /* Send some data for the other side over the secure connection. */ + if (packet_is_interactive()) + { + if (len > 1024) + len = 512; + } + else + { + if (len > 16384) + len = 16384; /* Keep the packets at reasonable size. */ + } + packet_start(SSH_MSG_CHANNEL_DATA); + packet_put_int(ch->remote_id); + packet_put_string(buffer_ptr(&ch->input), len); + packet_send(); + buffer_consume(&ch->input, len); + } + else if(ch->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 */ + chan_ibuf_empty(ch); + } + } +} + +/* This is called when a packet of type CHANNEL_DATA has just been received. + The message type has already been consumed, but channel number and data + is still there. */ + +void channel_input_data(int payload_len) +{ + int channel; + char *data; + unsigned int data_len; + + /* Get the channel number and verify it. */ + channel = packet_get_int(); + if (channel < 0 || channel >= channels_alloc || + channels[channel].type == SSH_CHANNEL_FREE) + packet_disconnect("Received data for nonexistent channel %d.", channel); + + /* Ignore any data for non-open channels (might happen on close) */ + if (channels[channel].type != SSH_CHANNEL_OPEN && + channels[channel].type != SSH_CHANNEL_X11_OPEN) + return; + + /* Get the data. */ + data = packet_get_string(&data_len); + packet_integrity_check(payload_len, 4 + 4+data_len, SSH_MSG_CHANNEL_DATA); + buffer_append(&channels[channel].output, data, data_len); + xfree(data); +} + +/* 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() +{ + unsigned int i; + Channel *ch; + + for (i = 0; i < channels_alloc; i++) + { + ch = &channels[i]; + switch (ch->type) + { + case SSH_CHANNEL_X11_LISTENER: + case SSH_CHANNEL_PORT_LISTENER: + case SSH_CHANNEL_AUTH_SOCKET: + continue; + case SSH_CHANNEL_OPEN: + if (buffer_len(&ch->input) > 32768) + return 0; + if (buffer_len(&ch->output) > 32768) + return 0; + continue; + case SSH_CHANNEL_INPUT_DRAINING: + case SSH_CHANNEL_OUTPUT_DRAINING: + case SSH_CHANNEL_X11_OPEN: + case SSH_CHANNEL_FREE: + default: + continue; + } + } + return 1; +} + +/* This is called after receiving CHANNEL_CLOSE/IEOF. */ + +void channel_input_close() +{ + int channel; + + /* Get the channel number and verify it. */ + channel = packet_get_int(); + if (channel < 0 || channel >= channels_alloc || + channels[channel].type == SSH_CHANNEL_FREE) + packet_disconnect("Received data for nonexistent channel %d.", channel); + + if(!compat13){ + /* proto version 1.5 overloads CLOSE with IEOF */ + chan_rcvd_ieof(&channels[channel]); + return; + } + + /* 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(channels[channel].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 (channels[channel].type != SSH_CHANNEL_CLOSED) + { + /* Not a closed channel - mark it as draining, which will cause it to + be freed later. */ + buffer_consume(&channels[channel].input, + buffer_len(&channels[channel].input)); + channels[channel].type = SSH_CHANNEL_OUTPUT_DRAINING; + /* debug("Setting status to output draining; output len = %d", + buffer_len(&channels[channel].output)); */ + } +} + +/* This is called after receiving CHANNEL_CLOSE_CONFIRMATION/OCLOSE. */ + +void channel_input_close_confirmation() +{ + int channel; + + /* Get the channel number and verify it. */ + channel = packet_get_int(); + if (channel < 0 || channel >= channels_alloc) + packet_disconnect("Received close confirmation for out-of-range channel %d.", + channel); + + if(!compat13){ + /* proto version 1.5 overloads CLOSE_CONFIRMATION with OCLOSE */ + chan_rcvd_oclose(&channels[channel]); + return; + } + + if (channels[channel].type != SSH_CHANNEL_CLOSED) + packet_disconnect("Received close confirmation for non-closed channel %d (type %d).", + channel, channels[channel].type); + + /* Free the channel. */ + channel_free(channel); +} + +/* This is called after receiving CHANNEL_OPEN_CONFIRMATION. */ + +void channel_input_open_confirmation() +{ + int channel, remote_channel; + + /* Get the channel number and verify it. */ + channel = packet_get_int(); + if (channel < 0 || channel >= channels_alloc || + channels[channel].type != SSH_CHANNEL_OPENING) + packet_disconnect("Received open confirmation for non-opening channel %d.", + channel); + + /* Get remote side's id for this channel. */ + remote_channel = packet_get_int(); + + /* Record the remote channel number and mark that the channel is now open. */ + channels[channel].remote_id = remote_channel; + channels[channel].type = SSH_CHANNEL_OPEN; +} + +/* This is called after receiving CHANNEL_OPEN_FAILURE from the other side. */ + +void channel_input_open_failure() +{ + int channel; + + /* Get the channel number and verify it. */ + channel = packet_get_int(); + if (channel < 0 || channel >= channels_alloc || + channels[channel].type != SSH_CHANNEL_OPENING) + packet_disconnect("Received open failure for non-opening channel %d.", + channel); + + /* Free the channel. This will also close the socket. */ + channel_free(channel); +} + +/* Stops listening for channels, and removes any unix domain sockets that + we might have. */ + +void channel_stop_listening() +{ + int i; + for (i = 0; i < channels_alloc; i++) + { + switch (channels[i].type) + { + case SSH_CHANNEL_AUTH_SOCKET: + close(channels[i].sock); + remove(channels[i].path); + channel_free(i); + break; + case SSH_CHANNEL_PORT_LISTENER: + case SSH_CHANNEL_X11_LISTENER: + close(channels[i].sock); + channel_free(i); + break; + default: + break; + } + } +} + +/* Closes the sockets of all channels. This is used to close extra file + descriptors after a fork. */ + +void channel_close_all() +{ + int i; + for (i = 0; i < channels_alloc; i++) + { + if (channels[i].type != SSH_CHANNEL_FREE) + close(channels[i].sock); + } +} + +/* Returns the maximum file descriptor number used by the channels. */ + +int channel_max_fd() +{ + return channel_max_fd_value; +} + +/* Returns true if any channel is still open. */ + +int channel_still_open() +{ + unsigned int i; + for (i = 0; i < channels_alloc; i++) + switch (channels[i].type) + { + case SSH_CHANNEL_FREE: + case SSH_CHANNEL_X11_LISTENER: + case SSH_CHANNEL_PORT_LISTENER: + case SSH_CHANNEL_CLOSED: + case SSH_CHANNEL_AUTH_SOCKET: + 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", channels[i].type); + /*NOTREACHED*/ + } + return 0; +} + +/* 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() +{ + Buffer buffer; + int i; + char buf[512], *cp; + + 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++){ + Channel *c=&channels[i]; + switch (c->type) + { + case SSH_CHANNEL_FREE: + case SSH_CHANNEL_X11_LISTENER: + case SSH_CHANNEL_PORT_LISTENER: + case SSH_CHANNEL_CLOSED: + case SSH_CHANNEL_AUTH_SOCKET: + continue; + case SSH_CHANNEL_OPENING: + case SSH_CHANNEL_OPEN: + case SSH_CHANNEL_X11_OPEN: + case SSH_CHANNEL_INPUT_DRAINING: + case SSH_CHANNEL_OUTPUT_DRAINING: + snprintf(buf, sizeof buf, " #%d/%d %.300s\r\n", + c->self,c->type,c->remote_name); + buffer_append(&buffer, buf, strlen(buf)); + continue; + default: + fatal("channel_still_open: bad channel type %d", c->type); + /*NOTREACHED*/ + } + } + buffer_append(&buffer, "\0", 1); + cp = xstrdup(buffer_ptr(&buffer)); + buffer_free(&buffer); + return cp; +} + +/* Initiate forwarding of connections to local port "port" through the secure + channel to host:port from remote side. */ + +void channel_request_local_forwarding(int port, const char *host, + int host_port) +{ + int ch, sock; + struct sockaddr_in sin; + extern Options options; + + if (strlen(host) > sizeof(channels[0].path) - 1) + packet_disconnect("Forward host name too long."); + + /* Create a port to listen for the host. */ + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) + packet_disconnect("socket: %.100s", strerror(errno)); + + /* Initialize socket address. */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + if (options.gateway_ports == 1) + sin.sin_addr.s_addr = htonl(INADDR_ANY); + else + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(port); + + /* Bind the socket to the address. */ + if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + packet_disconnect("bind: %.100s", strerror(errno)); + + /* Start listening for connections on the socket. */ + if (listen(sock, 5) < 0) + packet_disconnect("listen: %.100s", strerror(errno)); + + /* Allocate a channel number for the socket. */ + ch = channel_allocate(SSH_CHANNEL_PORT_LISTENER, sock, + xstrdup("port listener")); + strcpy(channels[ch].path, host); /* note: host name stored here */ + channels[ch].host_port = host_port; /* port on host to connect to */ + channels[ch].listening_port = port; /* port being listened */ +} + +/* Initiate forwarding of connections to port "port" on remote host through + the secure channel to host:port from local side. */ + +void channel_request_remote_forwarding(int port, const char *host, + int remote_port) +{ + int payload_len; + /* 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"); + permitted_opens[num_permitted_opens].host = xstrdup(host); + permitted_opens[num_permitted_opens].port = remote_port; + num_permitted_opens++; + + /* Send the forward request to the remote side. */ + packet_start(SSH_CMSG_PORT_FORWARD_REQUEST); + packet_put_int(port); + packet_put_string(host, strlen(host)); + packet_put_int(remote_port); + packet_send(); + packet_write_wait(); + + /* Wait for response from the remote side. It will send a disconnect + message on failure, and we will never see it here. */ + packet_read_expect(&payload_len, SSH_SMSG_SUCCESS); +} + +/* 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). This never returns if there was an + error. */ + +void channel_input_port_forward_request(int is_root) +{ + int port, host_port; + char *hostname; + + /* Get arguments from the packet. */ + port = packet_get_int(); + hostname = packet_get_string(NULL); + host_port = packet_get_int(); + + /* Port numbers are 16 bit quantities. */ + if ((port & 0xffff) != port) + packet_disconnect("Requested forwarding of nonexistent port %d.", port); + + /* 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); + + /* Initiate forwarding. */ + channel_request_local_forwarding(port, hostname, host_port); + + /* Free the argument string. */ + xfree(hostname); +} + +/* This is called after receiving PORT_OPEN message. This attempts to connect + to the given host:port, and sends back CHANNEL_OPEN_CONFIRMATION or + CHANNEL_OPEN_FAILURE. */ + +void channel_input_port_open(int payload_len) +{ + int remote_channel, sock, newch, host_port, i; + struct sockaddr_in sin; + char *host, *originator_string; + struct hostent *hp; + int host_len, originator_len; + + /* Get remote channel number. */ + remote_channel = packet_get_int(); + + /* Get host name to connect to. */ + host = packet_get_string(&host_len); + + /* Get port to connect to. */ + host_port = packet_get_int(); + + /* Get remote originator name. */ + if (have_hostname_in_open) + originator_string = packet_get_string(&originator_len); + else + originator_string = xstrdup("unknown (remote did not supply name)"); + + packet_integrity_check(payload_len, + 4 + 4 + host_len + 4 + 4 + originator_len, + SSH_MSG_PORT_OPEN); + + /* Check if opening that port is permitted. */ + if (!all_opens_permitted) + { + /* Go trough all permitted ports. */ + for (i = 0; i < num_permitted_opens; i++) + if (permitted_opens[i].port == host_port && + strcmp(permitted_opens[i].host, host) == 0) + break; + + /* Check if we found the requested port among those permitted. */ + if (i >= num_permitted_opens) + { + /* The port is not permitted. */ + log("Received request to connect to %.100s:%d, but the request was denied.", + host, host_port); + packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(remote_channel); + packet_send(); + } + } + + memset(&sin, 0, sizeof(sin)); + sin.sin_addr.s_addr = inet_addr(host); + if ((sin.sin_addr.s_addr & 0xffffffff) != 0xffffffff) + { + /* It was a valid numeric host address. */ + sin.sin_family = AF_INET; + } + else + { + /* Look up the host address from the name servers. */ + hp = gethostbyname(host); + if (!hp) + { + error("%.100s: unknown host.", host); + goto fail; + } + if (!hp->h_addr_list[0]) + { + error("%.100s: host has no IP address.", host); + goto fail; + } + sin.sin_family = hp->h_addrtype; + memcpy(&sin.sin_addr, hp->h_addr_list[0], + sizeof(sin.sin_addr)); + } + sin.sin_port = htons(host_port); + + /* Create the socket. */ + sock = socket(sin.sin_family, SOCK_STREAM, 0); + if (sock < 0) + { + error("socket: %.100s", strerror(errno)); + goto fail; + } + + /* Connect to the host/port. */ + if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + { + error("connect %.100s:%d: %.100s", host, host_port, + strerror(errno)); + close(sock); + goto fail; + } + + /* Successful connection. */ + + /* Allocate a channel for this connection. */ + newch = channel_allocate(SSH_CHANNEL_OPEN, sock, originator_string); + channels[newch].remote_id = remote_channel; + + /* Send a confirmation to the remote host. */ + packet_start(SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + packet_put_int(remote_channel); + packet_put_int(newch); + packet_send(); + + /* Free the argument string. */ + xfree(host); + + return; + + fail: + /* Free the argument string. */ + xfree(host); + + /* Send refusal to the remote host. */ + packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(remote_channel); + packet_send(); +} + +/* Creates an internet domain socket for listening for X11 connections. + Returns a suitable value for the DISPLAY variable, or NULL if an error + occurs. */ + +char *x11_create_display_inet(int screen_number) +{ + extern ServerOptions options; + int display_number, port, sock; + struct sockaddr_in sin; + char buf[512]; + char hostname[MAXHOSTNAMELEN]; + + for (display_number = options.x11_display_offset; display_number < MAX_DISPLAYS; display_number++) + { + port = 6000 + display_number; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(port); + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) + { + error("socket: %.100s", strerror(errno)); + return NULL; + } + + if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + { + debug("bind port %d: %.100s", port, strerror(errno)); + shutdown(sock, SHUT_RDWR); + close(sock); + continue; + } + break; + } + if (display_number >= MAX_DISPLAYS) + { + error("Failed to allocate internet-domain X11 display socket."); + return NULL; + } + + /* Start listening for connections on the socket. */ + if (listen(sock, 5) < 0) + { + error("listen: %.100s", strerror(errno)); + shutdown(sock, SHUT_RDWR); + close(sock); + return NULL; + } + + /* Set up a suitable value for the DISPLAY variable. */ + if (gethostname(hostname, sizeof(hostname)) < 0) + fatal("gethostname: %.100s", strerror(errno)); + snprintf(buf, sizeof buf, "%.400s:%d.%d", hostname, + display_number, screen_number); + + /* Allocate a channel for the socket. */ + (void)channel_allocate(SSH_CHANNEL_X11_LISTENER, sock, + xstrdup("X11 inet listener")); + + /* Return a suitable value for the DISPLAY environment variable. */ + return xstrdup(buf); +} + +#ifndef X_UNIX_PATH +#define X_UNIX_PATH "/tmp/.X11-unix/X" +#endif + +static +int +connect_local_xsocket(unsigned dnr) +{ + static const char *const x_sockets[] = { + X_UNIX_PATH "%u", + "/var/X/.X11-unix/X" "%u", + "/usr/spool/sockets/X11/" "%u", + NULL + }; + int sock; + struct sockaddr_un addr; + const char *const *path; + + for (path = x_sockets; *path; ++path) + { + 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, 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; +} + + +/* 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. */ + +void x11_input_open(int payload_len) +{ + int remote_channel, display_number, sock, newch; + const char *display; + struct sockaddr_in sin; + char buf[1024], *cp, *remote_host; + struct hostent *hp; + int remote_len; + + /* Get remote channel number. */ + remote_channel = packet_get_int(); + + /* Get remote originator name. */ + if (have_hostname_in_open) + remote_host = packet_get_string(&remote_len); + else + remote_host = xstrdup("unknown (remote did not supply name)"); + + debug("Received X11 open request."); + packet_integrity_check(payload_len, 4 + 4+remote_len, SSH_SMSG_X11_OPEN); + + /* Try to open a socket for the local X server. */ + display = getenv("DISPLAY"); + if (!display) + { + error("DISPLAY not set."); + goto fail; + } + + /* 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, "%d", &display_number) != 1) + { + error("Could not parse display number from DISPLAY: %.100s", + display); + goto fail; + } + /* Create a socket. */ + sock = connect_local_xsocket(display_number); + if (sock < 0) + goto fail; + + /* OK, we now have a connection to the display. */ + goto success; + } + + /* Connect to an inet socket. The DISPLAY value is supposedly + hostname:d[.s], where hostname may also be numeric IP address. */ + strncpy(buf, display, sizeof(buf)); + buf[sizeof(buf) - 1] = 0; + cp = strchr(buf, ':'); + if (!cp) + { + error("Could not find ':' in DISPLAY: %.100s", display); + goto fail; + } + *cp = 0; + /* buf now contains the host name. But first we parse the display number. */ + if (sscanf(cp + 1, "%d", &display_number) != 1) + { + error("Could not parse display number from DISPLAY: %.100s", + display); + goto fail; + } + + /* Try to parse the host name as a numeric IP address. */ + memset(&sin, 0, sizeof(sin)); + sin.sin_addr.s_addr = inet_addr(buf); + if ((sin.sin_addr.s_addr & 0xffffffff) != 0xffffffff) + { + /* It was a valid numeric host address. */ + sin.sin_family = AF_INET; + } + else + { + /* Not a numeric IP address. */ + /* Look up the host address from the name servers. */ + hp = gethostbyname(buf); + if (!hp) + { + error("%.100s: unknown host.", buf); + goto fail; + } + if (!hp->h_addr_list[0]) + { + error("%.100s: host has no IP address.", buf); + goto fail; + } + sin.sin_family = hp->h_addrtype; + memcpy(&sin.sin_addr, hp->h_addr_list[0], + sizeof(sin.sin_addr)); + } + /* Set port number. */ + sin.sin_port = htons(6000 + display_number); + + /* Create a socket. */ + sock = socket(sin.sin_family, SOCK_STREAM, 0); + if (sock < 0) + { + error("socket: %.100s", strerror(errno)); + goto fail; + } + /* Connect it to the display. */ + if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + { + error("connect %.100s:%d: %.100s", buf, 6000 + display_number, + strerror(errno)); + close(sock); + goto fail; + } + + success: + /* We have successfully obtained a connection to the real X display. */ + + /* Allocate a channel for this connection. */ + if (x11_saved_proto == NULL) + newch = channel_allocate(SSH_CHANNEL_OPEN, sock, remote_host); + else + newch = channel_allocate(SSH_CHANNEL_X11_OPEN, sock, remote_host); + channels[newch].remote_id = remote_channel; + + /* Send a confirmation to the remote host. */ + packet_start(SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + packet_put_int(remote_channel); + packet_put_int(newch); + packet_send(); + + return; + + fail: + /* Send refusal to the remote host. */ + packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(remote_channel); + packet_send(); +} + +/* Requests forwarding of X11 connections, generates fake authentication + data, and enables authentication spoofing. */ + +void x11_request_forwarding_with_spoofing(const char *proto, const char *data) +{ + unsigned int data_len = (unsigned int)strlen(data) / 2; + unsigned int i, value; + char *new_data; + int screen_number; + const char *cp; + u_int32_t rand = 0; + + cp = getenv("DISPLAY"); + if (cp) + cp = strchr(cp, ':'); + if (cp) + cp = strchr(cp, '.'); + if (cp) + screen_number = atoi(cp + 1); + else + screen_number = 0; + + /* 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) + rand = arc4random(); + x11_saved_data[i] = value; + x11_fake_data[i] = rand & 0xff; + rand >>= 8; + } + x11_saved_data_len = data_len; + x11_fake_data_len = data_len; + + /* Convert the fake data into hex. */ + new_data = xmalloc(2 * data_len + 1); + for (i = 0; i < data_len; i++) + sprintf(new_data + 2 * i, "%02x", (unsigned char)x11_fake_data[i]); + + /* Send the request packet. */ + packet_start(SSH_CMSG_X11_REQUEST_FORWARDING); + packet_put_string(proto, strlen(proto)); + packet_put_string(new_data, strlen(new_data)); + packet_put_int(screen_number); + packet_send(); + packet_write_wait(); + xfree(new_data); +} + +/* Sends a message to the server to request authentication fd forwarding. */ + +void auth_request_forwarding() +{ + packet_start(SSH_CMSG_AGENT_REQUEST_FORWARDING); + packet_send(); + packet_write_wait(); +} + +/* Returns the name of the forwarded authentication socket. Returns NULL + if there is no forwarded authentication socket. The returned value + points to a static buffer. */ + +char *auth_get_socket_name() +{ + return channel_forwarded_auth_socket_name; +} + +/* removes the agent forwarding socket */ + +void cleanup_socket(void) { + remove(channel_forwarded_auth_socket_name); + rmdir(channel_forwarded_auth_socket_dir); +} + +/* This if called to process SSH_CMSG_AGENT_REQUEST_FORWARDING on the server. + This starts forwarding authentication requests. */ + +void auth_input_request_forwarding(struct passwd *pw) +{ + int sock, newch; + struct sockaddr_un sunaddr; + + if (auth_get_socket_name() != NULL) + fatal("Protocol error: authentication forwarding requested twice."); + + /* Temporarily drop privileged uid for mkdir/bind. */ + temporarily_use_uid(pw->pw_uid); + + /* Allocate a buffer for the socket name, and format the name. */ + channel_forwarded_auth_socket_name = xmalloc(MAX_SOCKET_NAME); + channel_forwarded_auth_socket_dir = xmalloc(MAX_SOCKET_NAME); + strlcpy(channel_forwarded_auth_socket_dir, "/tmp/ssh-XXXXXXXX", MAX_SOCKET_NAME); + + /* Create private directory for socket */ + if (mkdtemp(channel_forwarded_auth_socket_dir) == NULL) + packet_disconnect("mkdtemp: %.100s", strerror(errno)); + snprintf(channel_forwarded_auth_socket_name, MAX_SOCKET_NAME, + "%s/agent.%d", channel_forwarded_auth_socket_dir, (int)getpid()); + + if (atexit(cleanup_socket) < 0) { + int saved=errno; + cleanup_socket(); + packet_disconnect("socket: %.100s", strerror(saved)); + } + + /* Create the socket. */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) + packet_disconnect("socket: %.100s", strerror(errno)); + + /* Bind it to the name. */ + memset(&sunaddr, 0, sizeof(sunaddr)); + sunaddr.sun_family = AF_UNIX; + strncpy(sunaddr.sun_path, channel_forwarded_auth_socket_name, + sizeof(sunaddr.sun_path)); + + if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) < 0) + packet_disconnect("bind: %.100s", strerror(errno)); + + /* Restore the privileged uid. */ + restore_uid(); + + /* Start listening on the socket. */ + if (listen(sock, 5) < 0) + packet_disconnect("listen: %.100s", strerror(errno)); + + /* Allocate a channel for the authentication agent socket. */ + newch = channel_allocate(SSH_CHANNEL_AUTH_SOCKET, sock, + xstrdup("auth socket")); + strcpy(channels[newch].path, channel_forwarded_auth_socket_name); +} + +/* This is called to process an SSH_SMSG_AGENT_OPEN message. */ + +void auth_input_open_request() +{ + int remch, sock, newch; + char *dummyname; + + /* Read the remote channel number from the message. */ + remch = packet_get_int(); + + /* Get a connection to the local authentication agent (this may again get + forwarded). */ + sock = ssh_get_authentication_socket(); + + /* If we could not connect the agent, send an error message back to + the server. This should never happen unless the agent + dies, because authentication forwarding is only enabled if we have an + agent. */ + if (sock < 0){ + packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(remch); + packet_send(); + return; + } + + debug("Forwarding authentication connection."); + + /* Dummy host name. This will be freed when the channel is freed; it will + still be valid in the packet_put_string below since the channel cannot + yet be freed at that point. */ + dummyname = xstrdup("authentication agent connection"); + + newch = channel_allocate(SSH_CHANNEL_OPEN, sock, dummyname); + channels[newch].remote_id = remch; + + /* Send a confirmation to the remote host. */ + packet_start(SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + packet_put_int(remch); + packet_put_int(newch); + packet_send(); +} diff --git a/channels.h b/channels.h new file mode 100644 index 00000000..9794ef50 --- /dev/null +++ b/channels.h @@ -0,0 +1,41 @@ +/* RCSID("$Id: channels.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef CHANNELS_H +#define CHANNELS_H + +/* Definitions for channel types. */ +#define SSH_CHANNEL_FREE 0 /* This channel is free (unused). */ +#define SSH_CHANNEL_X11_LISTENER 1 /* Listening for inet X11 conn. */ +#define SSH_CHANNEL_PORT_LISTENER 2 /* Listening on a port. */ +#define SSH_CHANNEL_OPENING 3 /* waiting for confirmation */ +#define SSH_CHANNEL_OPEN 4 /* normal open two-way channel */ +#define SSH_CHANNEL_CLOSED 5 /* waiting for close confirmation */ +/* SSH_CHANNEL_AUTH_FD 6 authentication fd */ +#define SSH_CHANNEL_AUTH_SOCKET 7 /* authentication socket */ +/* SSH_CHANNEL_AUTH_SOCKET_FD 8 connection to auth socket */ +#define SSH_CHANNEL_X11_OPEN 9 /* reading first X11 packet */ +#define SSH_CHANNEL_INPUT_DRAINING 10 /* sending remaining data to conn */ +#define SSH_CHANNEL_OUTPUT_DRAINING 11 /* sending remaining data to app */ + +/* Data structure for channel data. This is iniailized in channel_allocate + and cleared in channel_free. */ + +typedef struct Channel +{ + int type; /* channel type/state */ + int self; /* my own channel identifier */ + int remote_id; /* channel identifier for remote peer */ + /* peer can be reached over encrypted connection, via packet-sent */ + int istate; + int ostate; + int x11; + int sock; /* data socket, linked to this channel */ + Buffer input; /* data read from socket, to be sent over encrypted connection */ + Buffer output; /* data received over encrypted connection for send on socket */ + char path[200]; /* path for unix domain sockets, or host name for forwards */ + int listening_port; /* port being listened for forwards */ + int host_port; /* remote port to connect for forwards */ + char *remote_name; /* remote hostname */ +} Channel; + +#endif diff --git a/cipher.c b/cipher.c new file mode 100644 index 00000000..b47e7ecd --- /dev/null +++ b/cipher.c @@ -0,0 +1,304 @@ +/* + +cipher.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Wed Apr 19 17:41:39 1995 ylo + +*/ + +#include "includes.h" +RCSID("$Id: cipher.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "ssh.h" +#include "cipher.h" + +#include <openssl/md5.h> + +/* + * What kind of tripple DES are these 2 routines? + * + * Why is there a redundant initialization vector? + * + * If only iv3 was used, then, this would till effect have been + * outer-cbc. However, there is also a private iv1 == iv2 which + * perhaps makes differential analysis easier. On the other hand, the + * private iv1 probably makes the CRC-32 attack ineffective. This is a + * result of that there is no longer any known iv1 to use when + * choosing the X block. + */ +void +SSH_3CBC_ENCRYPT(des_key_schedule ks1, + des_key_schedule ks2, des_cblock *iv2, + des_key_schedule ks3, des_cblock *iv3, + void *dest, void *src, + unsigned int len) +{ + des_cblock iv1; + + memcpy(&iv1, iv2, 8); + + des_cbc_encrypt(src, dest, len, ks1, &iv1, DES_ENCRYPT); + memcpy(&iv1, dest + len - 8, 8); + + des_cbc_encrypt(dest, dest, len, ks2, iv2, DES_DECRYPT); + memcpy(iv2, &iv1, 8); /* Note how iv1 == iv2 on entry and exit. */ + + des_cbc_encrypt(dest, dest, len, ks3, iv3, DES_ENCRYPT); + memcpy(iv3, dest + len - 8, 8); +} + +void +SSH_3CBC_DECRYPT(des_key_schedule ks1, + des_key_schedule ks2, des_cblock *iv2, + des_key_schedule ks3, des_cblock *iv3, + void *dest, void *src, + unsigned int len) +{ + des_cblock iv1; + + memcpy(&iv1, iv2, 8); + + des_cbc_encrypt(src, dest, len, ks3, iv3, DES_DECRYPT); + memcpy(iv3, src + len - 8, 8); + + des_cbc_encrypt(dest, dest, len, ks2, iv2, DES_ENCRYPT); + memcpy(iv2, dest + len - 8, 8); + + des_cbc_encrypt(dest, dest, len, ks1, &iv1, DES_DECRYPT); + /* memcpy(&iv1, iv2, 8); */ /* Note how iv1 == iv2 on entry and exit. */ +} + +/* + * SSH uses a variation on Blowfish, all bytes must be swapped before + * and after encryption/decryption. Thus the swap_bytes stuff (yuk). + */ +static +void +swap_bytes(const unsigned char *src, unsigned char *dst_, int n) +{ + u_int32_t *dst = (u_int32_t *)dst_; /* dst must be properly aligned. */ + union { + u_int32_t i; + char c[4]; + } t; + + /* assert((n & 7) == 0); */ + + /* Process 8 bytes every lap. */ + for (n = n / 8; n > 0; n--) + { + t.c[3] = *src++; + t.c[2] = *src++; + t.c[1] = *src++; + t.c[0] = *src++; + *dst++ = t.i; + + t.c[3] = *src++; + t.c[2] = *src++; + t.c[1] = *src++; + t.c[0] = *src++; + *dst++ = t.i; + } +} + +void (*cipher_attack_detected)(const char *fmt, ...) = fatal; + +static inline +void +detect_cbc_attack(const unsigned char *src, + unsigned int len) +{ + return; + + log("CRC-32 CBC insertion attack detected"); + cipher_attack_detected("CRC-32 CBC insertion attack detected"); +} + +/* Names of all encryption algorithms. These must match the numbers defined + int cipher.h. */ +static char *cipher_names[] = +{ + "none", + "idea", + "des", + "3des", + "tss", + "rc4", + "blowfish" +}; + +/* Returns a bit mask indicating which ciphers are supported by this + implementation. The bit mask has the corresponding bit set of each + supported cipher. */ + +unsigned int cipher_mask() +{ + unsigned int mask = 0; + mask |= 1 << SSH_CIPHER_3DES; /* Mandatory */ + mask |= 1 << SSH_CIPHER_BLOWFISH; + return mask; +} + +/* Returns the name of the cipher. */ + +const +char *cipher_name(int cipher) +{ + if (cipher < 0 || cipher >= sizeof(cipher_names) / sizeof(cipher_names[0]) || + cipher_names[cipher] == NULL) + fatal("cipher_name: bad cipher number: %d", cipher); + return cipher_names[cipher]; +} + +/* Parses the name of the cipher. Returns the number of the corresponding + cipher, or -1 on error. */ + +int +cipher_number(const char *name) +{ + int i; + for (i = 0; i < sizeof(cipher_names) / sizeof(cipher_names[0]); i++) + if (strcmp(cipher_names[i], name) == 0 && + (cipher_mask() & (1 << i))) + return i; + return -1; +} + +/* Selects the cipher, and keys if by computing the MD5 checksum of the + passphrase and using the resulting 16 bytes as the key. */ + +void cipher_set_key_string(CipherContext *context, int cipher, + const char *passphrase, int for_encryption) +{ + MD5_CTX md; + unsigned char digest[16]; + + MD5_Init(&md); + MD5_Update(&md, (const unsigned char *)passphrase, strlen(passphrase)); + MD5_Final(digest, &md); + + cipher_set_key(context, cipher, digest, 16, for_encryption); + + memset(digest, 0, sizeof(digest)); + memset(&md, 0, sizeof(md)); +} + +/* Selects the cipher to use and sets the key. */ + +void cipher_set_key(CipherContext *context, int cipher, + const unsigned char *key, int keylen, int for_encryption) +{ + unsigned char padded[32]; + + /* Set cipher type. */ + context->type = cipher; + + /* Get 32 bytes of key data. Pad if necessary. (So that code below does + not need to worry about key size). */ + memset(padded, 0, sizeof(padded)); + memcpy(padded, key, keylen < sizeof(padded) ? keylen : sizeof(padded)); + + /* Initialize the initialization vector. */ + switch (cipher) + { + case SSH_CIPHER_NONE: + /* Has to stay for authfile saving of private key with no passphrase */ + break; + + case SSH_CIPHER_3DES: + /* Note: the least significant bit of each byte of key is parity, + and must be ignored by the implementation. 16 bytes of key are + used (first and last keys are the same). */ + if (keylen < 16) + error("Key length %d is insufficient for 3DES.", keylen); + des_set_key((void*)padded, context->u.des3.key1); + des_set_key((void*)(padded + 8), context->u.des3.key2); + if (keylen <= 16) + des_set_key((void*)padded, context->u.des3.key3); + else + des_set_key((void*)(padded + 16), context->u.des3.key3); + memset(context->u.des3.iv2, 0, sizeof(context->u.des3.iv2)); + memset(context->u.des3.iv3, 0, sizeof(context->u.des3.iv3)); + break; + + case SSH_CIPHER_BLOWFISH: + BF_set_key(&context->u.bf.key, keylen, padded); + memset(context->u.bf.iv, 0, 8); + break; + + default: + fatal("cipher_set_key: unknown cipher: %d", cipher); + } + memset(padded, 0, sizeof(padded)); +} + +/* Encrypts data using the cipher. */ + +void cipher_encrypt(CipherContext *context, unsigned char *dest, + const unsigned char *src, unsigned int len) +{ + assert((len & 7) == 0); + + switch (context->type) + { + case SSH_CIPHER_NONE: + memcpy(dest, src, len); + break; + + case SSH_CIPHER_3DES: + SSH_3CBC_ENCRYPT(context->u.des3.key1, + context->u.des3.key2, &context->u.des3.iv2, + context->u.des3.key3, &context->u.des3.iv3, + dest, (void*)src, len); + break; + + case SSH_CIPHER_BLOWFISH: + swap_bytes(src, dest, len); + BF_cbc_encrypt(dest, dest, len, + &context->u.bf.key, context->u.bf.iv, BF_ENCRYPT); + swap_bytes(dest, dest, len); + break; + + default: + fatal("cipher_encrypt: unknown cipher: %d", context->type); + } +} + +/* Decrypts data using the cipher. */ + +void cipher_decrypt(CipherContext *context, unsigned char *dest, + const unsigned char *src, unsigned int len) +{ + assert((len & 7) == 0); + + switch (context->type) + { + case SSH_CIPHER_NONE: + memcpy(dest, src, len); + break; + + case SSH_CIPHER_3DES: + /* CRC-32 attack? */ + SSH_3CBC_DECRYPT(context->u.des3.key1, + context->u.des3.key2, &context->u.des3.iv2, + context->u.des3.key3, &context->u.des3.iv3, + dest, (void*)src, len); + break; + + case SSH_CIPHER_BLOWFISH: + detect_cbc_attack(src, len); + swap_bytes(src, dest, len); + BF_cbc_encrypt((void*)dest, dest, len, + &context->u.bf.key, context->u.bf.iv, BF_DECRYPT); + swap_bytes(dest, dest, len); + break; + + default: + fatal("cipher_decrypt: unknown cipher: %d", context->type); + } +} diff --git a/cipher.h b/cipher.h new file mode 100644 index 00000000..4ecb8f8d --- /dev/null +++ b/cipher.h @@ -0,0 +1,84 @@ +/* + +cipher.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Wed Apr 19 16:50:42 1995 ylo + +*/ + +/* RCSID("$Id: cipher.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef CIPHER_H +#define CIPHER_H + +#include <openssl/des.h> +#include <openssl/blowfish.h> + +/* Cipher types. New types can be added, but old types should not be removed + for compatibility. The maximum allowed value is 31. */ +#define SSH_CIPHER_NOT_SET -1 /* None selected (invalid number). */ +#define SSH_CIPHER_NONE 0 /* no encryption */ +#define SSH_CIPHER_IDEA 1 /* IDEA CFB */ +#define SSH_CIPHER_DES 2 /* DES CBC */ +#define SSH_CIPHER_3DES 3 /* 3DES CBC */ +#define SSH_CIPHER_TSS 4 /* TRI's Simple Stream encryption CBC */ +#define SSH_CIPHER_RC4 5 /* Alleged RC4 */ +#define SSH_CIPHER_BLOWFISH 6 + +typedef struct { + unsigned int type; + union { + struct { + des_key_schedule key1; + des_key_schedule key2; + des_cblock iv2; + des_key_schedule key3; + des_cblock iv3; + } des3; + struct { + struct bf_key_st key; + unsigned char iv[8]; + } bf; + } u; +} CipherContext; + +/* Returns a bit mask indicating which ciphers are supported by this + implementation. The bit mask has the corresponding bit set of each + supported cipher. */ +unsigned int cipher_mask(); + +/* Returns the name of the cipher. */ +const char *cipher_name(int cipher); + +/* Parses the name of the cipher. Returns the number of the corresponding + cipher, or -1 on error. */ +int cipher_number(const char *name); + +/* Selects the cipher to use and sets the key. If for_encryption is true, + the key is setup for encryption; otherwise it is setup for decryption. */ +void cipher_set_key(CipherContext *context, int cipher, + const unsigned char *key, int keylen, int for_encryption); + +/* Sets key for the cipher by computing the MD5 checksum of the passphrase, + and using the resulting 16 bytes as the key. */ +void cipher_set_key_string(CipherContext *context, int cipher, + const char *passphrase, int for_encryption); + +/* Encrypts data using the cipher. */ +void cipher_encrypt(CipherContext *context, unsigned char *dest, + const unsigned char *src, unsigned int len); + +/* Decrypts data using the cipher. */ +void cipher_decrypt(CipherContext *context, unsigned char *dest, + const unsigned char *src, unsigned int len); + +/* If and CRC-32 attack is detected this function is called. Defaults + * to fatal, changed to packet_disconnect in sshd and ssh. */ +extern void (*cipher_attack_detected)(const char *fmt, ...); + +#endif /* CIPHER_H */ diff --git a/clientloop.c b/clientloop.c new file mode 100644 index 00000000..43373b72 --- /dev/null +++ b/clientloop.c @@ -0,0 +1,924 @@ +/* + +clientloop.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + + +Created: Sat Sep 23 12:23:57 1995 ylo + +The main loop for the interactive session (client side). + +*/ + +#include "includes.h" +RCSID("$Id: clientloop.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "xmalloc.h" +#include "ssh.h" +#include "packet.h" +#include "buffer.h" +#include "authfd.h" + +/* Flag indicating whether quiet mode is on. */ +extern int quiet_flag; + +/* Flag indicating that stdin should be redirected from /dev/null. */ +extern int stdin_null_flag; + +/* Name of the host we are connecting to. This is the name given on the + command line, or the HostName specified for the user-supplied name + in a configuration file. */ +extern char *host; + +/* Flag to indicate that we have received a window change signal which has + not yet been processed. This will cause a message indicating the new + window size to be sent to the server a little later. This is volatile + because this is updated in a signal handler. */ +static volatile int received_window_change_signal = 0; + +/* Terminal modes, as saved by enter_raw_mode. */ +static struct termios saved_tio; + +/* Flag indicating whether we are in raw mode. This is used by enter_raw_mode + and leave_raw_mode. */ +static int in_raw_mode = 0; + +/* Flag indicating whether the user\'s terminal is in non-blocking mode. */ +static int in_non_blocking_mode = 0; + +/* Common data for the client loop code. */ +static int escape_pending; /* Last character was the escape character */ +static int last_was_cr; /* Last character was a newline. */ +static int exit_status; /* Used to store the exit status of the command. */ +static int stdin_eof; /* EOF has been encountered on standard error. */ +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 unsigned int buffer_high; /* Soft max buffer size. */ +static int max_fd; /* Maximum file descriptor number in select(). */ +static int connection_in; /* Connection to server (input). */ +static int connection_out; /* Connection to server (output). */ +static unsigned long stdin_bytes, stdout_bytes, stderr_bytes; +static int quit_pending; /* Set to non-zero to quit the client loop. */ +static int escape_char; /* Escape character. */ + +/* Returns the user\'s terminal to normal mode if it had been put in raw + mode. */ + +void leave_raw_mode() +{ + if (!in_raw_mode) + return; + in_raw_mode = 0; + if (tcsetattr(fileno(stdin), TCSADRAIN, &saved_tio) < 0) + perror("tcsetattr"); + + fatal_remove_cleanup((void (*)(void *))leave_raw_mode, NULL); +} + +/* Puts the user\'s terminal in raw mode. */ + +void enter_raw_mode() +{ + struct termios tio; + + if (tcgetattr(fileno(stdin), &tio) < 0) + perror("tcgetattr"); + saved_tio = tio; + tio.c_iflag |= IGNPAR; + tio.c_iflag &= ~(ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXANY|IXOFF); + tio.c_lflag &= ~(ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHONL); +#ifdef IEXTEN + tio.c_lflag &= ~IEXTEN; +#endif /* IEXTEN */ + tio.c_oflag &= ~OPOST; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + if (tcsetattr(fileno(stdin), TCSADRAIN, &tio) < 0) + perror("tcsetattr"); + in_raw_mode = 1; + + fatal_add_cleanup((void (*)(void *))leave_raw_mode, NULL); +} + +/* Puts stdin terminal in non-blocking mode. */ + +/* Restores stdin to blocking mode. */ + +void leave_non_blocking() +{ + if (in_non_blocking_mode) + { + (void)fcntl(fileno(stdin), F_SETFL, 0); + in_non_blocking_mode = 0; + fatal_remove_cleanup((void (*)(void *))leave_non_blocking, NULL); + } +} + +void enter_non_blocking() +{ + in_non_blocking_mode = 1; + (void)fcntl(fileno(stdin), F_SETFL, O_NONBLOCK); + fatal_add_cleanup((void (*)(void *))leave_non_blocking, NULL); +} + +/* Signal handler for the window change signal (SIGWINCH). This just + sets a flag indicating that the window has changed. */ + +void window_change_handler(int sig) +{ + received_window_change_signal = 1; + signal(SIGWINCH, window_change_handler); +} + +/* Signal handler for signals that cause the program to terminate. These + signals must be trapped to restore terminal modes. */ + +void signal_handler(int sig) +{ + if (in_raw_mode) + leave_raw_mode(); + if (in_non_blocking_mode) + leave_non_blocking(); + channel_stop_listening(); + packet_close(); + fatal("Killed by signal %d.", sig); +} + +/* Returns current time in seconds from Jan 1, 1970 with the maximum available + resolution. */ + +double get_current_time() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0; +} + +/* This is called when the interactive is entered. This checks if there + is an EOF coming on stdin. We must check this explicitly, as select() + does not appear to wake up when redirecting from /dev/null. */ + +void client_check_initial_eof_on_stdin() +{ + int len; + char buf[1]; + + /* If standard input is to be "redirected from /dev/null", we simply + mark that we have seen an EOF and send an EOF message to the server. + Otherwise, we try to read a single character; it appears that for some + files, such /dev/null, select() never wakes up for read for this + descriptor, which means that we never get EOF. This way we will get + the EOF if stdin comes from /dev/null or similar. */ + if (stdin_null_flag) + { + /* Fake EOF on stdin. */ + debug("Sending eof."); + stdin_eof = 1; + packet_start(SSH_CMSG_EOF); + packet_send(); + } + else + { + /* Enter non-blocking mode for stdin. */ + enter_non_blocking(); + + /* Check for immediate EOF on stdin. */ + len = read(fileno(stdin), buf, 1); + if (len == 0) + { + /* EOF. Record that we have seen it and send EOF to server. */ + debug("Sending eof."); + stdin_eof = 1; + packet_start(SSH_CMSG_EOF); + packet_send(); + } + else + if (len > 0) + { + /* Got data. We must store the data in the buffer, and also + process it as an escape character if appropriate. */ + if ((unsigned char)buf[0] == escape_char) + escape_pending = 1; + else + { + buffer_append(&stdin_buffer, buf, 1); + stdin_bytes += 1; + } + } + + /* Leave non-blocking mode. */ + leave_non_blocking(); + } +} + +/* Get packets from the connection input buffer, and process them as long + as there are packets available. */ + +void client_process_buffered_input_packets() +{ + int type; + char *data; + unsigned int data_len; + int payload_len; + + /* Process any buffered packets from the server. */ + while (!quit_pending && (type = packet_read_poll(&payload_len)) != SSH_MSG_NONE) + { + switch (type) + { + + case SSH_SMSG_STDOUT_DATA: + data = packet_get_string(&data_len); + packet_integrity_check(payload_len, 4 + data_len, type); + buffer_append(&stdout_buffer, data, data_len); + stdout_bytes += data_len; + memset(data, 0, data_len); + xfree(data); + break; + + case SSH_SMSG_STDERR_DATA: + data = packet_get_string(&data_len); + packet_integrity_check(payload_len, 4 + data_len, type); + buffer_append(&stderr_buffer, data, data_len); + stdout_bytes += data_len; + memset(data, 0, data_len); + xfree(data); + break; + + case SSH_SMSG_EXITSTATUS: + packet_integrity_check(payload_len, 4, type); + exit_status = packet_get_int(); + /* Acknowledge the exit. */ + packet_start(SSH_CMSG_EXIT_CONFIRMATION); + packet_send(); + /* Must wait for packet to be sent since we are exiting the + loop. */ + packet_write_wait(); + /* Flag that we want to exit. */ + quit_pending = 1; + break; + + case SSH_SMSG_X11_OPEN: + x11_input_open(payload_len); + break; + + case SSH_MSG_PORT_OPEN: + channel_input_port_open(payload_len); + break; + + case SSH_SMSG_AGENT_OPEN: + packet_integrity_check(payload_len, 4, type); + auth_input_open_request(); + break; + + case SSH_MSG_CHANNEL_OPEN_CONFIRMATION: + packet_integrity_check(payload_len, 4 + 4, type); + channel_input_open_confirmation(); + break; + + case SSH_MSG_CHANNEL_OPEN_FAILURE: + packet_integrity_check(payload_len, 4, type); + channel_input_open_failure(); + break; + + case SSH_MSG_CHANNEL_DATA: + channel_input_data(payload_len); + break; + + case SSH_MSG_CHANNEL_CLOSE: + packet_integrity_check(payload_len, 4, type); + channel_input_close(); + break; + + case SSH_MSG_CHANNEL_CLOSE_CONFIRMATION: + packet_integrity_check(payload_len, 4, type); + channel_input_close_confirmation(); + break; + + default: + /* Any unknown packets received during the actual session + cause the session to terminate. This is intended to make + debugging easier since no confirmations are sent. Any + compatible protocol extensions must be negotiated during + the preparatory phase. */ + packet_disconnect("Protocol error during session: type %d", + type); + } + } +} + +/* Make packets from buffered stdin data, and buffer them for sending to + the connection. */ + +void client_make_packets_from_stdin_data() +{ + unsigned int len; + + /* Send buffered stdin data to the server. */ + while (buffer_len(&stdin_buffer) > 0 && + packet_not_very_much_data_to_write()) + { + len = buffer_len(&stdin_buffer); + if (len > 32768) + len = 32768; /* Keep the packets at reasonable size. */ + packet_start(SSH_CMSG_STDIN_DATA); + packet_put_string(buffer_ptr(&stdin_buffer), len); + packet_send(); + buffer_consume(&stdin_buffer, len); + /* If we have a pending EOF, send it now. */ + if (stdin_eof && buffer_len(&stdin_buffer) == 0) + { + packet_start(SSH_CMSG_EOF); + packet_send(); + } + } +} + +/* Checks if the client window has changed, and sends a packet about it to + the server if so. The actual change is detected elsewhere (by a software + interrupt on Unix); this just checks the flag and sends a message if + appropriate. */ + +void client_check_window_change() +{ + /* Send possible window change message to the server. */ + if (received_window_change_signal) + { + struct winsize ws; + + /* Clear the window change indicator. */ + received_window_change_signal = 0; + + /* Read new window size. */ + if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) >= 0) + { + /* Successful, send the packet now. */ + packet_start(SSH_CMSG_WINDOW_SIZE); + packet_put_int(ws.ws_row); + packet_put_int(ws.ws_col); + packet_put_int(ws.ws_xpixel); + packet_put_int(ws.ws_ypixel); + packet_send(); + } + } +} + +/* Waits until the client can do something (some data becomes available on + one of the file descriptors). */ + +void client_wait_until_can_do_something(fd_set *readset, fd_set *writeset) +{ + /* Initialize select masks. */ + FD_ZERO(readset); + + /* Read from the connection, unless our buffers are full. */ + if (buffer_len(&stdout_buffer) < buffer_high && + buffer_len(&stderr_buffer) < buffer_high && + channel_not_very_much_buffered_data()) + FD_SET(connection_in, readset); + + /* Read from stdin, unless we have seen EOF or have very much buffered + data to send to the server. */ + if (!stdin_eof && packet_not_very_much_data_to_write()) + FD_SET(fileno(stdin), readset); + + FD_ZERO(writeset); + + /* Add any selections by the channel mechanism. */ + channel_prepare_select(readset, writeset); + + /* Select server connection if have data to write to the server. */ + if (packet_have_data_to_write()) + FD_SET(connection_out, writeset); + + /* Select stdout if have data in buffer. */ + if (buffer_len(&stdout_buffer) > 0) + FD_SET(fileno(stdout), writeset); + + /* Select stderr if have data in buffer. */ + if (buffer_len(&stderr_buffer) > 0) + FD_SET(fileno(stderr), writeset); + + /* Update maximum file descriptor number, if appropriate. */ + if (channel_max_fd() > max_fd) + max_fd = channel_max_fd(); + + /* Wait for something to happen. This will suspend the process until + some selected descriptor can be read, written, or has some other + event pending. Note: if you want to implement SSH_MSG_IGNORE + messages to fool traffic analysis, this might be the place to do + it: just have a random timeout for the select, and send a random + SSH_MSG_IGNORE packet when the timeout expires. */ + if (select(max_fd + 1, readset, writeset, NULL, NULL) < 0) + { + char buf[100]; + /* Some systems fail to clear these automatically. */ + FD_ZERO(readset); + FD_ZERO(writeset); + if (errno == EINTR) + return; + /* Note: we might still have data in the buffers. */ + snprintf(buf, sizeof buf, "select: %s\r\n", strerror(errno)); + buffer_append(&stderr_buffer, buf, strlen(buf)); + stderr_bytes += strlen(buf); + quit_pending = 1; + } +} + +void client_suspend_self() +{ + struct winsize oldws, newws; + + /* Flush stdout and stderr buffers. */ + if (buffer_len(&stdout_buffer) > 0) + write(fileno(stdout), + buffer_ptr(&stdout_buffer), + buffer_len(&stdout_buffer)); + if (buffer_len(&stderr_buffer) > 0) + write(fileno(stderr), + buffer_ptr(&stderr_buffer), + buffer_len(&stderr_buffer)); + + /* Leave raw mode. */ + leave_raw_mode(); + + /* Free (and clear) the buffer to reduce the + amount of data that gets written to swap. */ + buffer_free(&stdin_buffer); + buffer_free(&stdout_buffer); + buffer_free(&stderr_buffer); + + /* Save old window size. */ + ioctl(fileno(stdin), TIOCGWINSZ, &oldws); + + /* Send the suspend signal to the program + itself. */ + kill(getpid(), SIGTSTP); + + /* Check if the window size has changed. */ + if (ioctl(fileno(stdin), TIOCGWINSZ, &newws) >= 0 && + (oldws.ws_row != newws.ws_row || oldws.ws_col != newws.ws_col || + oldws.ws_xpixel != newws.ws_xpixel || + oldws.ws_ypixel != newws.ws_ypixel)) + received_window_change_signal = 1; + + /* OK, we have been continued by the user. + Reinitialize buffers. */ + buffer_init(&stdin_buffer); + buffer_init(&stdout_buffer); + buffer_init(&stderr_buffer); + + /* Re-enter raw mode. */ + enter_raw_mode(); +} + +void client_process_input(fd_set *readset) +{ + int len, pid; + char buf[8192], *s; + + /* Read input from the server, and add any such data to the buffer of the + packet subsystem. */ + if (FD_ISSET(connection_in, readset)) + { + /* Read as much as possible. */ + len = read(connection_in, buf, sizeof(buf)); + if (len == 0) + { + /* Received EOF. The remote host has closed the connection. */ + snprintf(buf, sizeof buf, "Connection to %.300s closed by remote host.\r\n", + host); + buffer_append(&stderr_buffer, buf, strlen(buf)); + stderr_bytes += strlen(buf); + quit_pending = 1; + return; + } + + /* There is a kernel bug on Solaris that causes select to sometimes + wake up even though there is no data available. */ + if (len < 0 && errno == EAGAIN) + len = 0; + + if (len < 0) + { + /* An error has encountered. Perhaps there is a network + problem. */ + snprintf(buf, sizeof buf, "Read from remote host %.300s: %.100s\r\n", + host, strerror(errno)); + buffer_append(&stderr_buffer, buf, strlen(buf)); + stderr_bytes += strlen(buf); + quit_pending = 1; + return; + } + packet_process_incoming(buf, len); + } + + /* Read input from stdin. */ + if (FD_ISSET(fileno(stdin), readset)) + { + /* Read as much as possible. */ + len = read(fileno(stdin), buf, sizeof(buf)); + if (len <= 0) + { + /* Received EOF or error. They are treated similarly, + except that an error message is printed if it was + an error condition. */ + if (len < 0) + { + snprintf(buf, sizeof buf, "read: %.100s\r\n", strerror(errno)); + buffer_append(&stderr_buffer, buf, strlen(buf)); + stderr_bytes += strlen(buf); + } + /* Mark that we have seen EOF. */ + stdin_eof = 1; + /* Send an EOF message to the server unless there is data + in the buffer. If there is data in the buffer, no message + will be sent now. Code elsewhere will send the EOF + when the buffer becomes empty if stdin_eof is set. */ + if (buffer_len(&stdin_buffer) == 0) + { + packet_start(SSH_CMSG_EOF); + packet_send(); + } + } + else + if (escape_char == -1) + { + /* Normal successful read, and no escape character. Just + append the data to buffer. */ + buffer_append(&stdin_buffer, buf, len); + stdin_bytes += len; + } + else + { + /* Normal, successful read. But we have an escape character + and have to process the characters one by one. */ + unsigned int i; + for (i = 0; i < len; i++) + { + unsigned char ch; + /* Get one character at a time. */ + ch = buf[i]; + + /* Check if we have a pending escape character. */ + if (escape_pending) + { + /* We have previously seen an escape character. */ + /* Clear the flag now. */ + escape_pending = 0; + /* Process the escaped character. */ + switch (ch) + { + case '.': + /* Terminate the connection. */ + snprintf(buf, sizeof buf, "%c.\r\n", escape_char); + buffer_append(&stderr_buffer, buf, strlen(buf)); + stderr_bytes += strlen(buf); + quit_pending = 1; + return; + + case 'Z' - 64: + /* Suspend the program. */ + /* Print a message to that effect to the user. */ + snprintf(buf, sizeof buf, "%c^Z\r\n", escape_char); + buffer_append(&stderr_buffer, buf, strlen(buf)); + stderr_bytes += strlen(buf); + + /* Restore terminal modes and suspend. */ + client_suspend_self(); + + /* We have been continued. */ + continue; + + case '&': + /* Detach the program (continue to serve connections, + but put in background and no more new + connections). */ + if (!stdin_eof) + { + /* Sending SSH_CMSG_EOF alone does not always + appear to be enough. So we try to send an + EOF character first. */ + packet_start(SSH_CMSG_STDIN_DATA); + packet_put_string("\004", 1); + packet_send(); + /* Close stdin. */ + stdin_eof = 1; + if (buffer_len(&stdin_buffer) == 0) + { + packet_start(SSH_CMSG_EOF); + packet_send(); + } + } + /* Restore tty modes. */ + leave_raw_mode(); + + /* Stop listening for new connections. */ + channel_stop_listening(); + + printf("%c& [backgrounded]\n", escape_char); + + /* Fork into background. */ + pid = fork(); + if (pid < 0) + { + error("fork: %.100s", strerror(errno)); + continue; + } + if (pid != 0) + { /* This is the parent. */ + /* The parent just exits. */ + exit(0); + } + + /* The child continues serving connections. */ + continue; + + case '?': + snprintf(buf, sizeof buf, "%c?\r\n\ +Supported escape sequences:\r\n\ +~. - terminate connection\r\n\ +~^Z - suspend ssh\r\n\ +~# - list forwarded connections\r\n\ +~& - background ssh (when waiting for connections to terminate)\r\n\ +~? - this message\r\n\ +~~ - send the escape character by typing it twice\r\n\ +(Note that escapes are only recognized immediately after newline.)\r\n", + escape_char); + buffer_append(&stderr_buffer, buf, strlen(buf)); + continue; + + case '#': + snprintf(buf, sizeof buf, "%c#\r\n", escape_char); + buffer_append(&stderr_buffer, buf, strlen(buf)); + s = channel_open_message(); + buffer_append(&stderr_buffer, s, strlen(s)); + xfree(s); + continue; + + default: + if (ch != escape_char) + { + /* Escape character followed by non-special + character. Append both to the input + buffer. */ + buf[0] = escape_char; + buf[1] = ch; + buffer_append(&stdin_buffer, buf, 2); + stdin_bytes += 2; + continue; + } + /* Note that escape character typed twice falls through + here; the latter gets processed as a normal + character below. */ + break; + } + } + else + { + /* The previous character was not an escape char. + Check if this is an escape. */ + if (last_was_cr && ch == escape_char) + { + /* It is. Set the flag and continue to next + character. */ + escape_pending = 1; + continue; + } + } + + /* Normal character. Record whether it was a newline, + and append it to the buffer. */ + last_was_cr = (ch == '\r' || ch == '\n'); + buf[0] = ch; + buffer_append(&stdin_buffer, buf, 1); + stdin_bytes += 1; + continue; + } + } + } +} + +void client_process_output(fd_set *writeset) +{ + int len; + char buf[100]; + + /* Write buffered output to stdout. */ + if (FD_ISSET(fileno(stdout), writeset)) + { + /* Write as much data as possible. */ + len = write(fileno(stdout), buffer_ptr(&stdout_buffer), + buffer_len(&stdout_buffer)); + if (len <= 0) + { + if (errno == EAGAIN) + len = 0; + else + { + /* An error or EOF was encountered. Put an error message + to stderr buffer. */ + snprintf(buf, sizeof buf, "write stdout: %.50s\r\n", strerror(errno)); + buffer_append(&stderr_buffer, buf, strlen(buf)); + stderr_bytes += strlen(buf); + quit_pending = 1; + return; + } + } + /* Consume printed data from the buffer. */ + buffer_consume(&stdout_buffer, len); + } + + /* Write buffered output to stderr. */ + if (FD_ISSET(fileno(stderr), writeset)) + { + /* Write as much data as possible. */ + len = write(fileno(stderr), buffer_ptr(&stderr_buffer), + buffer_len(&stderr_buffer)); + if (len <= 0) { + if (errno == EAGAIN) + len = 0; + else + { + /* EOF or error, but can't even print error message. */ + quit_pending = 1; + return; + } + } + /* Consume printed characters from the buffer. */ + buffer_consume(&stderr_buffer, len); + } +} + +/* Implements the interactive session with the server. This is called + after the user has been authenticated, and a command has been + started on the remote host. If escape_char != -1, it is the character + used as an escape character for terminating or suspending the + session. */ + +int client_loop(int have_pty, int escape_char_arg) +{ + double start_time, total_time; + int len; + char buf[100]; + + debug("Entering interactive session."); + + start_time = get_current_time(); + + /* Initialize variables. */ + escape_pending = 0; + last_was_cr = 1; + exit_status = -1; + stdin_eof = 0; + buffer_high = 64 * 1024; + connection_in = packet_get_connection_in(); + connection_out = packet_get_connection_out(); + max_fd = connection_in; + if (connection_out > max_fd) + max_fd = connection_out; + stdin_bytes = 0; + stdout_bytes = 0; + stderr_bytes = 0; + quit_pending = 0; + escape_char = escape_char_arg; + + /* Initialize buffers. */ + buffer_init(&stdin_buffer); + buffer_init(&stdout_buffer); + buffer_init(&stderr_buffer); + + /* Set signal handlers to restore non-blocking mode. */ + signal(SIGINT, signal_handler); + signal(SIGQUIT, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGPIPE, SIG_IGN); + if (have_pty) + signal(SIGWINCH, window_change_handler); + + /* Enter raw mode if have a pseudo terminal. */ + if (have_pty) + enter_raw_mode(); + + /* Check if we should immediately send of on stdin. */ + client_check_initial_eof_on_stdin(); + + /* Main loop of the client for the interactive session mode. */ + while (!quit_pending) + { + fd_set readset, writeset; + + /* Precess buffered packets sent by the server. */ + client_process_buffered_input_packets(); + + /* Make packets of buffered stdin data, and buffer them for sending + to the server. */ + client_make_packets_from_stdin_data(); + + /* Make packets from buffered channel data, and buffer them for sending + to the server. */ + if (packet_not_very_much_data_to_write()) + channel_output_poll(); + + /* Check if the window size has changed, and buffer a message about + it to the server if so. */ + client_check_window_change(); + + if (quit_pending) + break; + + /* Wait until we have something to do (something becomes available + on one of the descriptors). */ + client_wait_until_can_do_something(&readset, &writeset); + + if (quit_pending) + break; + + /* Do channel operations. */ + channel_after_select(&readset, &writeset); + + /* Process input from the connection and from stdin. Buffer any data + that is available. */ + client_process_input(&readset); + + /* Process output to stdout and stderr. Output to the connection + is processed elsewhere (above). */ + client_process_output(&writeset); + + /* Send as much buffered packet data as possible to the sender. */ + if (FD_ISSET(connection_out, &writeset)) + packet_write_poll(); + } + + /* Terminate the session. */ + + /* Stop watching for window change. */ + if (have_pty) + signal(SIGWINCH, SIG_DFL); + + /* Stop listening for connections. */ + channel_stop_listening(); + + /* In interactive mode (with pseudo tty) display a message indicating that + the connection has been closed. */ + if (have_pty && !quiet_flag) + { + snprintf(buf, sizeof buf, "Connection to %.64s closed.\r\n", host); + buffer_append(&stderr_buffer, buf, strlen(buf)); + stderr_bytes += strlen(buf); + } + + /* Output any buffered data for stdout. */ + while (buffer_len(&stdout_buffer) > 0) + { + len = write(fileno(stdout), buffer_ptr(&stdout_buffer), + buffer_len(&stdout_buffer)); + if (len <= 0) + { + error("Write failed flushing stdout buffer."); + break; + } + buffer_consume(&stdout_buffer, len); + } + + /* Output any buffered data for stderr. */ + while (buffer_len(&stderr_buffer) > 0) + { + len = write(fileno(stderr), buffer_ptr(&stderr_buffer), + buffer_len(&stderr_buffer)); + if (len <= 0) + { + error("Write failed flushing stderr buffer."); + break; + } + buffer_consume(&stderr_buffer, len); + } + + /* Leave raw mode. */ + if (have_pty) + leave_raw_mode(); + + /* Clear and free any buffers. */ + memset(buf, 0, sizeof(buf)); + buffer_free(&stdin_buffer); + buffer_free(&stdout_buffer); + buffer_free(&stderr_buffer); + + /* Report bytes transferred, and transfer rates. */ + total_time = get_current_time() - start_time; + debug("Transferred: stdin %lu, stdout %lu, stderr %lu bytes in %.1f seconds", + stdin_bytes, stdout_bytes, stderr_bytes, total_time); + if (total_time > 0) + debug("Bytes per second: stdin %.1f, stdout %.1f, stderr %.1f", + stdin_bytes / total_time, stdout_bytes / total_time, + stderr_bytes / total_time); + + /* Return the exit status of the program. */ + debug("Exit status %d", exit_status); + return exit_status; +} diff --git a/compat.c b/compat.c new file mode 100644 index 00000000..4974b1cb --- /dev/null +++ b/compat.c @@ -0,0 +1,10 @@ +#include "includes.h" +RCSID("$Id: compat.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "ssh.h" + +int compat13=0; +void enable_compat13(void){ + log("Enabling compatibility mode for protocol 1.3"); + compat13=1; +} diff --git a/compat.h b/compat.h new file mode 100644 index 00000000..9d896c7d --- /dev/null +++ b/compat.h @@ -0,0 +1,7 @@ +/* RCSID("$Id: compat.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef COMPAT_H +#define COMPAT_H +void enable_compat13(void); +extern int compat13; +#endif diff --git a/compress.c b/compress.c new file mode 100644 index 00000000..c3267f73 --- /dev/null +++ b/compress.c @@ -0,0 +1,160 @@ +/* + +compress.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Wed Oct 25 22:12:46 1995 ylo + +Interface to packet compression for ssh. + +*/ + +#include "includes.h" +RCSID("$Id: compress.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "ssh.h" +#include "buffer.h" +#include "zlib.h" + +static z_stream incoming_stream; +static z_stream outgoing_stream; + +/* Initializes compression; level is compression level from 1 to 9 (as in + gzip). */ + +void buffer_compress_init(int level) +{ + debug("Enabling compression at level %d.", level); + if (level < 1 || level > 9) + fatal("Bad compression level %d.", level); + inflateInit(&incoming_stream); + deflateInit(&outgoing_stream, level); +} + +/* Frees any data structures allocated for compression. */ + +void buffer_compress_uninit() +{ + debug("compress outgoing: raw data %lu, compressed %lu, factor %.2f", + outgoing_stream.total_in, outgoing_stream.total_out, + outgoing_stream.total_in == 0 ? 0.0 : + (double)outgoing_stream.total_out / outgoing_stream.total_in); + debug("compress incoming: raw data %lu, compressed %lu, factor %.2f", + incoming_stream.total_out, incoming_stream.total_in, + incoming_stream.total_out == 0 ? 0.0 : + (double)incoming_stream.total_in / incoming_stream.total_out); + inflateEnd(&incoming_stream); + deflateEnd(&outgoing_stream); +} + +/* Compresses the contents of input_buffer into output_buffer. All + packets compressed using this function will form a single + compressed data stream; however, data will be flushed at the end of + every call so that each output_buffer can be decompressed + independently (but in the appropriate order since they together + form a single compression stream) by the receiver. This appends + the compressed data to the output buffer. */ + +void buffer_compress(Buffer *input_buffer, Buffer *output_buffer) +{ + char buf[4096]; + int status; + + /* This case is not handled below. */ + if (buffer_len(input_buffer) == 0) + return; + + /* Input is the contents of the input buffer. */ + outgoing_stream.next_in = buffer_ptr(input_buffer); + outgoing_stream.avail_in = buffer_len(input_buffer); + + /* Loop compressing until deflate() returns with avail_out != 0. */ + do + { + /* Set up fixed-size output buffer. */ + outgoing_stream.next_out = buf; + outgoing_stream.avail_out = sizeof(buf); + + /* Compress as much data into the buffer as possible. */ + status = deflate(&outgoing_stream, Z_PARTIAL_FLUSH); + switch (status) + { + case Z_OK: + /* Append compressed data to output_buffer. */ + buffer_append(output_buffer, buf, + sizeof(buf) - outgoing_stream.avail_out); + break; + case Z_STREAM_END: + fatal("buffer_compress: deflate returned Z_STREAM_END"); + /*NOTREACHED*/ + case Z_STREAM_ERROR: + fatal("buffer_compress: deflate returned Z_STREAM_ERROR"); + /*NOTREACHED*/ + case Z_BUF_ERROR: + fatal("buffer_compress: deflate returned Z_BUF_ERROR"); + /*NOTREACHED*/ + default: + fatal("buffer_compress: deflate returned %d", status); + /*NOTREACHED*/ + } + } + while (outgoing_stream.avail_out == 0); +} + +/* Uncompresses the contents of input_buffer into output_buffer. All + packets uncompressed using this function will form a single + compressed data stream; however, data will be flushed at the end of + every call so that each output_buffer. This must be called for the + same size units that the buffer_compress was called, and in the + same order that buffers compressed with that. This appends the + uncompressed data to the output buffer. */ + +void buffer_uncompress(Buffer *input_buffer, Buffer *output_buffer) +{ + char buf[4096]; + int status; + + incoming_stream.next_in = buffer_ptr(input_buffer); + incoming_stream.avail_in = buffer_len(input_buffer); + + incoming_stream.next_out = buf; + incoming_stream.avail_out = sizeof(buf); + + for (;;) + { + status = inflate(&incoming_stream, Z_PARTIAL_FLUSH); + switch (status) + { + case Z_OK: + buffer_append(output_buffer, buf, + sizeof(buf) - incoming_stream.avail_out); + incoming_stream.next_out = buf; + incoming_stream.avail_out = sizeof(buf); + break; + case Z_STREAM_END: + fatal("buffer_uncompress: inflate returned Z_STREAM_END"); + /*NOTREACHED*/ + case Z_DATA_ERROR: + fatal("buffer_uncompress: inflate returned Z_DATA_ERROR"); + /*NOTREACHED*/ + case Z_STREAM_ERROR: + fatal("buffer_uncompress: inflate returned Z_STREAM_ERROR"); + /*NOTREACHED*/ + case Z_BUF_ERROR: + /* Comments in zlib.h say that we should keep calling inflate() + until we get an error. This appears to be the error that we + get. */ + return; + case Z_MEM_ERROR: + fatal("buffer_uncompress: inflate returned Z_MEM_ERROR"); + /*NOTREACHED*/ + default: + fatal("buffer_uncompress: inflate returned %d", status); + } + } +} + diff --git a/compress.h b/compress.h new file mode 100644 index 00000000..b3144d62 --- /dev/null +++ b/compress.h @@ -0,0 +1,46 @@ +/* + +compress.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Wed Oct 25 22:12:46 1995 ylo + +Interface to packet compression for ssh. + +*/ + +/* RCSID("$Id: compress.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef COMPRESS_H +#define COMPRESS_H + +/* Initializes compression; level is compression level from 1 to 9 (as in + gzip). */ +void buffer_compress_init(int level); + +/* Frees any data structures allocated by buffer_compress_init. */ +void buffer_compress_uninit(); + +/* Compresses the contents of input_buffer into output_buffer. All + packets compressed using this function will form a single + compressed data stream; however, data will be flushed at the end of + every call so that each output_buffer can be decompressed + independently (but in the appropriate order since they together + form a single compression stream) by the receiver. This appends + the compressed data to the output buffer. */ +void buffer_compress(Buffer *input_buffer, Buffer *output_buffer); + +/* Uncompresses the contents of input_buffer into output_buffer. All + packets uncompressed using this function will form a single + compressed data stream; however, data will be flushed at the end of + every call so that each output_buffer. This must be called for the + same size units that the buffer_compress was called, and in the + same order that buffers compressed with that. This appends the + uncompressed data to the output buffer. */ +void buffer_uncompress(Buffer *input_buffer, Buffer *output_buffer); + +#endif /* COMPRESS_H */ diff --git a/crc32.c b/crc32.c new file mode 100644 index 00000000..dbb1e6b7 --- /dev/null +++ b/crc32.c @@ -0,0 +1,120 @@ +/* The implementation here was originally done by Gary S. Brown. I have + borrowed the tables directly, and made some minor changes to the + crc32-function (including changing the interface). //ylo */ + +#include "includes.h" +RCSID("$Id: crc32.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "crc32.h" + + /* ============================================================= */ + /* COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or */ + /* code or tables extracted from it, as desired without restriction. */ + /* */ + /* First, the polynomial itself and its table of feedback terms. The */ + /* polynomial is */ + /* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */ + /* */ + /* Note that we take it "backwards" and put the highest-order term in */ + /* the lowest-order bit. The X^32 term is "implied"; the LSB is the */ + /* X^31 term, etc. The X^0 term (usually shown as "+1") results in */ + /* the MSB being 1. */ + /* */ + /* Note that the usual hardware shift register implementation, which */ + /* is what we're using (we're merely optimizing it by doing eight-bit */ + /* chunks at a time) shifts bits into the lowest-order term. In our */ + /* implementation, that means shifting towards the right. Why do we */ + /* do it this way? Because the calculated CRC must be transmitted in */ + /* order from highest-order term to lowest-order term. UARTs transmit */ + /* characters in order from LSB to MSB. By storing the CRC this way, */ + /* we hand it to the UART in the order low-byte to high-byte; the UART */ + /* sends each low-bit to hight-bit; and the result is transmission bit */ + /* by bit from highest- to lowest-order term without requiring any bit */ + /* shuffling on our part. Reception works similarly. */ + /* */ + /* The feedback terms table consists of 256, 32-bit entries. Notes: */ + /* */ + /* The table can be generated at runtime if desired; code to do so */ + /* is shown later. It might not be obvious, but the feedback */ + /* terms simply represent the results of eight shift/xor opera- */ + /* tions for all combinations of data and CRC register values. */ + /* */ + /* The values must be right-shifted by eight bits by the "updcrc" */ + /* logic; the shift must be unsigned (bring in zeroes). On some */ + /* hardware you could probably optimize the shift in assembler by */ + /* using byte-swap instructions. */ + /* polynomial $edb88320 */ + /* */ + /* -------------------------------------------------------------------- */ + +static unsigned int crc32_tab[] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL + }; + +/* Return a 32-bit CRC of the contents of the buffer. */ + +unsigned int crc32(const unsigned char *s, unsigned int len) +{ + unsigned int i; + unsigned int crc32val; + + crc32val = 0; + for (i = 0; i < len; i ++) + { + crc32val = + crc32_tab[(crc32val ^ s[i]) & 0xff] ^ + (crc32val >> 8); + } + return crc32val; +} diff --git a/crc32.h b/crc32.h new file mode 100644 index 00000000..456b20b8 --- /dev/null +++ b/crc32.h @@ -0,0 +1,25 @@ +/* + +crc32.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1992 Tatu Ylonen, Espoo, Finland + All rights reserved + +Created: Tue Feb 11 14:37:27 1992 ylo + +Functions for computing 32-bit CRC. + +*/ + +/* RCSID("$Id: crc32.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef CRC32_H +#define CRC32_H + +/* This computes a 32 bit CRC of the data in the buffer, and returns the + CRC. The polynomial used is 0xedb88320. */ +unsigned int crc32(const unsigned char *buf, unsigned int len); + +#endif /* CRC32_H */ diff --git a/deattack.c b/deattack.c new file mode 100644 index 00000000..d5f8608c --- /dev/null +++ b/deattack.c @@ -0,0 +1,180 @@ +/* + * $Id: deattack.c,v 1.1 1999/10/27 03:42:44 damien Exp $ + * Cryptographic attack detector for ssh - source code + * + * Copyright (c) 1998 CORE SDI S.A., Buenos Aires, Argentina. + * + * All rights reserved. Redistribution and use in source and binary + * forms, with or without modification, are permitted provided that + * this copyright notice is retained. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI S.A. BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR + * CONSEQUENTIAL DAMAGES RESULTING FROM THE USE OR MISUSE OF THIS + * SOFTWARE. + * + * Ariel Futoransky <futo@core-sdi.com> + * <http://www.core-sdi.com> */ + +#include "includes.h" +#include "deattack.h" +#include "ssh.h" +#include "crc32.h" +#include "getput.h" +#include "xmalloc.h" + +/* SSH Constants */ +#define SSH_MAXBLOCKS (32 * 1024) +#define SSH_BLOCKSIZE (8) + +/* Hashing constants */ +#define HASH_MINSIZE (8 * 1024) +#define HASH_ENTRYSIZE (2) +#define HASH_FACTOR(x) ((x)*3/2) +#define HASH_UNUSEDCHAR (0xff) +#define HASH_UNUSED (0xffff) +#define HASH_IV (0xfffe) + +#define HASH_MINBLOCKS (7*SSH_BLOCKSIZE) + + +/* Hash function (Input keys are cipher results) */ +#define HASH(x) GET_32BIT(x) + +#define CMP(a,b) (memcmp(a, b, SSH_BLOCKSIZE)) + + +void +crc_update(u_int32_t * a, u_int32_t b) +{ + b ^= *a; + *a = crc32((unsigned char *) &b, sizeof(b)); +} + +/* + check_crc + detects if a block is used in a particular pattern + */ + +int +check_crc(unsigned char *S, unsigned char *buf, u_int32_t len, unsigned char *IV) +{ + u_int32_t crc; + unsigned char *c; + + crc = 0; + if (IV && !CMP(S, IV)) + { + crc_update(&crc, 1); + crc_update(&crc, 0); + } + for (c = buf; c < buf + len; c += SSH_BLOCKSIZE) + { + if (!CMP(S, c)) + { + crc_update(&crc, 1); + crc_update(&crc, 0); + } else + { + crc_update(&crc, 0); + crc_update(&crc, 0); + } + } + + return (crc == 0); +} + + +/* + detect_attack + Detects a crc32 compensation attack on a packet + */ +int +detect_attack(unsigned char *buf, u_int32_t len, unsigned char *IV) +{ + static u_int16_t *h = (u_int16_t *) NULL; + static u_int16_t n = HASH_MINSIZE / HASH_ENTRYSIZE; + register u_int32_t i, j; + u_int32_t l; + register unsigned char *c; + unsigned char *d; + + + assert(len <= (SSH_MAXBLOCKS * SSH_BLOCKSIZE)); + assert(len % SSH_BLOCKSIZE == 0); + + for (l = n; l < HASH_FACTOR(len / SSH_BLOCKSIZE); l = l << 2); + + if (h == NULL) + { + debug("Installing crc compensation attack detector."); + n = l; + h = (u_int16_t *) xmalloc(n * HASH_ENTRYSIZE); + } else + { + if (l > n) + { + n = l; + h = (u_int16_t *) xrealloc(h, n * HASH_ENTRYSIZE); + } + } + + + if (len <= HASH_MINBLOCKS) + { + for (c = buf; c < buf + len; c += SSH_BLOCKSIZE) + { + if (IV && (!CMP(c, IV))) + { + if ((check_crc(c, buf, len, IV))) + return (DEATTACK_DETECTED); + else + break; + } + for (d = buf; d < c; d += SSH_BLOCKSIZE) + { + if (!CMP(c, d)) + { + if ((check_crc(c, buf, len, IV))) + return (DEATTACK_DETECTED); + else + break; + } + } + } + return (DEATTACK_OK); + } + memset(h, HASH_UNUSEDCHAR, n * HASH_ENTRYSIZE); + + if (IV) + h[HASH(IV) & (n - 1)] = HASH_IV; + + + for (c = buf, j = 0; c < (buf + len); c += SSH_BLOCKSIZE, j++) + { + for (i = HASH(c) & (n - 1); h[i] != HASH_UNUSED; + i = (i + 1) & (n - 1)) + { + if (h[i] == HASH_IV) + { + if (!CMP(c, IV)) + { + if (check_crc(c, buf, len, IV)) + return (DEATTACK_DETECTED); + else + break; + } + } else if (!CMP(c, buf + h[i] * SSH_BLOCKSIZE)) + { + if (check_crc(c, buf, len, IV)) + return (DEATTACK_DETECTED); + else + break; + } + } + h[i] = j; + } + + return (DEATTACK_OK); +} diff --git a/deattack.h b/deattack.h new file mode 100644 index 00000000..a0dcf5b6 --- /dev/null +++ b/deattack.h @@ -0,0 +1,27 @@ +/* $Id: deattack.h,v 1.1 1999/10/27 03:42:44 damien Exp $ + * Cryptographic attack detector for ssh - Header file + * + * Copyright (c) 1998 CORE SDI S.A., Buenos Aires, Argentina. + * + * All rights reserved. Redistribution and use in source and binary + * forms, with or without modification, are permitted provided that + * this copyright notice is retained. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI S.A. BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR + * CONSEQUENTIAL DAMAGES RESULTING FROM THE USE OR MISUSE OF THIS + * SOFTWARE. + * + * Ariel Futoransky <futo@core-sdi.com> + * <http://www.core-sdi.com> */ + +#ifndef _DEATTACK_H +#define _DEATTACK_H + +/* Return codes */ +#define DEATTACK_OK 0 +#define DEATTACK_DETECTED 1 + +int detect_attack(unsigned char *buf, u_int32_t len, unsigned char IV[8]); +#endif diff --git a/getput.h b/getput.h new file mode 100644 index 00000000..7b5d7425 --- /dev/null +++ b/getput.h @@ -0,0 +1,64 @@ +/* + +getput.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Wed Jun 28 22:36:30 1995 ylo + +Macros for storing and retrieving data in msb first and lsb first order. + +*/ + +/* RCSID("$Id: getput.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef GETPUT_H +#define GETPUT_H + +/*------------ macros for storing/extracting msb first words -------------*/ + +#define GET_32BIT(cp) (((unsigned long)(unsigned char)(cp)[0] << 24) | \ + ((unsigned long)(unsigned char)(cp)[1] << 16) | \ + ((unsigned long)(unsigned char)(cp)[2] << 8) | \ + ((unsigned long)(unsigned char)(cp)[3])) + +#define GET_16BIT(cp) (((unsigned long)(unsigned char)(cp)[0] << 8) | \ + ((unsigned long)(unsigned char)(cp)[1])) + +#define PUT_32BIT(cp, value) do { \ + (cp)[0] = (value) >> 24; \ + (cp)[1] = (value) >> 16; \ + (cp)[2] = (value) >> 8; \ + (cp)[3] = (value); } while (0) + +#define PUT_16BIT(cp, value) do { \ + (cp)[0] = (value) >> 8; \ + (cp)[1] = (value); } while (0) + +/*------------ macros for storing/extracting lsb first words -------------*/ + +#define GET_32BIT_LSB_FIRST(cp) \ + (((unsigned long)(unsigned char)(cp)[0]) | \ + ((unsigned long)(unsigned char)(cp)[1] << 8) | \ + ((unsigned long)(unsigned char)(cp)[2] << 16) | \ + ((unsigned long)(unsigned char)(cp)[3] << 24)) + +#define GET_16BIT_LSB_FIRST(cp) \ + (((unsigned long)(unsigned char)(cp)[0]) | \ + ((unsigned long)(unsigned char)(cp)[1] << 8)) + +#define PUT_32BIT_LSB_FIRST(cp, value) do { \ + (cp)[0] = (value); \ + (cp)[1] = (value) >> 8; \ + (cp)[2] = (value) >> 16; \ + (cp)[3] = (value) >> 24; } while (0) + +#define PUT_16BIT_LSB_FIRST(cp, value) do { \ + (cp)[0] = (value); \ + (cp)[1] = (value) >> 8; } while (0) + +#endif /* GETPUT_H */ + diff --git a/helper.c b/helper.c new file mode 100644 index 00000000..3b0402ec --- /dev/null +++ b/helper.c @@ -0,0 +1,108 @@ +/* +** +** OpenBSD emulation routines +** +** Damien Miller <djm@ibs.com.au> +** +** Copyright 1999 Internet Business Solutions +** +** Permission is hereby granted, free of charge, to any person +** obtaining a copy of this software and associated documentation +** files (the "Software"), to deal in the Software without +** restriction, including without limitation the rights to use, copy, +** modify, merge, publish, distribute, sublicense, and/or sell copies +** of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +** KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +** WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE +** AND NONINFRINGEMENT. IN NO EVENT SHALL DAMIEN MILLER OR INTERNET +** BUSINESS SOLUTIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +** OR OTHER DEALINGS IN THE SOFTWARE. +** +** Except as contained in this notice, the name of Internet Business +** Solutions shall not be used in advertising or otherwise to promote +** the sale, use or other dealings in this Software without prior +** written authorization from Internet Business Solutions. +** +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "rc4.h" +#include "xmalloc.h" + +#include "helper.h" + +void get_random_bytes(unsigned char *buf, int len); + +static rc4_t *rc4 = NULL; + +void setproctitle(const char *fmt, ...) +{ + /* FIXME */ +} + +unsigned char arc4random(void) +{ + unsigned char r; + + if (rc4 == NULL) + arc4random_stir(); + + rc4_getbytes(rc4, &r, 1); + + return(r); +} + +void arc4random_stir(void) +{ + unsigned char rand_buf[32]; + + if (rc4 == NULL) + rc4 = xmalloc(sizeof(*rc4)); + + get_random_bytes(rand_buf, sizeof(rand_buf)); + rc4_key(rc4, rand_buf, sizeof(rand_buf)); +} + +void get_random_bytes(unsigned char *buf, int len) +{ + int urandom; + int c; + + urandom = open("/dev/urandom", O_RDONLY); + if (urandom == -1) + { + fprintf(stderr, "Couldn't open /dev/urandom: %s", strerror(errno)); + exit(1); + } + + c = read(urandom, buf, len); + if (c == -1) + { + fprintf(stderr, "Couldn't read from /dev/urandom: %s", strerror(errno)); + exit(1); + } + + if (c != len) + { + fprintf(stderr, "Short read from /dev/urandom"); + exit(1); + } +} + diff --git a/helper.h b/helper.h new file mode 100644 index 00000000..2f09daa8 --- /dev/null +++ b/helper.h @@ -0,0 +1,43 @@ +/* +** +** OpenBSD emulation routines +** +** Damien Miller <djm@ibs.com.au> +** +** Copyright 1999 Internet Business Solutions +** +** Permission is hereby granted, free of charge, to any person +** obtaining a copy of this software and associated documentation +** files (the "Software"), to deal in the Software without +** restriction, including without limitation the rights to use, copy, +** modify, merge, publish, distribute, sublicense, and/or sell copies +** of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +** KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +** WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE +** AND NONINFRINGEMENT. IN NO EVENT SHALL DAMIEN MILLER OR INTERNET +** BUSINESS SOLUTIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +** OR OTHER DEALINGS IN THE SOFTWARE. +** +** Except as contained in this notice, the name of Internet Business +** Solutions shall not be used in advertising or otherwise to promote +** the sale, use or other dealings in this Software without prior +** written authorization from Internet Business Solutions. +** +*/ + +#ifndef _HELPER_H +#define _HELPER_H + +unsigned char arc4random(void); +void arc4random_stir(void); +void setproctitle(const char *fmt, ...); + +#endif /* _HELPER_H */ diff --git a/hostfile.c b/hostfile.c new file mode 100644 index 00000000..ca0fe88a --- /dev/null +++ b/hostfile.c @@ -0,0 +1,279 @@ +/* + +hostfile.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Thu Jun 29 07:10:56 1995 ylo + +Functions for manipulating the known hosts files. + +*/ + +#include "includes.h" +RCSID("$Id: hostfile.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "packet.h" +#include "ssh.h" + +/* Reads a multiple-precision integer in hex from the buffer, and advances the + pointer. The integer must already be initialized. This function is + permitted to modify the buffer. This leaves *cpp to point just beyond + the last processed (and maybe modified) character. Note that this may + modify the buffer containing the number. */ + +int +auth_rsa_read_bignum(char **cpp, BIGNUM *value) +{ + char *cp = *cpp; + int len, old; + + /* Skip any leading whitespace. */ + for (; *cp == ' ' || *cp == '\t'; cp++) + ; + + /* Check that it begins with a hex digit. */ + if (*cp < '0' || *cp > '9') + return 0; + + /* Save starting position. */ + *cpp = cp; + + /* Move forward until all hex digits skipped. */ + for (; *cp >= '0' && *cp <= '9'; cp++) + ; + + /* Compute the length of the hex number. */ + len = cp - *cpp; + + /* Save the old terminating character, and replace it by \0. */ + old = *cp; + *cp = 0; + + + /* Parse the number. */ + if (BN_dec2bn(&value, *cpp) == 0) + return 0; + + /* Restore old terminating character. */ + *cp = old; + + /* Move beyond the number and return success. */ + *cpp = cp; + return 1; +} + +/* Parses an RSA key (number of bits, e, n) from a string. Moves the pointer + over the key. Skips any whitespace at the beginning and at end. */ + +int +auth_rsa_read_key(char **cpp, unsigned int *bitsp, BIGNUM *e, BIGNUM *n) +{ + unsigned int bits; + char *cp; + + /* Skip leading whitespace. */ + for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++) + ; + + /* Get number of bits. */ + if (*cp < '0' || *cp > '9') + return 0; /* Bad bit count... */ + for (bits = 0; *cp >= '0' && *cp <= '9'; cp++) + bits = 10 * bits + *cp - '0'; + + /* Get public exponent. */ + if (!auth_rsa_read_bignum(&cp, e)) + return 0; + + /* Get public modulus. */ + if (!auth_rsa_read_bignum(&cp, n)) + return 0; + + /* Skip trailing whitespace. */ + for (; *cp == ' ' || *cp == '\t'; cp++) + ; + + /* Return results. */ + *cpp = cp; + *bitsp = bits; + return 1; +} + +/* Tries to match the host name (which must be in all lowercase) against the + comma-separated sequence of subpatterns (each possibly preceded by ! to + indicate negation). Returns true if there is a positive match; zero + otherwise. */ + +int +match_hostname(const char *host, const char *pattern, unsigned int len) +{ + char sub[1024]; + int negated; + int got_positive; + unsigned int i, subi; + + got_positive = 0; + for (i = 0; i < len;) + { + /* Check if the subpattern is negated. */ + if (pattern[i] == '!') + { + negated = 1; + i++; + } + else + negated = 0; + + /* Extract the subpattern up to a comma or end. Convert the subpattern + to lowercase. */ + for (subi = 0; + i < len && subi < sizeof(sub) - 1 && pattern[i] != ','; + subi++, i++) + sub[subi] = isupper(pattern[i]) ? tolower(pattern[i]) : pattern[i]; + /* If subpattern too long, return failure (no match). */ + if (subi >= sizeof(sub) - 1) + return 0; + + /* If the subpattern was terminated by a comma, skip the comma. */ + if (i < len && pattern[i] == ',') + i++; + + /* Null-terminate the subpattern. */ + sub[subi] = '\0'; + + /* Try to match the subpattern against the host name. */ + if (match_pattern(host, sub)) { + if (negated) + return 0; /* Fail if host matches any negated subpattern. */ + else + got_positive = 1; + } + } + + /* Return success if got a positive match. If there was a negative match, + we have already returned zero and never get here. */ + return got_positive; +} + +/* Checks whether the given host (which must be in all lowercase) is + already in the list of our known hosts. + Returns HOST_OK if the host is known and has the specified key, + HOST_NEW if the host is not known, and HOST_CHANGED if the host is known + but used to have a different host key. */ + +HostStatus +check_host_in_hostfile(const char *filename, + const char *host, unsigned int bits, + BIGNUM *e, BIGNUM *n, + BIGNUM *ke, BIGNUM *kn) +{ + FILE *f; + char line[8192]; + unsigned int kbits, hostlen; + char *cp, *cp2; + HostStatus end_return; + struct stat st; + + /* Open the file containing the list of known hosts. */ + f = fopen(filename, "r"); + if (!f) + { + if (stat(filename, &st) >= 0) + { + packet_send_debug("Could not open %.900s for reading.", filename); + packet_send_debug("If your home directory is on an NFS volume, it may need to be world-readable."); + } + return HOST_NEW; + } + + /* Cache the length of the host name. */ + hostlen = strlen(host); + + /* Return value when the loop terminates. This is set to HOST_CHANGED if + we have seen a different key for the host and have not found the proper + one. */ + end_return = HOST_NEW; + + /* Go trough the file. */ + while (fgets(line, sizeof(line), f)) + { + cp = line; + + /* Skip any leading whitespace. */ + for (; *cp == ' ' || *cp == '\t'; cp++) + ; + + /* Ignore comment lines and empty lines. */ + if (!*cp || *cp == '#' || *cp == '\n') + continue; + + /* Find the end of the host name portion. */ + for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) + ; + + /* Check if the host name matches. */ + if (!match_hostname(host, cp, (unsigned int)(cp2 - cp))) + continue; + + /* Got a match. Skip host name. */ + cp = cp2; + + /* Extract the key from the line. This will skip any leading + whitespace. Ignore badly formatted lines. */ + if (!auth_rsa_read_key(&cp, &kbits, ke, kn)) + continue; + + /* Check if the current key is the same as the previous one. */ + if (kbits == bits && BN_cmp(ke, e) == 0 && BN_cmp(kn, n) == 0) + { + /* Ok, they match. */ + fclose(f); + return HOST_OK; + } + + /* They do not match. We will continue to go through the file; however, + we note that we will not return that it is new. */ + end_return = HOST_CHANGED; + } + /* Clear variables and close the file. */ + fclose(f); + + /* Return either HOST_NEW or HOST_CHANGED, depending on whether we saw a + different key for the host. */ + return end_return; +} + +/* Appends an entry to the host file. Returns false if the entry + could not be appended. */ + +int +add_host_to_hostfile(const char *filename, const char *host, + unsigned int bits, BIGNUM *e, BIGNUM *n) +{ + FILE *f; + char *buf; + + /* Open the file for appending. */ + f = fopen(filename, "a"); + if (!f) + return 0; + + /* Print the host name and key to the file. */ + fprintf(f, "%s %u ", host, bits); + buf = BN_bn2dec(e); + assert(buf != NULL); + fprintf(f, "%s ", buf); + free (buf); + buf = BN_bn2dec(n); + assert(buf != NULL); + fprintf(f, "%s\n", buf); + free (buf); + + /* Close the file. */ + fclose(f); + return 1; +} diff --git a/includes.h b/includes.h new file mode 100644 index 00000000..862dbd64 --- /dev/null +++ b/includes.h @@ -0,0 +1,78 @@ +/* + +includes.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Thu Mar 23 16:29:37 1995 ylo + +This file includes most of the needed system headers. + +*/ + +#ifndef INCLUDES_H +#define INCLUDES_H + +#define RCSID(msg) \ +static /**/const char *const rcsid[] = { (char *)rcsid, "\100(#)" msg } + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/un.h> +#include <sys/resource.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/tcp.h> +#include <netinet/ip.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <endian.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <assert.h> +#include <signal.h> +#include <termios.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <pwd.h> +#include <grp.h> +#include <unistd.h> +#include <time.h> +#include <paths.h> +#include <dirent.h> + +#include "version.h" + +#include "helper.h" +#include "mktemp.h" +#include "strlcpy.h" + +/* Define this to be the path of the xauth program. */ +#ifndef XAUTH_PATH +#define XAUTH_PATH "/usr/X11R6/bin/xauth" +#endif /* XAUTH_PATH */ + +/* Define this to be the path of the rsh program. */ +#ifndef _PATH_RSH +#define _PATH_RSH "/usr/bin/rsh" +#endif /* _PATH_RSH */ + +/* Define this to use pipes instead of socketpairs for communicating with the + client program. Socketpairs do not seem to work on all systems. */ +#define USE_PIPES 1 + +#endif /* INCLUDES_H */ diff --git a/log-client.c b/log-client.c new file mode 100644 index 00000000..1792ba84 --- /dev/null +++ b/log-client.c @@ -0,0 +1,138 @@ +/* + +log-client.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Mon Mar 20 21:13:40 1995 ylo + +Client-side versions of debug(), log(), etc. These print to stderr. + +*/ + +#include "includes.h" +RCSID("$Id: log-client.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "xmalloc.h" +#include "ssh.h" + +static int log_debug = 0; +static int log_quiet = 0; + +void log_init(char *av0, int on_stderr, int debug, int quiet, + SyslogFacility facility) +{ + log_debug = debug; + log_quiet = quiet; +} + +void log(const char *fmt, ...) +{ + va_list args; + + if (log_quiet) + return; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\r\n"); + va_end(args); +} + +void debug(const char *fmt, ...) +{ + va_list args; + if (log_quiet || !log_debug) + return; + va_start(args, fmt); + fprintf(stderr, "debug: "); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\r\n"); + va_end(args); +} + +void error(const char *fmt, ...) +{ + va_list args; + if (log_quiet) + return; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\r\n"); + va_end(args); +} + +struct fatal_cleanup +{ + struct fatal_cleanup *next; + void (*proc)(void *); + void *context; +}; + +static struct fatal_cleanup *fatal_cleanups = NULL; + +/* Registers a cleanup function to be called by fatal() before exiting. */ + +void fatal_add_cleanup(void (*proc)(void *), void *context) +{ + struct fatal_cleanup *cu; + + cu = xmalloc(sizeof(*cu)); + cu->proc = proc; + cu->context = context; + cu->next = fatal_cleanups; + fatal_cleanups = cu; +} + +/* Removes a cleanup frunction to be called at fatal(). */ + +void fatal_remove_cleanup(void (*proc)(void *context), void *context) +{ + struct fatal_cleanup **cup, *cu; + + for (cup = &fatal_cleanups; *cup; cup = &cu->next) + { + cu = *cup; + if (cu->proc == proc && cu->context == context) + { + *cup = cu->next; + xfree(cu); + return; + } + } + fatal("fatal_remove_cleanup: no such cleanup function: 0x%lx 0x%lx\n", + (unsigned long)proc, (unsigned long)context); +} + +/* Function to display an error message and exit. This is in this file because + this needs to restore terminal modes before exiting. See log-client.c + for other related functions. */ + +void fatal(const char *fmt, ...) +{ + va_list args; + struct fatal_cleanup *cu, *next_cu; + static int fatal_called = 0; + + if (!fatal_called) + { + fatal_called = 1; + + /* Call cleanup functions. */ + for (cu = fatal_cleanups; cu; cu = next_cu) + { + next_cu = cu->next; + (*cu->proc)(cu->context); + } + } + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\r\n"); + va_end(args); + exit(255); +} + +/* fatal() is in ssh.c so that it can properly reset terminal modes. */ diff --git a/log-server.c b/log-server.c new file mode 100644 index 00000000..fce96b01 --- /dev/null +++ b/log-server.c @@ -0,0 +1,233 @@ +/* + +log-server.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Mon Mar 20 21:19:30 1995 ylo + +Server-side versions of debug(), log(), etc. These normally send the output +to the system log. + +*/ + +#include "includes.h" +RCSID("$Id: log-server.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include <syslog.h> +#include "packet.h" +#include "xmalloc.h" +#include "ssh.h" + +static int log_debug = 0; +static int log_quiet = 0; +static int log_on_stderr = 0; + +/* Initialize the log. + av0 program name (should be argv[0]) + on_stderr print also on stderr + debug send debugging messages to system log + quiet don\'t log anything + */ + +void log_init(char *av0, int on_stderr, int debug, int quiet, + SyslogFacility facility) +{ + int log_facility; + + switch (facility) + { + case SYSLOG_FACILITY_DAEMON: + log_facility = LOG_DAEMON; + break; + case SYSLOG_FACILITY_USER: + log_facility = LOG_USER; + break; + case SYSLOG_FACILITY_AUTH: + log_facility = LOG_AUTH; + break; + case SYSLOG_FACILITY_LOCAL0: + log_facility = LOG_LOCAL0; + break; + case SYSLOG_FACILITY_LOCAL1: + log_facility = LOG_LOCAL1; + break; + case SYSLOG_FACILITY_LOCAL2: + log_facility = LOG_LOCAL2; + break; + case SYSLOG_FACILITY_LOCAL3: + log_facility = LOG_LOCAL3; + break; + case SYSLOG_FACILITY_LOCAL4: + log_facility = LOG_LOCAL4; + break; + case SYSLOG_FACILITY_LOCAL5: + log_facility = LOG_LOCAL5; + break; + case SYSLOG_FACILITY_LOCAL6: + log_facility = LOG_LOCAL6; + break; + case SYSLOG_FACILITY_LOCAL7: + log_facility = LOG_LOCAL7; + break; + default: + fprintf(stderr, "Unrecognized internal syslog facility code %d\n", + (int)facility); + exit(1); + } + + log_debug = debug; + log_quiet = quiet; + log_on_stderr = on_stderr; + closelog(); /* Close any previous log. */ + openlog(av0, LOG_PID, log_facility); +} + +#define MSGBUFSIZE 1024 + +#define DECL_MSGBUF char msgbuf[MSGBUFSIZE] + +/* Log this message (information that usually should go to the log). */ + +void log(const char *fmt, ...) +{ + va_list args; + DECL_MSGBUF; + if (log_quiet) + return; + va_start(args, fmt); + vsnprintf(msgbuf, MSGBUFSIZE, fmt, args); + va_end(args); + if (log_on_stderr) + fprintf(stderr, "log: %s\n", msgbuf); + syslog(LOG_INFO, "log: %.500s", msgbuf); +} + +/* Debugging messages that should not be logged during normal operation. */ + +void debug(const char *fmt, ...) +{ + va_list args; + DECL_MSGBUF; + if (!log_debug || log_quiet) + return; + va_start(args, fmt); + vsnprintf(msgbuf, MSGBUFSIZE, fmt, args); + va_end(args); + if (log_on_stderr) + fprintf(stderr, "debug: %s\n", msgbuf); + syslog(LOG_DEBUG, "debug: %.500s", msgbuf); +} + +/* Error messages that should be logged. */ + +void error(const char *fmt, ...) +{ + va_list args; + DECL_MSGBUF; + if (log_quiet) + return; + va_start(args, fmt); + vsnprintf(msgbuf, MSGBUFSIZE, fmt, args); + va_end(args); + if (log_on_stderr) + fprintf(stderr, "error: %s\n", msgbuf); + syslog(LOG_ERR, "error: %.500s", msgbuf); +} + +struct fatal_cleanup +{ + struct fatal_cleanup *next; + void (*proc)(void *); + void *context; +}; + +static struct fatal_cleanup *fatal_cleanups = NULL; + +/* Registers a cleanup function to be called by fatal() before exiting. */ + +void fatal_add_cleanup(void (*proc)(void *), void *context) +{ + struct fatal_cleanup *cu; + + cu = xmalloc(sizeof(*cu)); + cu->proc = proc; + cu->context = context; + cu->next = fatal_cleanups; + fatal_cleanups = cu; +} + +/* Removes a cleanup frunction to be called at fatal(). */ + +void fatal_remove_cleanup(void (*proc)(void *context), void *context) +{ + struct fatal_cleanup **cup, *cu; + + for (cup = &fatal_cleanups; *cup; cup = &cu->next) + { + cu = *cup; + if (cu->proc == proc && cu->context == context) + { + *cup = cu->next; + xfree(cu); + return; + } + } + fatal("fatal_remove_cleanup: no such cleanup function: 0x%lx 0x%lx\n", + (unsigned long)proc, (unsigned long)context); +} + +/* Fatal messages. This function never returns. */ + +void fatal(const char *fmt, ...) +{ + va_list args; + struct fatal_cleanup *cu, *next_cu; + static int fatal_called = 0; +#if defined(KRB4) + extern char *ticket; +#endif /* KRB4 */ + DECL_MSGBUF; + + if (log_quiet) + exit(1); + va_start(args, fmt); + vsnprintf(msgbuf, MSGBUFSIZE, fmt, args); + va_end(args); + if (log_on_stderr) + fprintf(stderr, "fatal: %s\n", msgbuf); + syslog(LOG_ERR, "fatal: %.500s", msgbuf); + + if (fatal_called) + exit(1); + fatal_called = 1; + + /* Call cleanup functions. */ + for (cu = fatal_cleanups; cu; cu = next_cu) + { + next_cu = cu->next; + debug("Calling cleanup 0x%lx(0x%lx)", + (unsigned long)cu->proc, (unsigned long)cu->context); + (*cu->proc)(cu->context); + } +#if defined(KRB4) + /* If you forwarded a ticket you get one shot for proper + authentication. */ + /* If tgt was passed unlink file */ + if (ticket) + { + if (strcmp(ticket,"none")) + unlink(ticket); + else + ticket = NULL; + } +#endif /* KRB4 */ + + /* If local XAUTHORITY was created, remove it. */ + if (xauthfile) unlink(xauthfile); + + exit(1); +} diff --git a/login.c b/login.c new file mode 100644 index 00000000..0c1e61b7 --- /dev/null +++ b/login.c @@ -0,0 +1,118 @@ +/* + +login.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Fri Mar 24 14:51:08 1995 ylo + +This file performs some of the things login(1) normally does. We cannot +easily use something like login -p -h host -f user, because there are +several different logins around, and it is hard to determined what kind of +login the current system has. Also, we want to be able to execute commands +on a tty. + +*/ + +#include "includes.h" +RCSID("$Id: login.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include <utmp.h> +#include "ssh.h" + +/* Returns the time when the user last logged in. Returns 0 if the + information is not available. This must be called before record_login. + The host the user logged in from will be returned in buf. */ + +/* Returns the time when the user last logged in (or 0 if no previous login + is found). The name of the host used last time is returned in buf. */ + +unsigned long get_last_login_time(uid_t uid, const char *logname, + char *buf, unsigned int bufsize) +{ + struct lastlog ll; + char *lastlog; + int fd; + + lastlog = _PATH_LASTLOG; + + buf[0] = '\0'; + + fd = open(lastlog, O_RDONLY); + if (fd < 0) + return 0; + lseek(fd, (off_t)((long)uid * sizeof(ll)), SEEK_SET); + if (read(fd, &ll, sizeof(ll)) != sizeof(ll)) + { + close(fd); + return 0; + } + close(fd); + if (bufsize > sizeof(ll.ll_host) + 1) + bufsize = sizeof(ll.ll_host) + 1; + strncpy(buf, ll.ll_host, bufsize - 1); + buf[bufsize - 1] = 0; + return ll.ll_time; +} + +/* Records that the user has logged in. I these parts of operating systems + were more standardized. */ + +void record_login(int pid, const char *ttyname, const char *user, uid_t uid, + const char *host, struct sockaddr_in *addr) +{ + int fd; + struct lastlog ll; + char *lastlog; + + struct utmp u; + const char *utmp, *wtmp; + + /* Construct an utmp/wtmp entry. */ + memset(&u, 0, sizeof(u)); + strncpy(u.ut_line, ttyname + 5, sizeof(u.ut_line)); + u.ut_time = time(NULL); + strncpy(u.ut_name, user, sizeof(u.ut_name)); + strncpy(u.ut_host, host, sizeof(u.ut_host)); + + /* Figure out the file names. */ + utmp = _PATH_UTMP; + wtmp = _PATH_WTMP; + + login(&u); + + lastlog = _PATH_LASTLOG; + + /* Update lastlog unless actually recording a logout. */ + if (strcmp(user, "") != 0) + { + /* It is safer to bzero the lastlog structure first because some + systems might have some extra fields in it (e.g. SGI) */ + memset(&ll, 0, sizeof(ll)); + + /* Update lastlog. */ + ll.ll_time = time(NULL); + strncpy(ll.ll_line, ttyname + 5, sizeof(ll.ll_line)); + strncpy(ll.ll_host, host, sizeof(ll.ll_host)); + fd = open(lastlog, O_RDWR); + if (fd >= 0) + { + lseek(fd, (off_t)((long)uid * sizeof(ll)), SEEK_SET); + if (write(fd, &ll, sizeof(ll)) != sizeof(ll)) + log("Could not write %.100s: %.100s", lastlog, strerror(errno)); + close(fd); + } + } +} + +/* Records that the user has logged out. */ + +void record_logout(int pid, const char *ttyname) +{ + const char *line = ttyname + 5; /* /dev/ttyq8 -> ttyq8 */ + if (logout(line)) + logwtmp(line, "", ""); +} diff --git a/match.c b/match.c new file mode 100644 index 00000000..b7a7d338 --- /dev/null +++ b/match.c @@ -0,0 +1,78 @@ +/* + +match.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Thu Jun 22 01:17:50 1995 ylo + +Simple pattern matching, with '*' and '?' as wildcards. + +*/ + +#include "includes.h" +RCSID("$Id: match.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "ssh.h" + +/* Returns true if the given string matches the pattern (which may contain + ? and * as wildcards), and zero if it does not match. */ + +int match_pattern(const char *s, const char *pattern) +{ + while (1) + { + /* If at end of pattern, accept if also at end of string. */ + if (!*pattern) + return !*s; + + /* Process '*'. */ + if (*pattern == '*') + { + /* Skip the asterisk. */ + pattern++; + + /* If at end of pattern, accept immediately. */ + if (!*pattern) + return 1; + + /* If next character in pattern is known, optimize. */ + if (*pattern != '?' && *pattern != '*') + { + /* Look instances of the next character in pattern, and try + to match starting from those. */ + for (; *s; s++) + if (*s == *pattern && + match_pattern(s + 1, pattern + 1)) + return 1; + /* Failed. */ + return 0; + } + + /* Move ahead one character at a time and try to match at each + position. */ + for (; *s; s++) + if (match_pattern(s, pattern)) + return 1; + /* Failed. */ + return 0; + } + + /* There must be at least one more character in the string. If we are + at the end, fail. */ + if (!*s) + return 0; + + /* Check if the next character of the string is acceptable. */ + if (*pattern != '?' && *pattern != *s) + return 0; + + /* Move to the next character, both in string and in pattern. */ + s++; + pattern++; + } + /*NOTREACHED*/ +} diff --git a/mktemp.c b/mktemp.c new file mode 100644 index 00000000..919c5317 --- /dev/null +++ b/mktemp.c @@ -0,0 +1,181 @@ +/* THIS FILE HAS BEEN MODIFIED FROM THE ORIGINAL OPENBSD SOURCE */ +/* Changes: Removed mktemp */ + +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char rcsid[] = "$OpenBSD: mktemp.c,v 1.13 1998/06/30 23:03:13 deraadt Exp $"; +#endif /* LIBC_SCCS and not lint */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <unistd.h> + +static int _gettemp __P((char *, int *, int, int)); + +int +mkstemps(path, slen) + char *path; + int slen; +{ + int fd; + + return (_gettemp(path, &fd, 0, slen) ? fd : -1); +} + +int +mkstemp(path) + char *path; +{ + int fd; + + return (_gettemp(path, &fd, 0, 0) ? fd : -1); +} + +char * +mkdtemp(path) + char *path; +{ + return(_gettemp(path, (int *)NULL, 1, 0) ? path : (char *)NULL); +} + +static int +_gettemp(path, doopen, domkdir, slen) + char *path; + register int *doopen; + int domkdir; + int slen; +{ + register char *start, *trv, *suffp; + struct stat sbuf; + int pid, rval; + + if (doopen && domkdir) { + errno = EINVAL; + return(0); + } + + for (trv = path; *trv; ++trv) + ; + trv -= slen; + suffp = trv; + --trv; + if (trv < path) { + errno = EINVAL; + return (0); + } + pid = getpid(); + while (*trv == 'X' && pid != 0) { + *trv-- = (pid % 10) + '0'; + pid /= 10; + } + while (*trv == 'X') { + char c; + + pid = (arc4random() & 0xffff) % (26+26); + if (pid < 26) + c = pid + 'A'; + else + c = (pid - 26) + 'a'; + *trv-- = c; + } + start = trv + 1; + + /* + * check the target directory; if you have six X's and it + * doesn't exist this runs for a *very* long time. + */ + if (doopen || domkdir) { + for (;; --trv) { + if (trv <= path) + break; + if (*trv == '/') { + *trv = '\0'; + rval = stat(path, &sbuf); + *trv = '/'; + if (rval != 0) + return(0); + if (!S_ISDIR(sbuf.st_mode)) { + errno = ENOTDIR; + return(0); + } + break; + } + } + } + + for (;;) { + if (doopen) { + if ((*doopen = + open(path, O_CREAT|O_EXCL|O_RDWR, 0600)) >= 0) + return(1); + if (errno != EEXIST) + return(0); + } else if (domkdir) { + if (mkdir(path, 0700) == 0) + return(1); + if (errno != EEXIST) + return(0); + } else if (lstat(path, &sbuf)) + return(errno == ENOENT ? 1 : 0); + + /* tricky little algorithm for backward compatibility */ + for (trv = start;;) { + if (!*trv) + return (0); + if (*trv == 'Z') { + if (trv == suffp) + return (0); + *trv++ = 'a'; + } else { + if (isdigit(*trv)) + *trv = 'a'; + else if (*trv == 'z') /* inc from z to A */ + *trv = 'A'; + else { + if (trv == suffp) + return (0); + ++*trv; + } + break; + } + } + } + /*NOTREACHED*/ +} diff --git a/mktemp.h b/mktemp.h new file mode 100644 index 00000000..5d380058 --- /dev/null +++ b/mktemp.h @@ -0,0 +1,7 @@ +#ifndef _MKTEMP_H +#define _MKTEMP_H +int mkstemps(char *path, int slen); +int mkstemp(char *path); +char *mkdtemp(char *path); + +#endif /* _MKTEMP_H */ diff --git a/mpaux.c b/mpaux.c new file mode 100644 index 00000000..fd2c1803 --- /dev/null +++ b/mpaux.c @@ -0,0 +1,46 @@ +/* + +mpaux.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sun Jul 16 04:29:30 1995 ylo + +This file contains various auxiliary functions related to multiple +precision integers. + +*/ + +#include "includes.h" +RCSID("$Id: mpaux.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include <openssl/bn.h> +#include "getput.h" +#include "xmalloc.h" + +#include <openssl/md5.h> + +void +compute_session_id(unsigned char session_id[16], + unsigned char cookie[8], + unsigned int host_key_bits, + BIGNUM *host_key_n, + unsigned int session_key_bits, + BIGNUM *session_key_n) +{ + unsigned int bytes = (host_key_bits + 7) / 8 + (session_key_bits + 7) / 8 + 8; + unsigned char *buf = xmalloc(bytes); + MD5_CTX md; + + BN_bn2bin(host_key_n, buf); + BN_bn2bin(session_key_n, buf + (host_key_bits + 7 ) / 8); + memcpy(buf + (host_key_bits + 7) / 8 + (session_key_bits + 7) / 8, + cookie, 8); + MD5_Init(&md); + MD5_Update(&md, buf, bytes); + MD5_Final(session_id, &md); + xfree(buf); +} diff --git a/mpaux.h b/mpaux.h new file mode 100644 index 00000000..3ad06813 --- /dev/null +++ b/mpaux.h @@ -0,0 +1,32 @@ +/* + +mpaux.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sun Jul 16 04:29:30 1995 ylo + +This file contains various auxiliary functions related to multiple +precision integers. + +*/ + +/* RCSID("$Id: mpaux.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef MPAUX_H +#define MPAUX_H + +/* Computes a 16-byte session id in the global variable session_id. + The session id is computed by concatenating the linearized, msb + first representations of host_key_n, session_key_n, and the cookie. */ +void compute_session_id(unsigned char session_id[16], + unsigned char cookie[8], + unsigned int host_key_bits, + BIGNUM *host_key_n, + unsigned int session_key_bits, + BIGNUM *session_key_n); + +#endif /* MPAUX_H */ diff --git a/nchan.c b/nchan.c new file mode 100644 index 00000000..fcaeae40 --- /dev/null +++ b/nchan.c @@ -0,0 +1,187 @@ +#include "includes.h" +RCSID("$Id: nchan.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "ssh.h" + +#include "buffer.h" +#include "packet.h" +#include "channels.h" +#include "nchan.h" + +static void chan_send_ieof(Channel *c); +static void chan_send_oclose(Channel *c); +static void chan_shutdown_write(Channel *c); +static void chan_shutdown_read(Channel *c); +static void chan_delele_if_full_closed(Channel *c); + +/* + * EVENTS: update channel input/output states + * execute ACTIONS + */ +/* events concerning the INPUT from socket for channel (istate) */ +void +chan_rcvd_oclose(Channel *c){ + switch(c->istate){ + case CHAN_INPUT_WAIT_OCLOSE: + debug("channel %d: INPUT_WAIT_OCLOSE -> INPUT_CLOSED [rcvd OCLOSE]", c->self); + c->istate=CHAN_INPUT_CLOSED; + chan_delele_if_full_closed(c); + break; + case CHAN_INPUT_OPEN: + debug("channel %d: INPUT_OPEN -> INPUT_CLOSED [rvcd OCLOSE, send IEOF]", c->self); + chan_shutdown_read(c); + chan_send_ieof(c); + c->istate=CHAN_INPUT_CLOSED; + chan_delele_if_full_closed(c); + break; + default: + debug("protocol error: chan_rcvd_oclose %d for istate %d",c->self,c->istate); + break; + } +} +void +chan_read_failed(Channel *c){ + switch(c->istate){ + case CHAN_INPUT_OPEN: + debug("channel %d: INPUT_OPEN -> INPUT_WAIT_DRAIN [read failed]", c->self); + chan_shutdown_read(c); + c->istate=CHAN_INPUT_WAIT_DRAIN; + break; + default: + debug("internal error: we do not read, but chan_read_failed %d for istate %d", + c->self,c->istate); + break; + } +} +void +chan_ibuf_empty(Channel *c){ + if(buffer_len(&c->input)){ + debug("internal error: chan_ibuf_empty %d for non empty buffer",c->self); + return; + } + switch(c->istate){ + case CHAN_INPUT_WAIT_DRAIN: + debug("channel %d: INPUT_WAIT_DRAIN -> INPUT_WAIT_OCLOSE [inbuf empty, send IEOF]", c->self); + chan_send_ieof(c); + c->istate=CHAN_INPUT_WAIT_OCLOSE; + break; + default: + debug("internal error: chan_ibuf_empty %d for istate %d",c->self,c->istate); + break; + } +} +/* events concerning the OUTPUT from channel for socket (ostate) */ +void +chan_rcvd_ieof(Channel *c){ + switch(c->ostate){ + case CHAN_OUTPUT_OPEN: + debug("channel %d: OUTPUT_OPEN -> OUTPUT_WAIT_DRAIN [rvcd IEOF]", c->self); + c->ostate=CHAN_OUTPUT_WAIT_DRAIN; + break; + case CHAN_OUTPUT_WAIT_IEOF: + debug("channel %d: OUTPUT_WAIT_IEOF -> OUTPUT_CLOSED [rvcd IEOF]", c->self); + c->ostate=CHAN_OUTPUT_CLOSED; + chan_delele_if_full_closed(c); + break; + default: + debug("protocol error: chan_rcvd_ieof %d for ostate %d", c->self,c->ostate); + break; + } +} +void +chan_write_failed(Channel *c){ + switch(c->ostate){ + case CHAN_OUTPUT_OPEN: + debug("channel %d: OUTPUT_OPEN -> OUTPUT_WAIT_IEOF [write failed]", c->self); + chan_send_oclose(c); + c->ostate=CHAN_OUTPUT_WAIT_IEOF; + break; + case CHAN_OUTPUT_WAIT_DRAIN: + debug("channel %d: OUTPUT_WAIT_DRAIN -> OUTPUT_CLOSED [write failed]", c->self); + chan_send_oclose(c); + c->ostate=CHAN_OUTPUT_CLOSED; + chan_delele_if_full_closed(c); + break; + default: + debug("internal error: chan_write_failed %d for ostate %d",c->self,c->ostate); + break; + } +} +void +chan_obuf_empty(Channel *c){ + if(buffer_len(&c->output)){ + debug("internal error: chan_obuf_empty %d for non empty buffer",c->self); + return; + } + switch(c->ostate){ + case CHAN_OUTPUT_WAIT_DRAIN: + debug("channel %d: OUTPUT_WAIT_DRAIN -> OUTPUT_CLOSED [obuf empty, send OCLOSE]", c->self); + chan_send_oclose(c); + c->ostate=CHAN_OUTPUT_CLOSED; + chan_delele_if_full_closed(c); + break; + default: + debug("internal error: chan_obuf_empty %d for ostate %d",c->self,c->ostate); + break; + } +} +/* + * ACTIONS: should never update c->istate or c->ostate + */ +static void +chan_send_ieof(Channel *c){ + switch(c->istate){ + case CHAN_INPUT_OPEN: + case CHAN_INPUT_WAIT_DRAIN: + packet_start(SSH_MSG_CHANNEL_INPUT_EOF); + packet_put_int(c->remote_id); + packet_send(); + break; + default: + debug("internal error: channel %d: cannot send IEOF for istate %d",c->self,c->istate); + break; + } +} +static void +chan_send_oclose(Channel *c){ + switch(c->ostate){ + case CHAN_OUTPUT_OPEN: + case CHAN_OUTPUT_WAIT_DRAIN: + chan_shutdown_write(c); + buffer_consume(&c->output, buffer_len(&c->output)); + packet_start(SSH_MSG_CHANNEL_OUTPUT_CLOSE); + packet_put_int(c->remote_id); + packet_send(); + break; + default: + debug("internal error: channel %d: cannot send OCLOSE for ostate %d",c->self,c->istate); + break; + } +} +/* helper */ +static void +chan_shutdown_write(Channel *c){ + debug("channel %d: shutdown_write", c->self); + if(shutdown(c->sock, SHUT_WR)<0) + error("chan_shutdown_write failed for #%d/fd%d: %.100s", + c->self, c->sock, strerror(errno)); +} +static void +chan_shutdown_read(Channel *c){ + debug("channel %d: shutdown_read", c->self); + if(shutdown(c->sock, SHUT_RD)<0) + error("chan_shutdown_read failed for #%d/fd%d: %.100s", + c->self, c->sock, strerror(errno)); +} +static void +chan_delele_if_full_closed(Channel *c){ + if(c->istate==CHAN_INPUT_CLOSED && c->ostate==CHAN_OUTPUT_CLOSED){ + debug("channel %d: closing", c->self); + channel_free(c->self); + } +} +void +chan_init_iostates(Channel *c){ + c->ostate=CHAN_OUTPUT_OPEN; + c->istate=CHAN_INPUT_OPEN; +} diff --git a/nchan.h b/nchan.h new file mode 100644 index 00000000..16d360d3 --- /dev/null +++ b/nchan.h @@ -0,0 +1,57 @@ +/* RCSID("$Id: nchan.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef NCHAN_H +#define NCHAN_H + +/* + * SSH Protocol 1.5 aka New Channel Protocol + * Thanks to Martina, Axel and everyone who left Erlangen, leaving me bored. + * Written by Markus Friedl in October 1999 + * + * Protocol versions 1.3 and 1.5 differ in the handshake protocol used for the + * tear down of channels: + * + * 1.3: strict request-ack-protocol: + * CLOSE -> + * <- CLOSE_CONFIRM + * + * 1.5: uses variations of: + * IEOF -> + * <- OCLOSE + * <- IEOF + * OCLOSE -> + * i.e. both sides have to close the channel + * + * See the debugging output from 'ssh -v' and 'sshd -d' of + * ssh-1.2.27 as an example. + * + */ + +/* ssh-proto-1.5 overloads prot-1.3-message-types */ +#define SSH_MSG_CHANNEL_INPUT_EOF SSH_MSG_CHANNEL_CLOSE +#define SSH_MSG_CHANNEL_OUTPUT_CLOSE SSH_MSG_CHANNEL_CLOSE_CONFIRMATION + +/* possible input states */ +#define CHAN_INPUT_OPEN 0x01 +#define CHAN_INPUT_WAIT_DRAIN 0x02 +#define CHAN_INPUT_WAIT_OCLOSE 0x04 +#define CHAN_INPUT_CLOSED 0x08 + +/* possible output states */ +#define CHAN_OUTPUT_OPEN 0x10 +#define CHAN_OUTPUT_WAIT_DRAIN 0x20 +#define CHAN_OUTPUT_WAIT_IEOF 0x40 +#define CHAN_OUTPUT_CLOSED 0x80 + +/* EVENTS for the input state */ +void chan_rcvd_oclose(Channel *c); +void chan_read_failed(Channel *c); +void chan_ibuf_empty(Channel *c); + +/* EVENTS for the output state */ +void chan_rcvd_ieof(Channel *c); +void chan_write_failed(Channel *c); +void chan_obuf_empty(Channel *c); + +void chan_init_iostates(Channel *c); +#endif diff --git a/nchan.ms b/nchan.ms new file mode 100644 index 00000000..b01512f7 --- /dev/null +++ b/nchan.ms @@ -0,0 +1,71 @@ +.TL +OpenSSH Channel Close Protocol 1.5 Implementation +.SH +Channel Input State Diagram +.PS +reset +l=1 +s=1.2 +ellipsewid=s*ellipsewid +boxwid=s*boxwid +ellipseht=s*ellipseht +S1: ellipse "INPUT" "OPEN" +move right 2*l from last ellipse.e +S4: ellipse "INPUT" "CLOSED" +move down l from last ellipse.s +S3: ellipse "INPUT" "WAIT" "OCLOSED" +move down l from 1st ellipse.s +S2: ellipse "INPUT" "WAIT" "DRAIN" +arrow "" "rcvd OCLOSE/" "shutdown_read" "send IEOF" from S1.e to S4.w +arrow "ibuf_empty/" "send IEOF" from S2.e to S3.w +arrow from S1.s to S2.n +box invis "read_failed/" "shutdown_read" with .e at last arrow.c +arrow from S3.n to S4.s +box invis "rcvd OCLOSE/" "-" with .w at last arrow.c +ellipse wid .9*ellipsewid ht .9*ellipseht at S4 +arrow "start" "" from S1.w+(-0.5,0) to S1.w +.PE +.SH +Channel Output State Diagram +.PS +S1: ellipse "OUTPUT" "OPEN" +move right 2*l from last ellipse.e +S3: ellipse "OUTPUT" "WAIT" "IEOF" +move down l from last ellipse.s +S4: ellipse "OUTPUT" "CLOSED" +move down l from 1st ellipse.s +S2: ellipse "OUTPUT" "WAIT" "DRAIN" +arrow "" "write_failed/" "shutdown_write" "send OCLOSE" from S1.e to S3.w +arrow "obuf_empty ||" "write_failed/" "shutdown_write" "send OCLOSE" from S2.e to S4.w +arrow from S1.s to S2.n +box invis "rcvd IEOF/" "-" with .e at last arrow.c +arrow from S3.s to S4.n +box invis "rcvd IEOF/" "-" with .w at last arrow.c +ellipse wid .9*ellipsewid ht .9*ellipseht at S4 +arrow "start" "" from S1.w+(-0.5,0) to S1.w +.PE +.SH +Notes +.PP +The input buffer is filled with data from the socket +(the socket represents the local comsumer/producer of the +forwarded channel). +The data is then sent over the INPUT-end of the channel to the +remote peer. +Data sent by the peer is received on the OUTPUT-end, +saved in the output buffer and written to the socket. +.PP +If the local protocol instance has forwarded all data on the +INPUT-end of the channel, it sends an IEOF message to the peer. +If the peer receives the IEOF and has comsumed all +data he replies with an OCLOSE. +When the local instance receives the OCLOSE +he considers the INPUT-half of the channel closed. +The peer has his OUTOUT-half closed. +.PP +A channel can be deallocated by a protocol instance +if both the INPUT- and the OUTOUT-half on his +side of the channel are closed. +Note that when an instance is unable to comsume the +received data, he is permitted to send an OCLOSE +before the matching IEOF is received. diff --git a/openssh.spec b/openssh.spec new file mode 100644 index 00000000..7ce58849 --- /dev/null +++ b/openssh.spec @@ -0,0 +1,105 @@ +Summary: OpenSSH free Secure Shell (SSH) implementation +Name: openssh +Version: 1.2pre3 +Release: 1 +Packager: Damien Miller <djm@ibs.com.au> +Source0: openssh-%{version}-linux.tar.gz +Copyright: BSD +Group: Applications/Internet +BuildRoot: /tmp/openssh-%{version}-buildroot + +%description +Ssh (Secure Shell) a program for logging into a remote machine and for +executing commands in a remote machine. It is intended to replace +rlogin and rsh, and provide secure encrypted communications between +two untrusted hosts over an insecure network. X11 connections and +arbitrary TCP/IP ports can also be forwarded over the secure channel. + +OpenSSH is OpenBSD's rework of the last free version of SSH, bringing it +up to date in terms of security and features, as well as removing all +patented algorithms to seperate libraries (OpenSSL). + +%changelog +* Wed Oct 27 1999 Damien Miller <djm@ibs.com.au> +- Initial RPMification, based on Jan "Yenya" Kasprzak's <kas@fi.muni.cz> spec. + +%prep + +%setup -n openssh + +%build + +make -f Makefile.GNU OPT_FLAGS="$RPM_OPT_FLAGS" + +%install +rm -rf $RPM_BUILD_ROOT +mkdir -p $RPM_BUILD_ROOT/usr/bin +mkdir -p $RPM_BUILD_ROOT/usr/sbin +mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d +mkdir -p $RPM_BUILD_ROOT/etc/pam.d +mkdir -p $RPM_BUILD_ROOT/etc/ssh +mkdir -p $RPM_BUILD_ROOT/usr/man/man1 +mkdir -p $RPM_BUILD_ROOT/usr/man/man8 + +install -m644 ssh.pam $RPM_BUILD_ROOT/etc/pam.d/ssh +install -m755 sshd.init $RPM_BUILD_ROOT/etc/rc.d/init.d/sshd +install -m600 ssh_config $RPM_BUILD_ROOT/etc/ssh/ssh_config +install -m600 sshd_config $RPM_BUILD_ROOT/etc/ssh/sshd_config + +install -s -m755 bin/sshd $RPM_BUILD_ROOT/usr/sbin +install -s -m755 bin/ssh $RPM_BUILD_ROOT/usr/bin +install -s -m755 bin/scp $RPM_BUILD_ROOT/usr/bin +install -s -m755 bin/ssh-agent $RPM_BUILD_ROOT/usr/bin +install -s -m755 bin/ssh-add $RPM_BUILD_ROOT/usr/bin +install -s -m755 bin/ssh-keygen $RPM_BUILD_ROOT/usr/bin + +install -m644 sshd.8 $RPM_BUILD_ROOT/usr/man/man8 +install -m644 ssh.1 $RPM_BUILD_ROOT/usr/man/man1 +install -m644 scp.1 $RPM_BUILD_ROOT/usr/man/man1 +install -m644 ssh-agent.1 $RPM_BUILD_ROOT/usr/man/man1 +install -m644 ssh-add.1 $RPM_BUILD_ROOT/usr/man/man1 +install -m644 ssh-keygen.1 $RPM_BUILD_ROOT/usr/man/man1 + +%clean +rm -rf $RPM_BUILD_ROOT + +%post +/sbin/chkconfig --add sshd +if [ ! -f /etc/ssh/ssh_host_key -o ! -s /etc/ssh/ssh_host_key ]; then + /usr/bin/ssh-keygen -b 1024 -f /etc/ssh/ssh_host_key -N '' >&2 +fi +if test -r /var/run/sshd.pid +then + /etc/rc.d/init.d/sshd restart >&2 +fi + +%preun +if [ "$1" = 0 ] +then + /etc/rc.d/init.d/sshd stop >&2 + /sbin/chkconfig --del sshd +fi + +%files +%defattr(-,root,root) +%doc COPYING.Ylonen ChangeLog ChangeLog.linux OVERVIEW +%doc README README.openssh +%attr(0755,root,root) /usr/sbin/sshd +%attr(0755,root,root) /usr/bin/ssh +%attr(0755,root,root) /usr/bin/ssh-agent +%attr(0755,root,root) /usr/bin/ssh-keygen +%attr(0755,root,root) /usr/bin/ssh-add +%attr(0755,root,root) /usr/bin/scp + +%attr(0755,root,root) /usr/man/man8/sshd.8 +%attr(0755,root,root) /usr/man/man1/ssh.1 +%attr(0755,root,root) /usr/man/man1/ssh-agent.1 +%attr(0755,root,root) /usr/man/man1/ssh-keygen.1 +%attr(0755,root,root) /usr/man/man1/ssh-add.1 +%attr(0755,root,root) /usr/man/man1/scp.1 + +%attr(0600,root,root) %config /etc/ssh/sshd_config +%attr(0600,root,root) %config /etc/pam.d/ssh +%attr(0755,root,root) %config /etc/rc.d/init.d/sshd +%attr(0644,root,root) %config /etc/ssh/ssh_config + diff --git a/packet.c b/packet.c new file mode 100644 index 00000000..7e74c73b --- /dev/null +++ b/packet.c @@ -0,0 +1,762 @@ +/* + +packet.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sat Mar 18 02:40:40 1995 ylo + +This file contains code implementing the packet protocol and communication +with the other side. This same code is used both on client and server side. + +*/ + +#include "includes.h" +RCSID("$Id: packet.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "xmalloc.h" +#include "buffer.h" +#include "packet.h" +#include "bufaux.h" +#include "ssh.h" +#include "crc32.h" +#include "cipher.h" +#include "getput.h" + +#include "compress.h" +#include "deattack.h" + +/* This variable contains the file descriptors used for communicating with + the other side. connection_in is used for reading; connection_out + for writing. These can be the same descriptor, in which case it is + assumed to be a socket. */ +static int connection_in = -1; +static int connection_out = -1; + +/* Cipher type. This value is only used to determine whether to pad the + packets with zeroes or random data. */ +static int cipher_type = SSH_CIPHER_NONE; + +/* Protocol flags for the remote side. */ +static unsigned int remote_protocol_flags = 0; + +/* Encryption context for receiving data. This is only used for decryption. */ +static CipherContext receive_context; +/* Encryption coontext for sending data. This is only used for encryption. */ +static CipherContext send_context; + +/* Buffer for raw input data from the socket. */ +static Buffer input; + +/* Buffer for raw output data going to the socket. */ +static Buffer output; + +/* Buffer for the partial outgoing packet being constructed. */ +static Buffer outgoing_packet; + +/* Buffer for the incoming packet currently being processed. */ +static Buffer incoming_packet; + +/* Scratch buffer for packet compression/decompression. */ +static Buffer compression_buffer; + +/* Flag indicating whether packet compression/decompression is enabled. */ +static int packet_compression = 0; + +/* Flag indicating whether this module has been initialized. */ +static int initialized = 0; + +/* Set to true if the connection is interactive. */ +static int interactive_mode = 0; + +/* Sets the descriptors used for communication. Disables encryption until + packet_set_encryption_key is called. */ + +void +packet_set_connection(int fd_in, int fd_out) +{ + connection_in = fd_in; + connection_out = fd_out; + cipher_type = SSH_CIPHER_NONE; + cipher_set_key(&send_context, SSH_CIPHER_NONE, (unsigned char *)"", 0, 1); + cipher_set_key(&receive_context, SSH_CIPHER_NONE, (unsigned char *)"", 0, 0); + if (!initialized) + { + initialized = 1; + buffer_init(&input); + buffer_init(&output); + buffer_init(&outgoing_packet); + buffer_init(&incoming_packet); + } + + /* Kludge: arrange the close function to be called from fatal(). */ + fatal_add_cleanup((void (*)(void *))packet_close, NULL); +} + +/* Sets the connection into non-blocking mode. */ + +void +packet_set_nonblocking() +{ + /* Set the socket into non-blocking mode. */ + if (fcntl(connection_in, F_SETFL, O_NONBLOCK) < 0) + error("fcntl O_NONBLOCK: %.100s", strerror(errno)); + + if (connection_out != connection_in) + { + if (fcntl(connection_out, F_SETFL, O_NONBLOCK) < 0) + error("fcntl O_NONBLOCK: %.100s", strerror(errno)); + } +} + +/* Returns the socket used for reading. */ + +int +packet_get_connection_in() +{ + return connection_in; +} + +/* Returns the descriptor used for writing. */ + +int +packet_get_connection_out() +{ + return connection_out; +} + +/* Closes the connection and clears and frees internal data structures. */ + +void +packet_close() +{ + if (!initialized) + return; + initialized = 0; + if (connection_in == connection_out) + { + shutdown(connection_out, SHUT_RDWR); + close(connection_out); + } + else + { + close(connection_in); + close(connection_out); + } + buffer_free(&input); + buffer_free(&output); + buffer_free(&outgoing_packet); + buffer_free(&incoming_packet); + if (packet_compression) + { + buffer_free(&compression_buffer); + buffer_compress_uninit(); + } +} + +/* Sets remote side protocol flags. */ + +void +packet_set_protocol_flags(unsigned int protocol_flags) +{ + remote_protocol_flags = protocol_flags; + channel_set_options((protocol_flags & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) != 0); +} + +/* Returns the remote protocol flags set earlier by the above function. */ + +unsigned int +packet_get_protocol_flags() +{ + return remote_protocol_flags; +} + +/* Starts packet compression from the next packet on in both directions. + Level is compression level 1 (fastest) - 9 (slow, best) as in gzip. */ + +void +packet_start_compression(int level) +{ + if (packet_compression) + fatal("Compression already enabled."); + packet_compression = 1; + buffer_init(&compression_buffer); + buffer_compress_init(level); +} + +/* Encrypts the given number of bytes, copying from src to dest. + bytes is known to be a multiple of 8. */ + +void +packet_encrypt(CipherContext *cc, void *dest, void *src, + unsigned int bytes) +{ + assert((bytes % 8) == 0); + cipher_encrypt(cc, dest, src, bytes); +} + +/* Decrypts the given number of bytes, copying from src to dest. + bytes is known to be a multiple of 8. */ + +void +packet_decrypt(CipherContext *cc, void *dest, void *src, + unsigned int bytes) +{ + int i; + + assert((bytes % 8) == 0); + + /* + Cryptographic attack detector for ssh - Modifications for packet.c + (C)1998 CORE-SDI, Buenos Aires Argentina + Ariel Futoransky(futo@core-sdi.com) + */ + switch (cc->type) + { + case SSH_CIPHER_NONE: + i = DEATTACK_OK; + break; + default: + i = detect_attack(src, bytes, NULL); + break; + } + + if (i == DEATTACK_DETECTED) + packet_disconnect("crc32 compensation attack: network attack detected"); + + cipher_decrypt(cc, dest, src, bytes); +} + +/* Causes any further packets to be encrypted using the given key. The same + key is used for both sending and reception. However, both directions + are encrypted independently of each other. */ + +void +packet_set_encryption_key(const unsigned char *key, unsigned int keylen, + int cipher, int is_client) +{ + cipher_type = cipher; + if (cipher == SSH_CIPHER_RC4) + { + if (is_client) + { /* In client: use first half for receiving, second for sending. */ + cipher_set_key(&receive_context, cipher, key, keylen / 2, 0); + cipher_set_key(&send_context, cipher, key + keylen / 2, + keylen / 2, 1); + } + else + { /* In server: use first half for sending, second for receiving. */ + cipher_set_key(&receive_context, cipher, key + keylen / 2, + keylen / 2, 0); + cipher_set_key(&send_context, cipher, key, keylen / 2, 1); + } + } + else + { + /* All other ciphers use the same key in both directions for now. */ + cipher_set_key(&receive_context, cipher, key, keylen, 0); + cipher_set_key(&send_context, cipher, key, keylen, 1); + } +} + +/* Starts constructing a packet to send. */ + +void +packet_start(int type) +{ + char buf[9]; + + buffer_clear(&outgoing_packet); + memset(buf, 0, 8); + buf[8] = type; + buffer_append(&outgoing_packet, buf, 9); +} + +/* Appends a character to the packet data. */ + +void +packet_put_char(int value) +{ + char ch = value; + buffer_append(&outgoing_packet, &ch, 1); +} + +/* Appends an integer to the packet data. */ + +void +packet_put_int(unsigned int value) +{ + buffer_put_int(&outgoing_packet, value); +} + +/* Appends a string to packet data. */ + +void +packet_put_string(const char *buf, unsigned int len) +{ + buffer_put_string(&outgoing_packet, buf, len); +} + +/* Appends an arbitrary precision integer to packet data. */ + +void +packet_put_bignum(BIGNUM *value) +{ + buffer_put_bignum(&outgoing_packet, value); +} + +/* Finalizes and sends the packet. If the encryption key has been set, + encrypts the packet before sending. */ + +void +packet_send() +{ + char buf[8], *cp; + int i, padding, len; + unsigned int checksum; + u_int32_t rand = 0; + + /* If using packet compression, compress the payload of the outgoing + packet. */ + if (packet_compression) + { + buffer_clear(&compression_buffer); + buffer_consume(&outgoing_packet, 8); /* Skip padding. */ + buffer_append(&compression_buffer, "\0\0\0\0\0\0\0\0", 8); /* padding */ + buffer_compress(&outgoing_packet, &compression_buffer); + buffer_clear(&outgoing_packet); + buffer_append(&outgoing_packet, buffer_ptr(&compression_buffer), + buffer_len(&compression_buffer)); + } + + /* Compute packet length without padding (add checksum, remove padding). */ + len = buffer_len(&outgoing_packet) + 4 - 8; + + /* Insert padding. */ + padding = 8 - len % 8; + if (cipher_type != SSH_CIPHER_NONE) + { + cp = buffer_ptr(&outgoing_packet); + for (i = 0; i < padding; i++) { + if (i % 4 == 0) + rand = arc4random(); + cp[7 - i] = rand & 0xff; + rand >>= 8; + } + } + buffer_consume(&outgoing_packet, 8 - padding); + + /* Add check bytes. */ + checksum = crc32((unsigned char *)buffer_ptr(&outgoing_packet), + buffer_len(&outgoing_packet)); + PUT_32BIT(buf, checksum); + buffer_append(&outgoing_packet, buf, 4); + +#ifdef PACKET_DEBUG + fprintf(stderr, "packet_send plain: "); + buffer_dump(&outgoing_packet); +#endif + + /* Append to output. */ + PUT_32BIT(buf, len); + buffer_append(&output, buf, 4); + buffer_append_space(&output, &cp, buffer_len(&outgoing_packet)); + packet_encrypt(&send_context, cp, buffer_ptr(&outgoing_packet), + buffer_len(&outgoing_packet)); + +#ifdef PACKET_DEBUG + fprintf(stderr, "encrypted: "); buffer_dump(&output); +#endif + + buffer_clear(&outgoing_packet); + + /* Note that the packet is now only buffered in output. It won\'t be + actually sent until packet_write_wait or packet_write_poll is called. */ +} + +/* Waits until a packet has been received, and returns its type. Note that + no other data is processed until this returns, so this function should + not be used during the interactive session. */ + +int +packet_read(int *payload_len_ptr) +{ + int type, len; + fd_set set; + char buf[8192]; + + /* Since we are blocking, ensure that all written packets have been sent. */ + packet_write_wait(); + + /* Stay in the loop until we have received a complete packet. */ + for (;;) + { + /* Try to read a packet from the buffer. */ + type = packet_read_poll(payload_len_ptr); + if (type == SSH_SMSG_SUCCESS + || type == SSH_SMSG_FAILURE + || type == SSH_CMSG_EOF + || type == SSH_CMSG_EXIT_CONFIRMATION) + packet_integrity_check(*payload_len_ptr, 0, type); + /* If we got a packet, return it. */ + if (type != SSH_MSG_NONE) + return type; + /* Otherwise, wait for some data to arrive, add it to the buffer, + and try again. */ + FD_ZERO(&set); + FD_SET(connection_in, &set); + /* Wait for some data to arrive. */ + select(connection_in + 1, &set, NULL, NULL, NULL); + /* Read data from the socket. */ + len = read(connection_in, buf, sizeof(buf)); + if (len == 0) + fatal("Connection closed by remote host."); + if (len < 0) + fatal("Read from socket failed: %.100s", strerror(errno)); + /* Append it to the buffer. */ + packet_process_incoming(buf, len); + } + /*NOTREACHED*/ +} + +/* Waits until a packet has been received, verifies that its type matches + that given, and gives a fatal error and exits if there is a mismatch. */ + +void +packet_read_expect(int *payload_len_ptr, int expected_type) +{ + int type; + + type = packet_read(payload_len_ptr); + if (type != expected_type) + packet_disconnect("Protocol error: expected packet type %d, got %d", + expected_type, type); +} + +/* Checks if a full packet is available in the data received so far via + packet_process_incoming. If so, reads the packet; otherwise returns + SSH_MSG_NONE. This does not wait for data from the connection. + + SSH_MSG_DISCONNECT is handled specially here. Also, + SSH_MSG_IGNORE messages are skipped by this function and are never returned + to higher levels. + + The returned payload_len does include space consumed by: + Packet length + Padding + Packet type + Check bytes + + + */ + +int +packet_read_poll(int *payload_len_ptr) +{ + unsigned int len, padded_len; + unsigned char *ucp; + char buf[8], *cp; + unsigned int checksum, stored_checksum; + + restart: + + /* Check if input size is less than minimum packet size. */ + if (buffer_len(&input) < 4 + 8) + return SSH_MSG_NONE; + /* Get length of incoming packet. */ + ucp = (unsigned char *)buffer_ptr(&input); + len = GET_32BIT(ucp); + if (len < 1 + 2 + 2 || len > 256*1024) + packet_disconnect("Bad packet length %d.", len); + padded_len = (len + 8) & ~7; + + /* Check if the packet has been entirely received. */ + if (buffer_len(&input) < 4 + padded_len) + return SSH_MSG_NONE; + + /* The entire packet is in buffer. */ + + /* Consume packet length. */ + buffer_consume(&input, 4); + + /* Copy data to incoming_packet. */ + buffer_clear(&incoming_packet); + buffer_append_space(&incoming_packet, &cp, padded_len); + packet_decrypt(&receive_context, cp, buffer_ptr(&input), padded_len); + buffer_consume(&input, padded_len); + +#ifdef PACKET_DEBUG + fprintf(stderr, "read_poll plain: "); buffer_dump(&incoming_packet); +#endif + + /* Compute packet checksum. */ + checksum = crc32((unsigned char *)buffer_ptr(&incoming_packet), + buffer_len(&incoming_packet) - 4); + + /* Skip padding. */ + buffer_consume(&incoming_packet, 8 - len % 8); + + /* Test check bytes. */ + assert(len == buffer_len(&incoming_packet)); + ucp = (unsigned char *)buffer_ptr(&incoming_packet) + len - 4; + stored_checksum = GET_32BIT(ucp); + if (checksum != stored_checksum) + packet_disconnect("Corrupted check bytes on input."); + buffer_consume_end(&incoming_packet, 4); + + /* If using packet compression, decompress the packet. */ + if (packet_compression) + { + buffer_clear(&compression_buffer); + buffer_uncompress(&incoming_packet, &compression_buffer); + buffer_clear(&incoming_packet); + buffer_append(&incoming_packet, buffer_ptr(&compression_buffer), + buffer_len(&compression_buffer)); + } + + /* Get packet type. */ + buffer_get(&incoming_packet, &buf[0], 1); + + /* Return length of payload (without type field). */ + *payload_len_ptr = buffer_len(&incoming_packet); + + /* Handle disconnect message. */ + if ((unsigned char)buf[0] == SSH_MSG_DISCONNECT) + fatal("%.900s", packet_get_string(NULL)); + + /* Ignore ignore messages. */ + if ((unsigned char)buf[0] == SSH_MSG_IGNORE) + goto restart; + + /* Send debug messages as debugging output. */ + if ((unsigned char)buf[0] == SSH_MSG_DEBUG) + { + debug("Remote: %.900s", packet_get_string(NULL)); + goto restart; + } + + /* Return type. */ + return (unsigned char)buf[0]; +} + +/* Buffers the given amount of input characters. This is intended to be + used together with packet_read_poll. */ + +void +packet_process_incoming(const char *buf, unsigned int len) +{ + buffer_append(&input, buf, len); +} + +/* Returns a character from the packet. */ + +unsigned int +packet_get_char() +{ + char ch; + buffer_get(&incoming_packet, &ch, 1); + return (unsigned char)ch; +} + +/* Returns an integer from the packet data. */ + +unsigned int +packet_get_int() +{ + return buffer_get_int(&incoming_packet); +} + +/* Returns an arbitrary precision integer from the packet data. The integer + must have been initialized before this call. */ + +void +packet_get_bignum(BIGNUM *value, int *length_ptr) +{ + *length_ptr = buffer_get_bignum(&incoming_packet, value); +} + +/* Returns a string from the packet data. The string is allocated using + xmalloc; it is the responsibility of the calling program to free it when + no longer needed. The length_ptr argument may be NULL, or point to an + integer into which the length of the string is stored. */ + +char +*packet_get_string(unsigned int *length_ptr) +{ + return buffer_get_string(&incoming_packet, length_ptr); +} + +/* Sends a diagnostic message from the server to the client. This message + can be sent at any time (but not while constructing another message). + The message is printed immediately, but only if the client is being + executed in verbose mode. These messages are primarily intended to + ease debugging authentication problems. The length of the formatted + message must not exceed 1024 bytes. This will automatically call + packet_write_wait. */ + +void +packet_send_debug(const char *fmt, ...) +{ + char buf[1024]; + va_list args; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + packet_start(SSH_MSG_DEBUG); + packet_put_string(buf, strlen(buf)); + packet_send(); + packet_write_wait(); +} + +/* Logs the error plus constructs and sends a disconnect + packet, closes the connection, and exits. This function never returns. + The error message should not contain a newline. The length of the + formatted message must not exceed 1024 bytes. */ + +void +packet_disconnect(const char *fmt, ...) +{ + char buf[1024]; + va_list args; + static int disconnecting = 0; + if (disconnecting) /* Guard against recursive invocations. */ + fatal("packet_disconnect called recursively."); + disconnecting = 1; + + /* Format the message. Note that the caller must make sure the message + is of limited size. */ + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + /* Send the disconnect message to the other side, and wait for it to get + sent. */ + packet_start(SSH_MSG_DISCONNECT); + packet_put_string(buf, strlen(buf)); + packet_send(); + packet_write_wait(); + + /* Stop listening for connections. */ + channel_stop_listening(); + + /* Close the connection. */ + packet_close(); + + /* Display the error locally and exit. */ + fatal("Local: %.100s", buf); +} + +/* Checks if there is any buffered output, and tries to write some of the + output. */ + +void +packet_write_poll() +{ + int len = buffer_len(&output); + if (len > 0) + { + len = write(connection_out, buffer_ptr(&output), len); + if (len <= 0) { + if (errno == EAGAIN) + return; + else + fatal("Write failed: %.100s", strerror(errno)); + } + buffer_consume(&output, len); + } +} + +/* Calls packet_write_poll repeatedly until all pending output data has + been written. */ + +void +packet_write_wait() +{ + packet_write_poll(); + while (packet_have_data_to_write()) + { + fd_set set; + FD_ZERO(&set); + FD_SET(connection_out, &set); + select(connection_out + 1, NULL, &set, NULL, NULL); + packet_write_poll(); + } +} + +/* Returns true if there is buffered data to write to the connection. */ + +int +packet_have_data_to_write() +{ + return buffer_len(&output) != 0; +} + +/* Returns true if there is not too much data to write to the connection. */ + +int +packet_not_very_much_data_to_write() +{ + if (interactive_mode) + return buffer_len(&output) < 16384; + else + return buffer_len(&output) < 128*1024; +} + +/* Informs that the current session is interactive. Sets IP flags for that. */ + +void +packet_set_interactive(int interactive, int keepalives) +{ + int on = 1; + + /* Record that we are in interactive mode. */ + interactive_mode = interactive; + + /* Only set socket options if using a socket (as indicated by the descriptors + being the same). */ + if (connection_in != connection_out) + return; + + if (keepalives) + { + /* Set keepalives if requested. */ + if (setsockopt(connection_in, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, + sizeof(on)) < 0) + error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); + } + + if (interactive) + { + /* Set IP options for an interactive connection. Use IPTOS_LOWDELAY + and TCP_NODELAY. */ + int lowdelay = IPTOS_LOWDELAY; + if (setsockopt(connection_in, IPPROTO_IP, IP_TOS, (void *)&lowdelay, + sizeof(lowdelay)) < 0) + error("setsockopt IPTOS_LOWDELAY: %.100s", strerror(errno)); + if (setsockopt(connection_in, IPPROTO_TCP, TCP_NODELAY, (void *)&on, + sizeof(on)) < 0) + error("setsockopt TCP_NODELAY: %.100s", strerror(errno)); + } + else + { + /* Set IP options for a non-interactive connection. Use + IPTOS_THROUGHPUT. */ + int throughput = IPTOS_THROUGHPUT; + if (setsockopt(connection_in, IPPROTO_IP, IP_TOS, (void *)&throughput, + sizeof(throughput)) < 0) + error("setsockopt IPTOS_THROUGHPUT: %.100s", strerror(errno)); + } +} + +/* Returns true if the current connection is interactive. */ + +int +packet_is_interactive() +{ + return interactive_mode; +} diff --git a/packet.h b/packet.h new file mode 100644 index 00000000..fb84e6c1 --- /dev/null +++ b/packet.h @@ -0,0 +1,166 @@ +/* + +packet.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sat Mar 18 02:02:14 1995 ylo + +Interface for the packet protocol functions. + +*/ + +/* RCSID("$Id: packet.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef PACKET_H +#define PACKET_H + +#include <openssl/bn.h> + +/* Sets the socket used for communication. Disables encryption until + packet_set_encryption_key is called. It is permissible that fd_in + and fd_out are the same descriptor; in that case it is assumed to + be a socket. */ +void packet_set_connection(int fd_in, int fd_out); + +/* Puts the connection file descriptors into non-blocking mode. */ +void packet_set_nonblocking(void); + +/* Returns the file descriptor used for input. */ +int packet_get_connection_in(void); + +/* Returns the file descriptor used for output. */ +int packet_get_connection_out(void); + +/* Closes the connection (both descriptors) and clears and frees + internal data structures. */ +void packet_close(void); + +/* Causes any further packets to be encrypted using the given key. The same + key is used for both sending and reception. However, both directions + are encrypted independently of each other. Cipher types are + defined in ssh.h. */ +void packet_set_encryption_key(const unsigned char *key, unsigned int keylen, + int cipher_type, int is_client); + +/* Sets remote side protocol flags for the current connection. This can + be called at any time. */ +void packet_set_protocol_flags(unsigned int flags); + +/* Returns the remote protocol flags set earlier by the above function. */ +unsigned int packet_get_protocol_flags(void); + +/* Enables compression in both directions starting from the next packet. */ +void packet_start_compression(int level); + +/* Informs that the current session is interactive. Sets IP flags for optimal + performance in interactive use. */ +void packet_set_interactive(int interactive, int keepalives); + +/* Returns true if the current connection is interactive. */ +int packet_is_interactive(void); + +/* Starts constructing a packet to send. */ +void packet_start(int type); + +/* Appends a character to the packet data. */ +void packet_put_char(int ch); + +/* Appends an integer to the packet data. */ +void packet_put_int(unsigned int value); + +/* Appends an arbitrary precision integer to packet data. */ +void packet_put_bignum(BIGNUM *value); + +/* Appends a string to packet data. */ +void packet_put_string(const char *buf, unsigned int len); + +/* Finalizes and sends the packet. If the encryption key has been set, + encrypts the packet before sending. */ +void packet_send(void); + +/* Waits until a packet has been received, and returns its type. */ +int packet_read(int *payload_len_ptr); + +/* Waits until a packet has been received, verifies that its type matches + that given, and gives a fatal error and exits if there is a mismatch. */ +void packet_read_expect(int *payload_len_ptr, int type); + +/* Checks if a full packet is available in the data received so far via + packet_process_incoming. If so, reads the packet; otherwise returns + SSH_MSG_NONE. This does not wait for data from the connection. + + SSH_MSG_DISCONNECT is handled specially here. Also, + SSH_MSG_IGNORE messages are skipped by this function and are never returned + to higher levels. */ +int packet_read_poll(int *packet_len_ptr); + +/* Buffers the given amount of input characters. This is intended to be + used together with packet_read_poll. */ +void packet_process_incoming(const char *buf, unsigned int len); + +/* Returns a character (0-255) from the packet data. */ +unsigned int packet_get_char(void); + +/* Returns an integer from the packet data. */ +unsigned int packet_get_int(void); + +/* Returns an arbitrary precision integer from the packet data. The integer + must have been initialized before this call. */ +void packet_get_bignum(BIGNUM *value, int *length_ptr); + +/* Returns a string from the packet data. The string is allocated using + xmalloc; it is the responsibility of the calling program to free it when + no longer needed. The length_ptr argument may be NULL, or point to an + integer into which the length of the string is stored. */ +char *packet_get_string(unsigned int *length_ptr); + +/* Logs the error in syslog using LOG_INFO, constructs and sends a disconnect + packet, closes the connection, and exits. This function never returns. + The error message should not contain a newline. The total length of the + message must not exceed 1024 bytes. */ +void packet_disconnect(const char *fmt, ...); + +/* Sends a diagnostic message to the other side. This message + can be sent at any time (but not while constructing another message). + The message is printed immediately, but only if the client is being + executed in verbose mode. These messages are primarily intended to + ease debugging authentication problems. The total length of the message + must not exceed 1024 bytes. This will automatically call + packet_write_wait. If the remote side protocol flags do not indicate + that it supports SSH_MSG_DEBUG, this will do nothing. */ +void packet_send_debug(const char *fmt, ...); + +/* Checks if there is any buffered output, and tries to write some of the + output. */ +void packet_write_poll(void); + +/* Waits until all pending output data has been written. */ +void packet_write_wait(void); + +/* Returns true if there is buffered data to write to the connection. */ +int packet_have_data_to_write(void); + +/* Returns true if there is not too much data to write to the connection. */ +int packet_not_very_much_data_to_write(void); + +/* Stores tty modes from the fd into current packet. */ +void tty_make_modes(int fd); + +/* Parses tty modes for the fd from the current packet. */ +void tty_parse_modes(int fd, int *n_bytes_ptr); + +#define packet_integrity_check(payload_len, expected_len, type) \ +do { \ + int _p = (payload_len), _e = (expected_len); \ + if (_p != _e) { \ + log("Packet integrity error (%d != %d) at %s:%d", \ + _p, _e, __FILE__, __LINE__); \ + packet_disconnect("Packet integrity error. (%d)", (type)); \ + } \ +} while (0) + +#endif /* PACKET_H */ @@ -0,0 +1,264 @@ +/* + +pty.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Fri Mar 17 04:37:25 1995 ylo + +Allocating a pseudo-terminal, and making it the controlling tty. + +*/ + +#include "includes.h" +RCSID("$Id: pty.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "pty.h" +#include "ssh.h" + +/* Pty allocated with _getpty gets broken if we do I_PUSH:es to it. */ +#if defined(HAVE__GETPTY) || defined(HAVE_OPENPTY) +#undef HAVE_DEV_PTMX +#endif + +#ifndef O_NOCTTY +#define O_NOCTTY 0 +#endif + +/* Allocates and opens a pty. Returns 0 if no pty could be allocated, + or nonzero if a pty was successfully allocated. On success, open file + descriptors for the pty and tty sides and the name of the tty side are + returned (the buffer must be able to hold at least 64 characters). */ + +int pty_allocate(int *ptyfd, int *ttyfd, char *namebuf) +{ +#ifdef HAVE_OPENPTY + + /* openpty(3) exists in OSF/1 and some other os'es */ + + int i; + + i = openpty(ptyfd, ttyfd, namebuf, NULL, NULL); + + if (i < 0) + { + error("openpty: %.100s", strerror(errno)); + return 0; + } + + return 1; + +#else /* HAVE_OPENPTY */ +#ifdef HAVE__GETPTY + + /* _getpty(3) exists in SGI Irix 4.x, 5.x & 6.x -- it generates more + pty's automagically when needed */ + + char *slave; + + slave = _getpty(ptyfd, O_RDWR, 0622, 0); + if (slave == NULL) + { + error("_getpty: %.100s", strerror(errno)); + return 0; + } + strcpy(namebuf, slave); + /* Open the slave side. */ + *ttyfd = open(namebuf, O_RDWR|O_NOCTTY); + if (*ttyfd < 0) + { + error("%.200s: %.100s", namebuf, strerror(errno)); + close(*ptyfd); + return 0; + } + return 1; + +#else /* HAVE__GETPTY */ +#ifdef HAVE_DEV_PTMX + /* This code is used e.g. on Solaris 2.x. (Note that Solaris 2.3 also has + bsd-style ptys, but they simply do not work.) */ + + int ptm; + char *pts; + + ptm = open("/dev/ptmx", O_RDWR|O_NOCTTY); + if (ptm < 0) + { + error("/dev/ptmx: %.100s", strerror(errno)); + return 0; + } + if (grantpt(ptm) < 0) + { + error("grantpt: %.100s", strerror(errno)); + return 0; + } + if (unlockpt(ptm) < 0) + { + error("unlockpt: %.100s", strerror(errno)); + return 0; + } + pts = ptsname(ptm); + if (pts == NULL) + error("Slave pty side name could not be obtained."); + strcpy(namebuf, pts); + *ptyfd = ptm; + + /* Open the slave side. */ + *ttyfd = open(namebuf, O_RDWR|O_NOCTTY); + if (*ttyfd < 0) + { + error("%.100s: %.100s", namebuf, strerror(errno)); + close(*ptyfd); + return 0; + } + /* Push the appropriate streams modules, as described in Solaris pts(7). */ + if (ioctl(*ttyfd, I_PUSH, "ptem") < 0) + error("ioctl I_PUSH ptem: %.100s", strerror(errno)); + if (ioctl(*ttyfd, I_PUSH, "ldterm") < 0) + error("ioctl I_PUSH ldterm: %.100s", strerror(errno)); + if (ioctl(*ttyfd, I_PUSH, "ttcompat") < 0) + error("ioctl I_PUSH ttcompat: %.100s", strerror(errno)); + return 1; + +#else /* HAVE_DEV_PTMX */ +#ifdef HAVE_DEV_PTS_AND_PTC + + /* AIX-style pty code. */ + + const char *name; + + *ptyfd = open("/dev/ptc", O_RDWR|O_NOCTTY); + if (*ptyfd < 0) + { + error("Could not open /dev/ptc: %.100s", strerror(errno)); + return 0; + } + name = ttyname(*ptyfd); + if (!name) + fatal("Open of /dev/ptc returns device for which ttyname fails."); + strcpy(namebuf, name); + *ttyfd = open(name, O_RDWR|O_NOCTTY); + if (*ttyfd < 0) + { + error("Could not open pty slave side %.100s: %.100s", + name, strerror(errno)); + close(*ptyfd); + return 0; + } + return 1; + +#else /* HAVE_DEV_PTS_AND_PTC */ + /* BSD-style pty code. */ + + char buf[64]; + int i; + const char *ptymajors = + "pqrstuvwxyzabcdefghijklmnoABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const char *ptyminors = "0123456789abcdef"; + int num_minors = strlen(ptyminors); + int num_ptys = strlen(ptymajors) * num_minors; + + for (i = 0; i < num_ptys; i++) + { + snprintf(buf, sizeof buf, "/dev/pty%c%c", ptymajors[i / num_minors], + ptyminors[i % num_minors]); + *ptyfd = open(buf, O_RDWR|O_NOCTTY); + if (*ptyfd < 0) + continue; + snprintf(namebuf, sizeof buf, "/dev/tty%c%c", ptymajors[i / num_minors], + ptyminors[i % num_minors]); + + /* Open the slave side. */ + *ttyfd = open(namebuf, O_RDWR|O_NOCTTY); + if (*ttyfd < 0) + { + error("%.100s: %.100s", namebuf, strerror(errno)); + close(*ptyfd); + return 0; + } + return 1; + } + return 0; +#endif /* HAVE_DEV_PTS_AND_PTC */ +#endif /* HAVE_DEV_PTMX */ +#endif /* HAVE__GETPTY */ +#endif /* HAVE_OPENPTY */ +} + +/* Releases the tty. Its ownership is returned to root, and permissions to + 0666. */ + +void pty_release(const char *ttyname) +{ + if (chown(ttyname, (uid_t)0, (gid_t)0) < 0) + debug("chown %.100s 0 0 failed: %.100s", ttyname, strerror(errno)); + if (chmod(ttyname, (mode_t)0666) < 0) + debug("chmod %.100s 0666 failed: %.100s", ttyname, strerror(errno)); +} + +/* Makes the tty the processes controlling tty and sets it to sane modes. */ + +void pty_make_controlling_tty(int *ttyfd, const char *ttyname) +{ + int fd; + + /* First disconnect from the old controlling tty. */ +#ifdef TIOCNOTTY + fd = open("/dev/tty", O_RDWR|O_NOCTTY); + if (fd >= 0) + { + (void)ioctl(fd, TIOCNOTTY, NULL); + close(fd); + } +#endif /* TIOCNOTTY */ + if (setsid() < 0) + error("setsid: %.100s", strerror(errno)); + + /* Verify that we are successfully disconnected from the controlling tty. */ + fd = open("/dev/tty", O_RDWR|O_NOCTTY); + if (fd >= 0) + { + error("Failed to disconnect from controlling tty."); + close(fd); + } + + /* Make it our controlling tty. */ +#ifdef TIOCSCTTY + debug("Setting controlling tty using TIOCSCTTY."); + /* We ignore errors from this, because HPSUX defines TIOCSCTTY, but returns + EINVAL with these arguments, and there is absolutely no documentation. */ + ioctl(*ttyfd, TIOCSCTTY, NULL); +#endif /* TIOCSCTTY */ + fd = open(ttyname, O_RDWR); + if (fd < 0) + error("%.100s: %.100s", ttyname, strerror(errno)); + else + close(fd); + + /* Verify that we now have a controlling tty. */ + fd = open("/dev/tty", O_WRONLY); + if (fd < 0) + error("open /dev/tty failed - could not set controlling tty: %.100s", + strerror(errno)); + else + { + close(fd); + } +} + +/* Changes the window size associated with the pty. */ + +void pty_change_window_size(int ptyfd, int row, int col, + int xpixel, int ypixel) +{ + struct winsize w; + w.ws_row = row; + w.ws_col = col; + w.ws_xpixel = xpixel; + w.ws_ypixel = ypixel; + (void)ioctl(ptyfd, TIOCSWINSZ, &w); +} + @@ -0,0 +1,40 @@ +/* + +pty.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Fri Mar 17 05:03:28 1995 ylo + +Functions for allocating a pseudo-terminal and making it the controlling +tty. + +*/ + +/* RCSID("$Id: pty.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef PTY_H +#define PTY_H + +/* Allocates and opens a pty. Returns 0 if no pty could be allocated, + or nonzero if a pty was successfully allocated. On success, open file + descriptors for the pty and tty sides and the name of the tty side are + returned (the buffer must be able to hold at least 64 characters). */ +int pty_allocate(int *ptyfd, int *ttyfd, char *ttyname); + +/* Releases the tty. Its ownership is returned to root, and permissions to + 0666. */ +void pty_release(const char *ttyname); + +/* Makes the tty the processes controlling tty and sets it to sane modes. + This may need to reopen the tty to get rid of possible eavesdroppers. */ +void pty_make_controlling_tty(int *ttyfd, const char *ttyname); + +/* Changes the window size associated with the pty. */ +void pty_change_window_size(int ptyfd, int row, int col, + int xpixel, int ypixel); + +#endif /* PTY_H */ diff --git a/radix.c b/radix.c new file mode 100644 index 00000000..1c497945 --- /dev/null +++ b/radix.c @@ -0,0 +1,258 @@ +/* + radix.c + + base-64 encoding pinched from lynx2-7-2, who pinched it from rpem. + Originally written by Mark Riordan 12 August 1990 and 17 Feb 1991 + and placed in the public domain. + + Dug Song <dugsong@UMICH.EDU> +*/ + +#include "includes.h" + +#ifdef AFS +#include <krb.h> + +char six2pr[64] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z', + '0','1','2','3','4','5','6','7','8','9','+','/' +}; + +unsigned char pr2six[256]; + +int uuencode(unsigned char *bufin, unsigned int nbytes, char *bufcoded) +{ + /* ENC is the basic 1 character encoding function to make a char printing */ +#define ENC(c) six2pr[c] + + register char *outptr = bufcoded; + unsigned int i; + + for (i=0; i<nbytes; i += 3) { + *(outptr++) = ENC(*bufin >> 2); /* c1 */ + *(outptr++) = ENC(((*bufin << 4) & 060) | ((bufin[1] >> 4) & 017)); /*c2*/ + *(outptr++) = ENC(((bufin[1] << 2) & 074) | ((bufin[2] >> 6) & 03));/*c3*/ + *(outptr++) = ENC(bufin[2] & 077); /* c4 */ + bufin += 3; + } + if (i == nbytes+1) { + outptr[-1] = '='; + } else if (i == nbytes+2) { + outptr[-1] = '='; + outptr[-2] = '='; + } + *outptr = '\0'; + return(outptr - bufcoded); +} + +int uudecode(const char *bufcoded, unsigned char *bufplain, int outbufsize) +{ + /* single character decode */ +#define DEC(c) pr2six[(unsigned char)c] +#define MAXVAL 63 + + static int first = 1; + int nbytesdecoded, j; + const char *bufin = bufcoded; + register unsigned char *bufout = bufplain; + register int nprbytes; + + /* If this is the first call, initialize the mapping table. */ + if (first) { + first = 0; + for(j=0; j<256; j++) pr2six[j] = MAXVAL+1; + for(j=0; j<64; j++) pr2six[(unsigned char)six2pr[j]] = (unsigned char)j; + } + + /* Strip leading whitespace. */ + while (*bufcoded==' ' || *bufcoded == '\t') bufcoded++; + + /* Figure out how many characters are in the input buffer. + If this would decode into more bytes than would fit into + the output buffer, adjust the number of input bytes downwards. */ + bufin = bufcoded; + while (DEC(*(bufin++)) <= MAXVAL); + nprbytes = bufin - bufcoded - 1; + nbytesdecoded = ((nprbytes+3)/4) * 3; + if (nbytesdecoded > outbufsize) + nprbytes = (outbufsize*4)/3; + + bufin = bufcoded; + + while (nprbytes > 0) { + *(bufout++) = (unsigned char) (DEC(*bufin) << 2 | DEC(bufin[1]) >> 4); + *(bufout++) = (unsigned char) (DEC(bufin[1]) << 4 | DEC(bufin[2]) >> 2); + *(bufout++) = (unsigned char) (DEC(bufin[2]) << 6 | DEC(bufin[3])); + bufin += 4; + nprbytes -= 4; + } + if (nprbytes & 03) { + if (DEC(bufin[-2]) > MAXVAL) + nbytesdecoded -= 2; + else + nbytesdecoded -= 1; + } + return(nbytesdecoded); +} + +typedef unsigned char my_u_char; +typedef unsigned int my_u_int32_t; +typedef unsigned short my_u_short; + +/* Nasty macros from BIND-4.9.2 */ + +#define GETSHORT(s, cp) { \ + register my_u_char *t_cp = (my_u_char*)(cp); \ + (s) = (((my_u_short)t_cp[0]) << 8) \ + | (((my_u_short)t_cp[1])) \ + ; \ + (cp) += 2; \ +} + +#define GETLONG(l, cp) { \ + register my_u_char *t_cp = (my_u_char*)(cp); \ + (l) = (((my_u_int32_t)t_cp[0]) << 24) \ + | (((my_u_int32_t)t_cp[1]) << 16) \ + | (((my_u_int32_t)t_cp[2]) << 8) \ + | (((my_u_int32_t)t_cp[3])) \ + ; \ + (cp) += 4; \ +} + +#define PUTSHORT(s, cp) { \ + register my_u_short t_s = (my_u_short)(s); \ + register my_u_char *t_cp = (my_u_char*)(cp); \ + *t_cp++ = t_s >> 8; \ + *t_cp = t_s; \ + (cp) += 2; \ +} + +#define PUTLONG(l, cp) { \ + register my_u_int32_t t_l = (my_u_int32_t)(l); \ + register my_u_char *t_cp = (my_u_char*)(cp); \ + *t_cp++ = t_l >> 24; \ + *t_cp++ = t_l >> 16; \ + *t_cp++ = t_l >> 8; \ + *t_cp = t_l; \ + (cp) += 4; \ +} + +#define GETSTRING(s, p, p_l) { \ + register char* p_targ = (p) + p_l; \ + register char* s_c = (s); \ + register char* p_c = (p); \ + while (*p_c && (p_c < p_targ)) { \ + *s_c++ = *p_c++; \ + } \ + if (p_c == p_targ) { \ + return 1; \ + } \ + *s_c = *p_c++; \ + (p_l) = (p_l) - (p_c - (p)); \ + (p) = p_c; \ +} + + +int creds_to_radix(CREDENTIALS *creds, unsigned char *buf) +{ + char *p, *s; + int len; + char temp[2048]; + + p = temp; + *p++ = 1; /* version */ + s = creds->service; while (*s) *p++ = *s++; *p++ = *s; + s = creds->instance; while (*s) *p++ = *s++; *p++ = *s; + s = creds->realm; while (*s) *p++ = *s++; *p++ = *s; + + s = creds->pname; while (*s) *p++ = *s++; *p++ = *s; + s = creds->pinst; while (*s) *p++ = *s++; *p++ = *s; + /* Null string to repeat the realm. */ + *p++ = '\0'; + + PUTLONG(creds->issue_date,p); + { + unsigned int endTime ; + endTime = (unsigned int)krb_life_to_time(creds->issue_date, + creds->lifetime); + PUTLONG(endTime,p); + } + + memcpy(p,&creds->session, sizeof(creds->session)); + p += sizeof(creds->session); + + PUTSHORT(creds->kvno,p); + PUTLONG(creds->ticket_st.length,p); + + memcpy(p,creds->ticket_st.dat, creds->ticket_st.length); + p += creds->ticket_st.length; + len = p - temp; + + return(uuencode(temp, len, buf)); +} + +int radix_to_creds(const char *buf, CREDENTIALS *creds) +{ + + char *p; + int len, tl; + char version; + char temp[2048]; + + if (!(len = uudecode(buf, temp, sizeof(temp)))) + return 0; + + p = temp; + + /* check version and length! */ + if (len < 1) return 0; + version = *p; p++; len--; + + GETSTRING(creds->service, p, len); + GETSTRING(creds->instance, p, len); + GETSTRING(creds->realm, p, len); + + GETSTRING(creds->pname, p, len); + GETSTRING(creds->pinst, p, len); + /* Ignore possibly different realm. */ + while (*p && len) p++, len--; + if (len == 0) return 0; + p++, len--; + + /* Enough space for remaining fixed-length parts? */ + if (len < (4 + 4 + sizeof(creds->session) + 2 + 4)) + return 0; + + GETLONG(creds->issue_date,p); + len -= 4; + { + unsigned int endTime; + GETLONG(endTime,p); + len -= 4; + creds->lifetime = krb_time_to_life(creds->issue_date, endTime); + } + + memcpy(&creds->session, p, sizeof(creds->session)); + p += sizeof(creds->session); + len -= sizeof(creds->session); + + GETSHORT(creds->kvno,p); + len -= 2; + GETLONG(creds->ticket_st.length,p); + len -= 4; + + tl = creds->ticket_st.length; + if (tl < 0 || tl > len || tl > sizeof(creds->ticket_st.dat)) + return 0; + + memcpy(creds->ticket_st.dat, p, tl); + p += tl; + len -= tl; + + return 1; +} + +#endif /* AFS */ @@ -0,0 +1,105 @@ +/*! \file rc4.c + \brief Source file for RC4 stream cipher routines + \author Damien Miller <djm@mindrot.org> + \version 0.0.0 + \date 1999 + + A simple implementation of the RC4 stream cipher, based on the + description given in _Bruce Schneier's_ "Applied Cryptography" + 2nd edition. + + Copyright 1999 Damien Miller + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + AND NONINFRINGEMENT. IN NO EVENT SHALL DAMIEN MILLER BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + \warning None of these functions clears its memory after use. It + \warning is the responsability of the calling routines to ensure + \warning that any sensitive data (keystream, key or plaintext) is + \warning properly erased after use. + + \warning The name "RC4" is trademarked in the United States, + \warning you may need to use "RC4 compatible" or "ARC4" + \warning (Alleged RC4). +*/ + +/* $Id: rc4.c,v 1.1.1.1 1999/10/26 05:48:13 damien Exp $ */ + +#include "rc4.h" + + +void rc4_key(rc4_t *r, unsigned char *key, int len) +{ + int t; + + for(r->i = 0; r->i < 256; r->i++) + r->s[r->i] = r->i; + + r->j = 0; + for(r->i = 0; r->i < 256; r->i++) + { + r->j = (r->j + r->s[r->i] + key[r->i % len]) % 256; + t = r->s[r->i]; + r->s[r->i] = r->s[r->j]; + r->s[r->j] = t; + } + r->i = r->j = 0; +} + +void rc4_crypt(rc4_t *r, unsigned char *plaintext, int len) +{ + int t; + int c; + + c = 0; + while(c < len) + { + r->i = (r->i + 1) % 256; + r->j = (r->j + r->s[r->i]) % 256; + t = r->s[r->i]; + r->s[r->i] = r->s[r->j]; + r->s[r->j] = t; + + t = (r->s[r->i] + r->s[r->j]) % 256; + + plaintext[c] ^= r->s[t]; + c++; + } +} + +void rc4_getbytes(rc4_t *r, unsigned char *buffer, int len) +{ + int t; + int c; + + c = 0; + while(c < len) + { + r->i = (r->i + 1) % 256; + r->j = (r->j + r->s[r->i]) % 256; + t = r->s[r->i]; + r->s[r->i] = r->s[r->j]; + r->s[r->j] = t; + + t = (r->s[r->i] + r->s[r->j]) % 256; + + buffer[c] = r->s[t]; + c++; + } +} @@ -0,0 +1,110 @@ +/*! \file rc4.h + \brief Header file for RC4 stream cipher routines + \author Damien Miller <djm@mindrot.org> + \version 0.0.0 + \date 1999 + + A simple implementation of the RC4 stream cipher, based on the + description given in _Bruce Schneier's_ "Applied Cryptography" + 2nd edition. + + Copyright 1999 Damien Miller + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + AND NONINFRINGEMENT. IN NO EVENT SHALL DAMIEN MILLER BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + \warning None of these functions clears its memory after use. It + \warning is the responsability of the calling routines to ensure + \warning that any sensitive data (keystream, key or plaintext) is + \warning properly erased after use. + + \warning The name "RC4" is trademarked in the United States, + \warning you may need to use "RC4 compatible" or "ARC4" + \warning (Alleged RC4). +*/ + +/* $Id: rc4.h,v 1.1.1.1 1999/10/26 05:48:13 damien Exp $ */ + +#ifndef _RC4_H +#define _RC4_H + +/*! \struct rc4_t + \brief RC4 stream cipher state object + \var s State array + \var i Monotonic index + \var j Randomised index + + \warning This structure should not be accessed directly. To + \warning initialise a rc4_t object, you should use the rc4_key() + \warning function + + This structure holds the current state of the RC4 algorithm. +*/ +typedef struct +{ + unsigned int s[256]; + int i; + int j; +} rc4_t; + +/*! \fn void rc4_key(rc4_t *r, unsigned char *key, int len); + \brief Set up key structure of RC4 stream cipher + \param r pointer to RC4 structure to be seeded + \param key pointer to buffer containing raw key + \param len length of key + + This function set the internal state of the RC4 data structure + pointed to by \a r using the specified \a key of length \a len. + + This function can use up to 256 bytes of key, any more are ignored. + + \warning Stream ciphers (such as RC4) can be insecure if the same + \warning key is used repeatedly. Ensure that any key specified has + \warning an reasonably sized Initialisation Vector component. +*/ +void rc4_key(rc4_t *r, unsigned char *key, int len); + +/*! \fn rc4_crypt(rc4_t *r, unsigned char *plaintext, int len); + \brief Crypt bytes using RC4 algorithm + \param r pointer to RC4 structure to be used + \param plaintext Pointer to bytes to encrypt + \param len number of bytes to crypt + + This function encrypts one or more bytes (pointed to by \a plaintext) + using the RC4 algorithm. \a r is a state structure that must be + initialiased using the rc4_key() function prior to use. + + Since RC4 XORs each byte of plaintext with a byte of keystream, + this function can be used for both encryption and decryption. +*/ +void rc4_crypt(rc4_t *r, unsigned char *plaintext, int len); + +/*! \fn rc4_getbytes(rc4_t *r, unsigned char *buffer, int len); + \brief Generate key stream using the RC4 stream cipher + \param r pointer to RC4 structure to be used + \param buffer pointer to buffer in which to deposit keystream + \param len number of bytes to deposit + + This function gives access to the raw RC4 key stream. In this + consiguration RC4 can be used as a fast, strong pseudo-random + number generator with a very long period. +*/ +void rc4_getbytes(rc4_t *r, unsigned char *buffer, int len); + +#endif /* _RC4_H */ diff --git a/readconf.c b/readconf.c new file mode 100644 index 00000000..281548d2 --- /dev/null +++ b/readconf.c @@ -0,0 +1,684 @@ +/* + +readconf.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sat Apr 22 00:03:10 1995 ylo + +Functions for reading the configuration files. + +*/ + +#include "includes.h" +RCSID("$Id: readconf.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "ssh.h" +#include "cipher.h" +#include "readconf.h" +#include "xmalloc.h" + +/* Format of the configuration file: + + # Configuration data is parsed as follows: + # 1. command line options + # 2. user-specific file + # 3. system-wide file + # Any configuration value is only changed the first time it is set. + # Thus, host-specific definitions should be at the beginning of the + # configuration file, and defaults at the end. + + # Host-specific declarations. These may override anything above. A single + # host may match multiple declarations; these are processed in the order + # that they are given in. + + Host *.ngs.fi ngs.fi + FallBackToRsh no + + Host fake.com + HostName another.host.name.real.org + User blaah + Port 34289 + ForwardX11 no + ForwardAgent no + + Host books.com + RemoteForward 9999 shadows.cs.hut.fi:9999 + Cipher 3des + + Host fascist.blob.com + Port 23123 + User tylonen + RhostsAuthentication no + PasswordAuthentication no + + Host puukko.hut.fi + User t35124p + ProxyCommand ssh-proxy %h %p + + Host *.fr + UseRsh yes + + Host *.su + Cipher none + PasswordAuthentication no + + # Defaults for various options + Host * + ForwardAgent no + ForwardX11 yes + RhostsAuthentication yes + PasswordAuthentication yes + RSAAuthentication yes + RhostsRSAAuthentication yes + FallBackToRsh no + UseRsh no + StrictHostKeyChecking yes + KeepAlives no + IdentityFile ~/.ssh/identity + Port 22 + EscapeChar ~ + +*/ + +/* Keyword tokens. */ + +typedef enum +{ + oForwardAgent, oForwardX11, oGatewayPorts, oRhostsAuthentication, + oPasswordAuthentication, oRSAAuthentication, oFallBackToRsh, oUseRsh, +#ifdef KRB4 + oKerberosAuthentication, +#endif /* KRB4 */ +#ifdef AFS + oKerberosTgtPassing, oAFSTokenPassing, +#endif + oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward, + oUser, oHost, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, + oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, + oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, + oCompressionLevel, oKeepAlives, oNumberOfPasswordPrompts, oTISAuthentication, + oUsePrivilegedPort +} OpCodes; + +/* Textual representations of the tokens. */ + +static struct +{ + const char *name; + OpCodes opcode; +} keywords[] = +{ + { "forwardagent", oForwardAgent }, + { "forwardx11", oForwardX11 }, + { "gatewayports", oGatewayPorts }, + { "useprivilegedport", oUsePrivilegedPort }, + { "rhostsauthentication", oRhostsAuthentication }, + { "passwordauthentication", oPasswordAuthentication }, + { "rsaauthentication", oRSAAuthentication }, +#ifdef KRB4 + { "kerberosauthentication", oKerberosAuthentication }, +#endif /* KRB4 */ +#ifdef AFS + { "kerberostgtpassing", oKerberosTgtPassing }, + { "afstokenpassing", oAFSTokenPassing }, +#endif + { "fallbacktorsh", oFallBackToRsh }, + { "usersh", oUseRsh }, + { "identityfile", oIdentityFile }, + { "hostname", oHostName }, + { "proxycommand", oProxyCommand }, + { "port", oPort }, + { "cipher", oCipher }, + { "remoteforward", oRemoteForward }, + { "localforward", oLocalForward }, + { "user", oUser }, + { "host", oHost }, + { "escapechar", oEscapeChar }, + { "rhostsrsaauthentication", oRhostsRSAAuthentication }, + { "globalknownhostsfile", oGlobalKnownHostsFile }, + { "userknownhostsfile", oUserKnownHostsFile }, + { "connectionattempts", oConnectionAttempts }, + { "batchmode", oBatchMode }, + { "checkhostip", oCheckHostIP }, + { "stricthostkeychecking", oStrictHostKeyChecking }, + { "compression", oCompression }, + { "compressionlevel", oCompressionLevel }, + { "keepalive", oKeepAlives }, + { "numberofpasswordprompts", oNumberOfPasswordPrompts }, + { "tisauthentication", oTISAuthentication }, + { NULL, 0 } +}; + +/* Characters considered whitespace in strtok calls. */ +#define WHITESPACE " \t\r\n" + + +/* Adds a local TCP/IP port forward to options. Never returns if there + is an error. */ + +void add_local_forward(Options *options, int port, const char *host, + int host_port) +{ + Forward *fwd; + extern uid_t original_real_uid; + if ((port & 0xffff) != port) + fatal("Requested forwarding of nonexistent port %d.", port); + if (port < IPPORT_RESERVED && original_real_uid != 0) + fatal("Privileged ports can only be forwarded by root.\n"); + if (options->num_local_forwards >= SSH_MAX_FORWARDS_PER_DIRECTION) + fatal("Too many local forwards (max %d).", SSH_MAX_FORWARDS_PER_DIRECTION); + fwd = &options->local_forwards[options->num_local_forwards++]; + fwd->port = port; + fwd->host = xstrdup(host); + fwd->host_port = host_port; +} + +/* Adds a remote TCP/IP port forward to options. Never returns if there + is an error. */ + +void add_remote_forward(Options *options, int port, const char *host, + int host_port) +{ + Forward *fwd; + if (options->num_remote_forwards >= SSH_MAX_FORWARDS_PER_DIRECTION) + fatal("Too many remote forwards (max %d).", + SSH_MAX_FORWARDS_PER_DIRECTION); + fwd = &options->remote_forwards[options->num_remote_forwards++]; + fwd->port = port; + fwd->host = xstrdup(host); + fwd->host_port = host_port; +} + +/* Returns the number of the token pointed to by cp of length len. + Never returns if the token is not known. */ + +static OpCodes parse_token(const char *cp, const char *filename, int linenum) +{ + unsigned int i; + + for (i = 0; keywords[i].name; i++) + if (strcmp(cp, keywords[i].name) == 0) + return keywords[i].opcode; + + fatal("%.200s line %d: Bad configuration option.", + filename, linenum); + /*NOTREACHED*/ + return 0; +} + +/* Processes a single option line as used in the configuration files. + This only sets those values that have not already been set. */ + +void process_config_line(Options *options, const char *host, + char *line, const char *filename, int linenum, + int *activep) +{ + char buf[256], *cp, *string, **charptr; + int opcode, *intptr, value, fwd_port, fwd_host_port; + + /* Skip leading whitespace. */ + cp = line + strspn(line, WHITESPACE); + if (!*cp || *cp == '\n' || *cp == '#') + return; + + /* Get the keyword. (Each line is supposed to begin with a keyword). */ + cp = strtok(cp, WHITESPACE); + { + char *t = cp; + for (; *t != 0; t++) + if ('A' <= *t && *t <= 'Z') + *t = *t - 'A' + 'a'; /* tolower */ + + } + opcode = parse_token(cp, filename, linenum); + + switch (opcode) + { + + case oForwardAgent: + intptr = &options->forward_agent; + parse_flag: + cp = strtok(NULL, WHITESPACE); + if (!cp) + fatal("%.200s line %d: Missing yes/no argument.", filename, linenum); + value = 0; /* To avoid compiler warning... */ + if (strcmp(cp, "yes") == 0 || strcmp(cp, "true") == 0) + value = 1; + else if (strcmp(cp, "no") == 0 || strcmp(cp, "false") == 0) + value = 0; + else + fatal("%.200s line %d: Bad yes/no argument.", filename, linenum); + if (*activep && *intptr == -1) + *intptr = value; + break; + + case oForwardX11: + intptr = &options->forward_x11; + goto parse_flag; + + case oGatewayPorts: + intptr = &options->gateway_ports; + goto parse_flag; + + case oUsePrivilegedPort: + intptr = &options->use_privileged_port; + goto parse_flag; + + case oRhostsAuthentication: + intptr = &options->rhosts_authentication; + goto parse_flag; + + case oPasswordAuthentication: + intptr = &options->password_authentication; + goto parse_flag; + + case oRSAAuthentication: + intptr = &options->rsa_authentication; + goto parse_flag; + + case oRhostsRSAAuthentication: + intptr = &options->rhosts_rsa_authentication; + goto parse_flag; + +#ifdef KRB4 + case oKerberosAuthentication: + intptr = &options->kerberos_authentication; + goto parse_flag; +#endif /* KRB4 */ + +#ifdef AFS + case oKerberosTgtPassing: + intptr = &options->kerberos_tgt_passing; + goto parse_flag; + + case oAFSTokenPassing: + intptr = &options->afs_token_passing; + goto parse_flag; +#endif + + case oFallBackToRsh: + intptr = &options->fallback_to_rsh; + goto parse_flag; + + case oUseRsh: + intptr = &options->use_rsh; + goto parse_flag; + + case oBatchMode: + intptr = &options->batch_mode; + goto parse_flag; + + case oCheckHostIP: + intptr = &options->check_host_ip; + goto parse_flag; + + case oStrictHostKeyChecking: + intptr = &options->strict_host_key_checking; + cp = strtok(NULL, WHITESPACE); + if (!cp) + fatal("%.200s line %d: Missing yes/no argument.", + filename, linenum); + value = 0; /* To avoid compiler warning... */ + if (strcmp(cp, "yes") == 0 || strcmp(cp, "true") == 0) + value = 1; + else if (strcmp(cp, "no") == 0 || strcmp(cp, "false") == 0) + value = 0; + else if (strcmp(cp, "ask") == 0) + value = 2; + else + fatal("%.200s line %d: Bad yes/no/ask argument.", filename, linenum); + if (*activep && *intptr == -1) + *intptr = value; + break; + + case oCompression: + intptr = &options->compression; + goto parse_flag; + + case oKeepAlives: + intptr = &options->keepalives; + goto parse_flag; + + case oNumberOfPasswordPrompts: + intptr = &options->number_of_password_prompts; + goto parse_int; + + case oTISAuthentication: + cp = strtok(NULL, WHITESPACE); + if (cp != 0 && (strcmp(cp, "yes") == 0 || strcmp(cp, "true") == 0)) + fprintf(stderr, + "%.99s line %d: Warning, TIS is not supported.\n", + filename, + linenum); + break; + + case oCompressionLevel: + intptr = &options->compression_level; + goto parse_int; + + case oIdentityFile: + cp = strtok(NULL, WHITESPACE); + if (!cp) + fatal("%.200s line %d: Missing argument.", filename, linenum); + if (*activep) + { + if (options->num_identity_files >= SSH_MAX_IDENTITY_FILES) + fatal("%.200s line %d: Too many identity files specified (max %d).", + filename, linenum, SSH_MAX_IDENTITY_FILES); + options->identity_files[options->num_identity_files++] = xstrdup(cp); + } + break; + + case oUser: + charptr = &options->user; + parse_string: + cp = strtok(NULL, WHITESPACE); + if (!cp) + fatal("%.200s line %d: Missing argument.", filename, linenum); + if (*activep && *charptr == NULL) + *charptr = xstrdup(cp); + break; + + case oGlobalKnownHostsFile: + charptr = &options->system_hostfile; + goto parse_string; + + case oUserKnownHostsFile: + charptr = &options->user_hostfile; + goto parse_string; + + case oHostName: + charptr = &options->hostname; + goto parse_string; + + case oProxyCommand: + charptr = &options->proxy_command; + string = xstrdup(""); + while ((cp = strtok(NULL, WHITESPACE)) != NULL) + { + string = xrealloc(string, strlen(string) + strlen(cp) + 2); + strcat(string, " "); + strcat(string, cp); + } + if (*activep && *charptr == NULL) + *charptr = string; + else + xfree(string); + return; + + case oPort: + intptr = &options->port; + parse_int: + cp = strtok(NULL, WHITESPACE); + if (!cp) + fatal("%.200s line %d: Missing argument.", filename, linenum); + if (cp[0] < '0' || cp[0] > '9') + fatal("%.200s line %d: Bad number.", filename, linenum); +#if 0 + value = atoi(cp); +#else + { + char *ptr; + value = strtol(cp, &ptr, 0); /* Octal, decimal, or hex format? */ + if (cp == ptr) + fatal("%.200s line %d: Bad number.", filename, linenum); + } +#endif + if (*activep && *intptr == -1) + *intptr = value; + break; + + case oConnectionAttempts: + intptr = &options->connection_attempts; + goto parse_int; + + case oCipher: + intptr = &options->cipher; + cp = strtok(NULL, WHITESPACE); + value = cipher_number(cp); + if (value == -1) + fatal("%.200s line %d: Bad cipher.", filename, linenum); + if (*activep && *intptr == -1) + *intptr = value; + break; + + case oRemoteForward: + cp = strtok(NULL, WHITESPACE); + if (!cp) + fatal("%.200s line %d: Missing argument.", filename, linenum); + if (cp[0] < '0' || cp[0] > '9') + fatal("%.200s line %d: Badly formatted port number.", + filename, linenum); + fwd_port = atoi(cp); + cp = strtok(NULL, WHITESPACE); + if (!cp) + fatal("%.200s line %d: Missing second argument.", + filename, linenum); + if (sscanf(cp, "%255[^:]:%d", buf, &fwd_host_port) != 2) + fatal("%.200s line %d: Badly formatted host:port.", + filename, linenum); + if (*activep) + add_remote_forward(options, fwd_port, buf, fwd_host_port); + break; + + case oLocalForward: + cp = strtok(NULL, WHITESPACE); + if (!cp) + fatal("%.200s line %d: Missing argument.", filename, linenum); + if (cp[0] < '0' || cp[0] > '9') + fatal("%.200s line %d: Badly formatted port number.", + filename, linenum); + fwd_port = atoi(cp); + cp = strtok(NULL, WHITESPACE); + if (!cp) + fatal("%.200s line %d: Missing second argument.", + filename, linenum); + if (sscanf(cp, "%255[^:]:%d", buf, &fwd_host_port) != 2) + fatal("%.200s line %d: Badly formatted host:port.", + filename, linenum); + if (*activep) + add_local_forward(options, fwd_port, buf, fwd_host_port); + break; + + case oHost: + *activep = 0; + while ((cp = strtok(NULL, WHITESPACE)) != NULL) + if (match_pattern(host, cp)) + { + debug("Applying options for %.100s", cp); + *activep = 1; + break; + } + /* Avoid garbage check below, as strtok already returned NULL. */ + return; + + case oEscapeChar: + intptr = &options->escape_char; + cp = strtok(NULL, WHITESPACE); + if (!cp) + fatal("%.200s line %d: Missing argument.", filename, linenum); + if (cp[0] == '^' && cp[2] == 0 && + (unsigned char)cp[1] >= 64 && (unsigned char)cp[1] < 128) + value = (unsigned char)cp[1] & 31; + else + if (strlen(cp) == 1) + value = (unsigned char)cp[0]; + else + if (strcmp(cp, "none") == 0) + value = -2; + else + { + fatal("%.200s line %d: Bad escape character.", + filename, linenum); + /*NOTREACHED*/ + value = 0; /* Avoid compiler warning. */ + } + if (*activep && *intptr == -1) + *intptr = value; + break; + + default: + fatal("parse_config_file: Unimplemented opcode %d", opcode); + } + + /* Check that there is no garbage at end of line. */ + if (strtok(NULL, WHITESPACE) != NULL) + fatal("%.200s line %d: garbage at end of line.", + filename, linenum); +} + + +/* Reads the config file and modifies the options accordingly. Options should + already be initialized before this call. This never returns if there + is an error. If the file does not exist, this returns immediately. */ + +void read_config_file(const char *filename, const char *host, Options *options) +{ + FILE *f; + char line[1024]; + int active, linenum; + + /* Open the file. */ + f = fopen(filename, "r"); + if (!f) + return; + + debug("Reading configuration data %.200s", filename); + + /* Mark that we are now processing the options. This flag is turned on/off + by Host specifications. */ + active = 1; + linenum = 0; + while (fgets(line, sizeof(line), f)) + { + /* Update line number counter. */ + linenum++; + + process_config_line(options, host, line, filename, linenum, &active); + } + fclose(f); +} + +/* Initializes options to special values that indicate that they have not + yet been set. Read_config_file will only set options with this value. + Options are processed in the following order: command line, user config + file, system config file. Last, fill_default_options is called. */ + +void initialize_options(Options *options) +{ + memset(options, 'X', sizeof(*options)); + options->forward_agent = -1; + options->forward_x11 = -1; + options->gateway_ports = -1; + options->use_privileged_port = -1; + options->rhosts_authentication = -1; + options->rsa_authentication = -1; +#ifdef KRB4 + options->kerberos_authentication = -1; +#endif +#ifdef AFS + options->kerberos_tgt_passing = -1; + options->afs_token_passing = -1; +#endif + options->password_authentication = -1; + options->rhosts_rsa_authentication = -1; + options->fallback_to_rsh = -1; + options->use_rsh = -1; + options->batch_mode = -1; + options->check_host_ip = -1; + options->strict_host_key_checking = -1; + options->compression = -1; + options->keepalives = -1; + options->compression_level = -1; + options->port = -1; + options->connection_attempts = -1; + options->number_of_password_prompts = -1; + options->cipher = -1; + options->num_identity_files = 0; + options->hostname = NULL; + options->proxy_command = NULL; + options->user = NULL; + options->escape_char = -1; + options->system_hostfile = NULL; + options->user_hostfile = NULL; + options->num_local_forwards = 0; + options->num_remote_forwards = 0; +} + +/* Called after processing other sources of option data, this fills those + options for which no value has been specified with their default values. */ + +void fill_default_options(Options *options) +{ + if (options->forward_agent == -1) + options->forward_agent = 1; + if (options->forward_x11 == -1) + options->forward_x11 = 1; + if (options->gateway_ports == -1) + options->gateway_ports = 0; + if (options->use_privileged_port == -1) + options->use_privileged_port = 1; + if (options->rhosts_authentication == -1) + options->rhosts_authentication = 1; + if (options->rsa_authentication == -1) + options->rsa_authentication = 1; +#ifdef KRB4 + if (options->kerberos_authentication == -1) + options->kerberos_authentication = 1; +#endif /* KRB4 */ +#ifdef AFS + if (options->kerberos_tgt_passing == -1) + options->kerberos_tgt_passing = 1; + if (options->afs_token_passing == -1) + options->afs_token_passing = 1; +#endif /* AFS */ + if (options->password_authentication == -1) + options->password_authentication = 1; + if (options->rhosts_rsa_authentication == -1) + options->rhosts_rsa_authentication = 1; + if (options->fallback_to_rsh == -1) + options->fallback_to_rsh = 1; + if (options->use_rsh == -1) + options->use_rsh = 0; + if (options->batch_mode == -1) + options->batch_mode = 0; + if (options->check_host_ip == -1) + options->check_host_ip = 1; + if (options->strict_host_key_checking == -1) + options->strict_host_key_checking = 2; /* 2 is default */ + if (options->compression == -1) + options->compression = 0; + if (options->keepalives == -1) + options->keepalives = 1; + if (options->compression_level == -1) + options->compression_level = 6; + if (options->port == -1) + options->port = 0; /* Filled in ssh_connect. */ + if (options->connection_attempts == -1) + options->connection_attempts = 4; + if (options->number_of_password_prompts == -1) + options->number_of_password_prompts = 3; + if (options->cipher == -1) + options->cipher = SSH_CIPHER_NOT_SET; /* Selected in ssh_login(). */ + if (options->num_identity_files == 0) + { + options->identity_files[0] = + xmalloc(2 + strlen(SSH_CLIENT_IDENTITY) + 1); + sprintf(options->identity_files[0], "~/%.100s", SSH_CLIENT_IDENTITY); + options->num_identity_files = 1; + } + if (options->escape_char == -1) + options->escape_char = '~'; + if (options->system_hostfile == NULL) + options->system_hostfile = SSH_SYSTEM_HOSTFILE; + if (options->user_hostfile == NULL) + options->user_hostfile = SSH_USER_HOSTFILE; + /* options->proxy_command should not be set by default */ + /* options->user will be set in the main program if appropriate */ + /* options->hostname will be set in the main program if appropriate */ +} + diff --git a/readconf.h b/readconf.h new file mode 100644 index 00000000..71655bd2 --- /dev/null +++ b/readconf.h @@ -0,0 +1,116 @@ +/* + +readconf.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sat Apr 22 00:25:29 1995 ylo + +Functions for reading the configuration file. + +*/ + +/* RCSID("$Id: readconf.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef READCONF_H +#define READCONF_H + +/* Data structure for representing a forwarding request. */ + +typedef struct +{ + int port; /* Port to forward. */ + char *host; /* Host to connect. */ + int host_port; /* Port to connect on host. */ +} Forward; + +/* Data structure for representing option data. */ + +typedef struct +{ + int forward_agent; /* Forward authentication agent. */ + int forward_x11; /* Forward X11 display. */ + int gateway_ports; /* Allow remote connects to forwarded ports. */ + int use_privileged_port; /* Don't use privileged port if false. */ + int rhosts_authentication; /* Try rhosts authentication. */ + int rhosts_rsa_authentication;/* Try rhosts with RSA authentication. */ + int rsa_authentication; /* Try RSA authentication. */ +#ifdef KRB4 + int kerberos_authentication; /* Try Kerberos authentication. */ +#endif +#ifdef AFS + int kerberos_tgt_passing; /* Try Kerberos tgt passing. */ + int afs_token_passing; /* Try AFS token passing. */ +#endif + int password_authentication; /* Try password authentication. */ + int fallback_to_rsh; /* Use rsh if cannot connect with ssh. */ + int use_rsh; /* Always use rsh (don\'t try ssh). */ + int batch_mode; /* Batch mode: do not ask for passwords. */ + int check_host_ip; /* Also keep track of keys for IP address */ + int strict_host_key_checking; /* Strict host key checking. */ + int compression; /* Compress packets in both directions. */ + int compression_level; /* Compression level 1 (fast) to 9 (best). */ + int keepalives; /* Set SO_KEEPALIVE. */ + + int port; /* Port to connect. */ + int connection_attempts; /* Max attempts (seconds) before giving up */ + int number_of_password_prompts; /* Max number of password prompts. */ + int cipher; /* Cipher to use. */ + char *hostname; /* Real host to connect. */ + char *proxy_command; /* Proxy command for connecting the host. */ + char *user; /* User to log in as. */ + int escape_char; /* Escape character; -2 = none */ + + char *system_hostfile; /* Path for /etc/ssh_known_hosts. */ + char *user_hostfile; /* Path for $HOME/.ssh/known_hosts. */ + + int num_identity_files; /* Number of files for RSA identities. */ + char *identity_files[SSH_MAX_IDENTITY_FILES]; + + /* Local TCP/IP forward requests. */ + int num_local_forwards; + Forward local_forwards[SSH_MAX_FORWARDS_PER_DIRECTION]; + + /* Remote TCP/IP forward requests. */ + int num_remote_forwards; + Forward remote_forwards[SSH_MAX_FORWARDS_PER_DIRECTION]; +} Options; + + +/* Initializes options to special values that indicate that they have not + yet been set. Read_config_file will only set options with this value. + Options are processed in the following order: command line, user config + file, system config file. Last, fill_default_options is called. */ +void initialize_options(Options *options); + +/* Called after processing other sources of option data, this fills those + options for which no value has been specified with their default values. */ +void fill_default_options(Options *options); + +/* Processes a single option line as used in the configuration files. + This only sets those values that have not already been set. */ +void process_config_line(Options *options, const char *host, + char *line, const char *filename, int linenum, + int *activep); + +/* Reads the config file and modifies the options accordingly. Options should + already be initialized before this call. This never returns if there + is an error. If the file does not exist, this returns immediately. */ +void read_config_file(const char *filename, const char *host, + Options *options); + +/* Adds a local TCP/IP port forward to options. Never returns if there + is an error. */ +void add_local_forward(Options *options, int port, const char *host, + int host_port); + +/* Adds a remote TCP/IP port forward to options. Never returns if there + is an error. */ +void add_remote_forward(Options *options, int port, const char *host, + int host_port); + + +#endif /* READCONF_H */ diff --git a/readpass.c b/readpass.c new file mode 100644 index 00000000..3031825e --- /dev/null +++ b/readpass.c @@ -0,0 +1,114 @@ +/* + +readpass.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Mon Jul 10 22:08:59 1995 ylo + +Functions for reading passphrases and passwords. + +*/ + +#include "includes.h" +RCSID("$Id: readpass.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "xmalloc.h" +#include "ssh.h" + +/* Saved old terminal mode for read_passphrase. */ +static struct termios saved_tio; + +/* Old interrupt signal handler for read_passphrase. */ +static void (*old_handler)(int sig) = NULL; + +/* Interrupt signal handler for read_passphrase. */ + +void intr_handler(int sig) +{ + /* Restore terminal modes. */ + tcsetattr(fileno(stdin), TCSANOW, &saved_tio); + /* Restore the old signal handler. */ + signal(sig, old_handler); + /* Resend the signal, with the old handler. */ + kill(getpid(), sig); +} + +/* Reads a passphrase from /dev/tty with echo turned off. Returns the + passphrase (allocated with xmalloc). Exits if EOF is encountered. + The passphrase if read from stdin if from_stdin is true (as is the + case with ssh-keygen). */ + +char *read_passphrase(const char *prompt, int from_stdin) +{ + char buf[1024], *cp; + struct termios tio; + FILE *f; + + if (from_stdin) + f = stdin; + else + { + /* Read the passphrase from /dev/tty to make it possible to ask it even + when stdin has been redirected. */ + f = fopen("/dev/tty", "r"); + if (!f) + { + /* No controlling terminal and no DISPLAY. Nowhere to read. */ + fprintf(stderr, "You have no controlling tty and no DISPLAY. Cannot read passphrase.\n"); + exit(1); + } + } + + /* Display the prompt (on stderr because stdout might be redirected). */ + fflush(stdout); + fprintf(stderr, "%s", prompt); + fflush(stderr); + + /* Get terminal modes. */ + tcgetattr(fileno(f), &tio); + saved_tio = tio; + /* Save signal handler and set the new handler. */ + old_handler = signal(SIGINT, intr_handler); + + /* Set new terminal modes disabling all echo. */ + tio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + tcsetattr(fileno(f), TCSANOW, &tio); + + /* Read the passphrase from the terminal. */ + if (fgets(buf, sizeof(buf), f) == NULL) + { + /* Got EOF. Just exit. */ + /* Restore terminal modes. */ + tcsetattr(fileno(f), TCSANOW, &saved_tio); + /* Restore the signal handler. */ + signal(SIGINT, old_handler); + /* Print a newline (the prompt probably didn\'t have one). */ + fprintf(stderr, "\n"); + /* Close the file. */ + if (f != stdin) + fclose(f); + exit(1); + } + /* Restore terminal modes. */ + tcsetattr(fileno(f), TCSANOW, &saved_tio); + /* Restore the signal handler. */ + (void)signal(SIGINT, old_handler); + /* Remove newline from the passphrase. */ + if (strchr(buf, '\n')) + *strchr(buf, '\n') = 0; + /* Allocate a copy of the passphrase. */ + cp = xstrdup(buf); + /* Clear the buffer so we don\'t leave copies of the passphrase laying + around. */ + memset(buf, 0, sizeof(buf)); + /* Print a newline since the prompt probably didn\'t have one. */ + fprintf(stderr, "\n"); + /* Close the file. */ + if (f != stdin) + fclose(f); + return cp; +} @@ -0,0 +1,164 @@ +/* + +rsa.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Fri Mar 3 22:07:06 1995 ylo + +Description of the RSA algorithm can be found e.g. from the following sources: + + Bruce Schneier: Applied Cryptography. John Wiley & Sons, 1994. + + Jennifer Seberry and Josed Pieprzyk: Cryptography: An Introduction to + Computer Security. Prentice-Hall, 1989. + + Man Young Rhee: Cryptography and Secure Data Communications. McGraw-Hill, + 1994. + + R. Rivest, A. Shamir, and L. M. Adleman: Cryptographic Communications + System and Method. US Patent 4,405,829, 1983. + + Hans Riesel: Prime Numbers and Computer Methods for Factorization. + Birkhauser, 1994. + + The RSA Frequently Asked Questions document by RSA Data Security, Inc., 1995. + + RSA in 3 lines of perl by Adam Back <aba@atlax.ex.ac.uk>, 1995, as included + below: + + gone - had to be deleted - what a pity + +*/ + +#include "includes.h" +RCSID("$Id: rsa.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "rsa.h" +#include "ssh.h" +#include "xmalloc.h" + +int rsa_verbose = 1; + +int +rsa_alive() +{ + RSA *key; + + key = RSA_generate_key(32, 3, NULL, NULL); + if (key == NULL) + return (0); + RSA_free(key); + return (1); +} + +/* Generates RSA public and private keys. This initializes the data + structures; they should be freed with rsa_clear_private_key and + rsa_clear_public_key. */ + +void +rsa_generate_key(RSA *prv, RSA *pub, unsigned int bits) +{ + RSA *key; + + if (rsa_verbose) { + printf("Generating RSA keys: "); + fflush(stdout); + } + + key = RSA_generate_key(bits, 35, NULL, NULL); + + assert(key != NULL); + + /* Copy public key parameters */ + pub->n = BN_new(); + BN_copy(pub->n, key->n); + pub->e = BN_new(); + BN_copy(pub->e, key->e); + + /* Copy private key parameters */ + prv->n = BN_new(); + BN_copy(prv->n, key->n); + prv->e = BN_new(); + BN_copy(prv->e, key->e); + prv->d = BN_new(); + BN_copy(prv->d, key->d); + prv->p = BN_new(); + BN_copy(prv->p, key->p); + prv->q = BN_new(); + BN_copy(prv->q, key->q); + + prv->dmp1 = BN_new(); + BN_copy(prv->dmp1, key->dmp1); + + prv->dmq1 = BN_new(); + BN_copy(prv->dmq1, key->dmq1); + + prv->iqmp = BN_new(); + BN_copy(prv->iqmp, key->iqmp); + + RSA_free(key); + + if (rsa_verbose) + printf("Key generation complete.\n"); +} + +void +rsa_public_encrypt(BIGNUM *out, BIGNUM *in, RSA* key) +{ + char *inbuf, *outbuf; + int len; + + if (BN_num_bits(key->e) < 2 || !BN_is_odd(key->e)) + fatal("rsa_public_encrypt() exponent too small or not odd"); + + len = BN_num_bytes(key->n); + outbuf = xmalloc(len); + + len = BN_num_bytes(in); + inbuf = xmalloc(len); + BN_bn2bin(in, inbuf); + + if ((len = RSA_public_encrypt(len, inbuf, outbuf, key, + RSA_PKCS1_PADDING)) <= 0) + fatal("rsa_public_encrypt() failed"); + + BN_bin2bn(outbuf, len, out); + + xfree(outbuf); + xfree(inbuf); +} + +void +rsa_private_decrypt(BIGNUM *out, BIGNUM *in, RSA *key) +{ + char *inbuf, *outbuf; + int len; + + len = BN_num_bytes(key->n); + outbuf = xmalloc(len); + + len = BN_num_bytes(in); + inbuf = xmalloc(len); + BN_bn2bin(in, inbuf); + + if ((len = RSA_private_decrypt(len, inbuf, outbuf, key, + RSA_SSLV23_PADDING)) <= 0) + fatal("rsa_private_decrypt() failed"); + + BN_bin2bn(outbuf, len, out); + + xfree(outbuf); + xfree(inbuf); +} + +/* Set whether to output verbose messages during key generation. */ + +void +rsa_set_verbose(int verbose) +{ + rsa_verbose = verbose; +} @@ -0,0 +1,36 @@ +/* + +rsa.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Fri Mar 3 22:01:06 1995 ylo + +RSA key generation, encryption and decryption. + +*/ + +/* RCSID("$Id: rsa.h,v 1.1 1999/10/27 03:42:44 damien Exp $"); */ + +#ifndef RSA_H +#define RSA_H + +#include <openssl/bn.h> +#include <openssl/rsa.h> + +/* Calls SSL RSA_generate_key, only copies to prv and pub */ +void rsa_generate_key(RSA *prv, RSA *pub, unsigned int bits); + +/* Indicates whether the rsa module is permitted to show messages on + the terminal. */ +void rsa_set_verbose __P((int verbose)); + +int rsa_alive __P((void)); + +void rsa_public_encrypt __P((BIGNUM *out, BIGNUM *in, RSA *prv)); +void rsa_private_decrypt __P((BIGNUM *out, BIGNUM *in, RSA *prv)); + +#endif /* RSA_H */ @@ -0,0 +1,110 @@ +.\" -*- nroff -*- +.\" +.\" scp.1 +.\" +.\" Author: Tatu Ylonen <ylo@cs.hut.fi> +.\" +.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland +.\" All rights reserved +.\" +.\" Created: Sun May 7 00:14:37 1995 ylo +.\" +.\" $Id: scp.1,v 1.1 1999/10/27 03:42:44 damien Exp $ +.\" +.Dd September 25, 1999 +.Dt SCP 1 +.Os +.Sh NAME +.Nm scp +.Nd secure copy (remote file copy program) +.Sh SYNOPSIS +.Nm scp +.Op Fl pqrvC +.Op Fl P Ar port +.Op Fl c Ar cipher +.Op Fl i Ar identity_file +.Sm off +.Oo +.Op Ar user@ +.Ar host1 No : +.Oc Ns Ar file1 +.Sm on +.Op Ar ... +.Sm off +.Oo +.Op Ar user@ +.Ar host2 No : +.Oc Ar file2 +.Sm on +.Sh DESCRIPTION +.Nm +copies files between hosts on a network. It uses +.Xr ssh 1 +for data transfer, and uses the same authentication and provides the +same security as +.Xr ssh 1 . +Unlike +.Xr rcp 1 , +.Nm +will ask for passwords or passphrases if they are needed for +authentication. +.Pp +Any file name may contain a host and user specification to indicate +that the file is to be copied to/from that host. Copies between two +remote hosts are permitted. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl c Ar cipher +Selects the cipher to use for encrypting the data transfer. This +option is directly passed to +.Xr ssh 1 . +.It Fl i Ar identity_file +Selects the file from which the identity (private key) for RSA +authentication is read. This option is directly passed to +.Xr ssh 1 . +.It Fl p +Preserves modification times, access times, and modes from the +original file. +.It Fl r +Recursively copy entire directories. +.It Fl v +Verbose mode. Causes +.Nm +and +.Xr ssh 1 +to print debugging messages about their progress. This is helpful in +debugging connection, authentication, and configuration problems. +.It Fl B +Selects batch mode (prevents asking for passwords or passphrases). +.It Fl q +Disables the progress meter. +.It Fl C +Compression enable. Passes the +.Fl C +flag to +.Xr ssh 1 +to enable compression. +.It Fl P Ar port +Specifies the port to connect to on the remote host. Note that this +option is written with a capital +.Sq P , +because +.Fl p +is already reserved for preserving the times and modes of the file in +.Xr rcp 1 . +.Sh AUTHORS +Timo Rinne <tri@iki.fi> and Tatu Ylonen <ylo@cs.hut.fi> +.Sh HISTORY +.Nm +is based on the +.Xr rcp 1 +program in BSD source code from the Regents of the University of +California. +.Sh SEE ALSO +.Xr rcp 1 , +.Xr ssh 1 , +.Xr ssh-add 1 , +.Xr ssh-agent 1 , +.Xr ssh-keygen 1 , +.Xr sshd 8 @@ -0,0 +1,1220 @@ +/* + +scp - secure remote copy. This is basically patched BSD rcp which uses ssh +to do the data transfer (instead of using rcmd). + +NOTE: This version should NOT be suid root. (This uses ssh to do the transfer +and ssh has the necessary privileges.) + +1995 Timo Rinne <tri@iki.fi>, Tatu Ylonen <ylo@cs.hut.fi> + +*/ + +/* + * Copyright (c) 1983, 1990, 1992, 1993, 1995 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * $Id: scp.c,v 1.1 1999/10/27 03:42:45 damien Exp $ + */ + +#include "includes.h" +RCSID("$Id: scp.c,v 1.1 1999/10/27 03:42:45 damien Exp $"); + +#include "ssh.h" +#include "xmalloc.h" +#include <utime.h> + +#define _PATH_CP "cp" + +/* For progressmeter() -- number of seconds before xfer considered "stalled" */ +#define STALLTIME 5 + +/* Visual statistics about files as they are transferred. */ +void progressmeter(int); + +/* Returns width of the terminal (for progress meter calculations). */ +int getttywidth(void); + +/* Time a transfer started. */ +static struct timeval start; + +/* Number of bytes of current file transferred so far. */ +volatile unsigned long statbytes; + +/* Total size of current file. */ +unsigned long totalbytes = 0; + +/* Name of current file being transferred. */ +char *curfile; + +/* This is set to non-zero to enable verbose mode. */ +int verbose = 0; + +/* This is set to non-zero if compression is desired. */ +int compress = 0; + +/* This is set to zero if the progressmeter is not desired. */ +int showprogress = 1; + +/* This is set to non-zero if running in batch mode (that is, password + and passphrase queries are not allowed). */ +int batchmode = 0; + +/* This is set to the cipher type string if given on the command line. */ +char *cipher = NULL; + +/* This is set to the RSA authentication identity file name if given on + the command line. */ +char *identity = NULL; + +/* This is the port to use in contacting the remote site (is non-NULL). */ +char *port = NULL; + +/* This function executes the given command as the specified user on the given + host. This returns < 0 if execution fails, and >= 0 otherwise. + This assigns the input and output file descriptors on success. */ + +int do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) +{ + int pin[2], pout[2], reserved[2]; + + if (verbose) + fprintf(stderr, "Executing: host %s, user %s, command %s\n", + host, remuser ? remuser : "(unspecified)", cmd); + + /* Reserve two descriptors so that the real pipes won't get descriptors + 0 and 1 because that will screw up dup2 below. */ + pipe(reserved); + + /* Create a socket pair for communicating with ssh. */ + if (pipe(pin) < 0) + fatal("pipe: %s", strerror(errno)); + if (pipe(pout) < 0) + fatal("pipe: %s", strerror(errno)); + + /* Free the reserved descriptors. */ + close(reserved[0]); + close(reserved[1]); + + /* For a child to execute the command on the remote host using ssh. */ + if (fork() == 0) + { + char *args[100]; + unsigned int i; + + /* Child. */ + close(pin[1]); + close(pout[0]); + dup2(pin[0], 0); + dup2(pout[1], 1); + close(pin[0]); + close(pout[1]); + + i = 0; + args[i++] = SSH_PROGRAM; + args[i++] = "-x"; + args[i++] = "-oFallBackToRsh no"; + if (verbose) + args[i++] = "-v"; + if (compress) + args[i++] = "-C"; + if (batchmode) + args[i++] = "-oBatchMode yes"; + if (cipher != NULL) + { + args[i++] = "-c"; + args[i++] = cipher; + } + if (identity != NULL) + { + args[i++] = "-i"; + args[i++] = identity; + } + if (port != NULL) + { + args[i++] = "-p"; + args[i++] = port; + } + if (remuser != NULL) + { + args[i++] = "-l"; + args[i++] = remuser; + } + args[i++] = host; + args[i++] = cmd; + args[i++] = NULL; + + execvp(SSH_PROGRAM, args); + perror(SSH_PROGRAM); + exit(1); + } + /* Parent. Close the other side, and return the local side. */ + close(pin[0]); + *fdout = pin[1]; + close(pout[1]); + *fdin = pout[0]; + return 0; +} + +void fatal(const char *fmt, ...) +{ + va_list ap; + char buf[1024]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + fprintf(stderr, "%s\n", buf); + exit(255); +} + +/* This stuff used to be in BSD rcp extern.h. */ + +typedef struct { + int cnt; + char *buf; +} BUF; + +extern int iamremote; + +BUF *allocbuf(BUF *, int, int); +char *colon(char *); +void lostconn(int); +void nospace(void); +int okname(char *); +void run_err(const char *, ...); +void verifydir(char *); + +/* Stuff from BSD rcp.c continues. */ + +struct passwd *pwd; +uid_t userid; +int errs, remin, remout; +int pflag, iamremote, iamrecursive, targetshouldbedirectory; + +#define CMDNEEDS 64 +char cmd[CMDNEEDS]; /* must hold "rcp -r -p -d\0" */ + +int response(void); +void rsource(char *, struct stat *); +void sink(int, char *[]); +void source(int, char *[]); +void tolocal(int, char *[]); +void toremote(char *, int, char *[]); +void usage(void); + +int +main(argc, argv) + int argc; + char *argv[]; +{ + int ch, fflag, tflag; + char *targ; + extern char *optarg; + extern int optind; + + fflag = tflag = 0; + while ((ch = getopt(argc, argv, "dfprtvBCc:i:P:q")) != EOF) + switch(ch) { /* User-visible flags. */ + case 'p': + pflag = 1; + break; + case 'P': + port = optarg; + break; + case 'r': + iamrecursive = 1; + break; + /* Server options. */ + case 'd': + targetshouldbedirectory = 1; + break; + case 'f': /* "from" */ + iamremote = 1; + fflag = 1; + break; + case 't': /* "to" */ + iamremote = 1; + tflag = 1; + break; + case 'c': + cipher = optarg; + break; + case 'i': + identity = optarg; + break; + case 'v': + verbose = 1; + break; + case 'B': + batchmode = 1; + break; + case 'C': + compress = 1; + break; + case 'q': + showprogress = 0; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if ((pwd = getpwuid(userid = getuid())) == NULL) + fatal("unknown user %d", (int)userid); + + if (! isatty(STDERR_FILENO)) + showprogress = 0; + + remin = STDIN_FILENO; + remout = STDOUT_FILENO; + + if (fflag) { /* Follow "protocol", send data. */ + (void)response(); + source(argc, argv); + exit(errs != 0); + } + + if (tflag) { /* Receive data. */ + sink(argc, argv); + exit(errs != 0); + } + + if (argc < 2) + usage(); + if (argc > 2) + targetshouldbedirectory = 1; + + remin = remout = -1; + /* Command to be executed on remote system using "ssh". */ + (void)sprintf(cmd, "scp%s%s%s%s", verbose ? " -v" : "", + iamrecursive ? " -r" : "", pflag ? " -p" : "", + targetshouldbedirectory ? " -d" : ""); + + (void)signal(SIGPIPE, lostconn); + + if ((targ = colon(argv[argc - 1]))) /* Dest is remote host. */ + toremote(targ, argc, argv); + else { + tolocal(argc, argv); /* Dest is local host. */ + if (targetshouldbedirectory) + verifydir(argv[argc - 1]); + } + exit(errs != 0); +} + +void +toremote(targ, argc, argv) + char *targ, *argv[]; + int argc; +{ + int i, len; + char *bp, *host, *src, *suser, *thost, *tuser; + + *targ++ = 0; + if (*targ == 0) + targ = "."; + + if ((thost = strchr(argv[argc - 1], '@'))) { + /* user@host */ + *thost++ = 0; + tuser = argv[argc - 1]; + if (*tuser == '\0') + tuser = NULL; + else if (!okname(tuser)) + exit(1); + } else { + thost = argv[argc - 1]; + tuser = NULL; + } + + for (i = 0; i < argc - 1; i++) { + src = colon(argv[i]); + if (src) { /* remote to remote */ + *src++ = 0; + if (*src == 0) + src = "."; + host = strchr(argv[i], '@'); + len = strlen(SSH_PROGRAM) + strlen(argv[i]) + + strlen(src) + (tuser ? strlen(tuser) : 0) + + strlen(thost) + strlen(targ) + CMDNEEDS + 32; + bp = xmalloc(len); + if (host) { + *host++ = 0; + suser = argv[i]; + if (*suser == '\0') + suser = pwd->pw_name; + else if (!okname(suser)) + continue; + (void)sprintf(bp, + "%s%s -x -o'FallBackToRsh no' -n -l %s %s %s %s '%s%s%s:%s'", + SSH_PROGRAM, verbose ? " -v" : "", + suser, host, cmd, src, + tuser ? tuser : "", tuser ? "@" : "", + thost, targ); + } else + (void)sprintf(bp, + "exec %s%s -x -o'FallBackToRsh no' -n %s %s %s '%s%s%s:%s'", + SSH_PROGRAM, verbose ? " -v" : "", + argv[i], cmd, src, + tuser ? tuser : "", tuser ? "@" : "", + thost, targ); + if (verbose) + fprintf(stderr, "Executing: %s\n", bp); + (void)system(bp); + (void)xfree(bp); + } else { /* local to remote */ + if (remin == -1) { + len = strlen(targ) + CMDNEEDS + 20; + bp = xmalloc(len); + (void)sprintf(bp, "%s -t %s", cmd, targ); + host = thost; + if (do_cmd(host, tuser, + bp, &remin, &remout) < 0) + exit(1); + if (response() < 0) + exit(1); + (void)xfree(bp); + } + source(1, argv+i); + } + } +} + +void +tolocal(argc, argv) + int argc; + char *argv[]; +{ + int i, len; + char *bp, *host, *src, *suser; + + for (i = 0; i < argc - 1; i++) { + if (!(src = colon(argv[i]))) { /* Local to local. */ + len = strlen(_PATH_CP) + strlen(argv[i]) + + strlen(argv[argc - 1]) + 20; + bp = xmalloc(len); + (void)sprintf(bp, "exec %s%s%s %s %s", _PATH_CP, + iamrecursive ? " -r" : "", pflag ? " -p" : "", + argv[i], argv[argc - 1]); + if (verbose) + fprintf(stderr, "Executing: %s\n", bp); + if (system(bp)) + ++errs; + (void)xfree(bp); + continue; + } + *src++ = 0; + if (*src == 0) + src = "."; + if ((host = strchr(argv[i], '@')) == NULL) { + host = argv[i]; + suser = NULL; + } else { + *host++ = 0; + suser = argv[i]; + if (*suser == '\0') + suser = pwd->pw_name; + else if (!okname(suser)) + continue; + } + len = strlen(src) + CMDNEEDS + 20; + bp = xmalloc(len); + (void)sprintf(bp, "%s -f %s", cmd, src); + if (do_cmd(host, suser, bp, &remin, &remout) < 0) { + (void)xfree(bp); + ++errs; + continue; + } + xfree(bp); + sink(1, argv + argc - 1); + (void)close(remin); + remin = remout = -1; + } +} + +void +source(argc, argv) + int argc; + char *argv[]; +{ + struct stat stb; + static BUF buffer; + BUF *bp; + off_t i; + int amt, fd, haderr, indx, result; + char *last, *name, buf[2048]; + + for (indx = 0; indx < argc; ++indx) { + name = argv[indx]; + statbytes = 0; + if ((fd = open(name, O_RDONLY, 0)) < 0) + goto syserr; + if (fstat(fd, &stb) < 0) { +syserr: run_err("%s: %s", name, strerror(errno)); + goto next; + } + switch (stb.st_mode & S_IFMT) { + case S_IFREG: + break; + case S_IFDIR: + if (iamrecursive) { + rsource(name, &stb); + goto next; + } + /* FALLTHROUGH */ + default: + run_err("%s: not a regular file", name); + goto next; + } + if ((last = strrchr(name, '/')) == NULL) + last = name; + else + ++last; + curfile = last; + if (pflag) { + /* + * Make it compatible with possible future + * versions expecting microseconds. + */ + (void)sprintf(buf, "T%lu 0 %lu 0\n", + (unsigned long)stb.st_mtime, + (unsigned long)stb.st_atime); + (void)write(remout, buf, strlen(buf)); + if (response() < 0) + goto next; + } +#define FILEMODEMASK (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO) + (void)sprintf(buf, "C%04o %lu %s\n", + (unsigned int)(stb.st_mode & FILEMODEMASK), + (unsigned long)stb.st_size, + last); + if (verbose) + { + fprintf(stderr, "Sending file modes: %s", buf); + fflush(stderr); + } + (void)write(remout, buf, strlen(buf)); + if (response() < 0) + goto next; + if ((bp = allocbuf(&buffer, fd, 2048)) == NULL) { +next: (void)close(fd); + continue; + } + + if (showprogress) { + totalbytes = stb.st_size; + progressmeter(-1); + } + + /* Keep writing after an error so that we stay sync'd up. */ + for (haderr = i = 0; i < stb.st_size; i += bp->cnt) { + amt = bp->cnt; + if (i + amt > stb.st_size) + amt = stb.st_size - i; + if (!haderr) { + result = read(fd, bp->buf, amt); + if (result != amt) + haderr = result >= 0 ? EIO : errno; + } + if (haderr) + (void)write(remout, bp->buf, amt); + else { + result = write(remout, bp->buf, amt); + if (result != amt) + haderr = result >= 0 ? EIO : errno; + statbytes += result; + } + } + if(showprogress) + progressmeter(1); + + if (close(fd) < 0 && !haderr) + haderr = errno; + if (!haderr) + (void)write(remout, "", 1); + else + run_err("%s: %s", name, strerror(haderr)); + (void)response(); + } +} + +void +rsource(name, statp) + char *name; + struct stat *statp; +{ + DIR *dirp; + struct dirent *dp; + char *last, *vect[1], path[1100]; + + if (!(dirp = opendir(name))) { + run_err("%s: %s", name, strerror(errno)); + return; + } + last = strrchr(name, '/'); + if (last == 0) + last = name; + else + last++; + if (pflag) { + (void)sprintf(path, "T%lu 0 %lu 0\n", + (unsigned long)statp->st_mtime, + (unsigned long)statp->st_atime); + (void)write(remout, path, strlen(path)); + if (response() < 0) { + closedir(dirp); + return; + } + } + (void)sprintf(path, + "D%04o %d %.1024s\n", (unsigned int)(statp->st_mode & FILEMODEMASK), + 0, last); + if (verbose) + fprintf(stderr, "Entering directory: %s", path); + (void)write(remout, path, strlen(path)); + if (response() < 0) { + closedir(dirp); + return; + } + while ((dp = readdir(dirp))) { + if (dp->d_ino == 0) + continue; + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + if (strlen(name) + 1 + strlen(dp->d_name) >= sizeof(path) - 1) { + run_err("%s/%s: name too long", name, dp->d_name); + continue; + } + (void)sprintf(path, "%s/%s", name, dp->d_name); + vect[0] = path; + source(1, vect); + } + (void)closedir(dirp); + (void)write(remout, "E\n", 2); + (void)response(); +} + +void +sink(argc, argv) + int argc; + char *argv[]; +{ + static BUF buffer; + struct stat stb; + enum { YES, NO, DISPLAYED } wrerr; + BUF *bp; + off_t i, j; + int amt, count, exists, first, mask, mode, ofd, omode; + int setimes, size, targisdir, wrerrno = 0; + char ch, *cp, *np, *targ, *why, *vect[1], buf[2048]; + struct utimbuf ut; + int dummy_usec; + +#define SCREWUP(str) { why = str; goto screwup; } + + setimes = targisdir = 0; + mask = umask(0); + if (!pflag) + (void)umask(mask); + if (argc != 1) { + run_err("ambiguous target"); + exit(1); + } + targ = *argv; + if (targetshouldbedirectory) + verifydir(targ); + + (void)write(remout, "", 1); + if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode)) + targisdir = 1; + for (first = 1;; first = 0) { + cp = buf; + if (read(remin, cp, 1) <= 0) + return; + if (*cp++ == '\n') + SCREWUP("unexpected <newline>"); + do { + if (read(remin, &ch, sizeof(ch)) != sizeof(ch)) + SCREWUP("lost connection"); + *cp++ = ch; + } while (cp < &buf[sizeof(buf) - 1] && ch != '\n'); + *cp = 0; + + if (buf[0] == '\01' || buf[0] == '\02') { + if (iamremote == 0) + (void)write(STDERR_FILENO, + buf + 1, strlen(buf + 1)); + if (buf[0] == '\02') + exit(1); + ++errs; + continue; + } + if (buf[0] == 'E') { + (void)write(remout, "", 1); + return; + } + + if (ch == '\n') + *--cp = 0; + +#define getnum(t) (t) = 0; \ + while (*cp >= '0' && *cp <= '9') (t) = (t) * 10 + (*cp++ - '0'); + cp = buf; + if (*cp == 'T') { + setimes++; + cp++; + getnum(ut.modtime); + if (*cp++ != ' ') + SCREWUP("mtime.sec not delimited"); + getnum(dummy_usec); + if (*cp++ != ' ') + SCREWUP("mtime.usec not delimited"); + getnum(ut.actime); + if (*cp++ != ' ') + SCREWUP("atime.sec not delimited"); + getnum(dummy_usec); + if (*cp++ != '\0') + SCREWUP("atime.usec not delimited"); + (void)write(remout, "", 1); + continue; + } + if (*cp != 'C' && *cp != 'D') { + /* + * Check for the case "rcp remote:foo\* local:bar". + * In this case, the line "No match." can be returned + * by the shell before the rcp command on the remote is + * executed so the ^Aerror_message convention isn't + * followed. + */ + if (first) { + run_err("%s", cp); + exit(1); + } + SCREWUP("expected control record"); + } + mode = 0; + for (++cp; cp < buf + 5; cp++) { + if (*cp < '0' || *cp > '7') + SCREWUP("bad mode"); + mode = (mode << 3) | (*cp - '0'); + } + if (*cp++ != ' ') + SCREWUP("mode not delimited"); + + for (size = 0; *cp >= '0' && *cp <= '9';) + size = size * 10 + (*cp++ - '0'); + if (*cp++ != ' ') + SCREWUP("size not delimited"); + if (targisdir) { + static char *namebuf; + static int cursize; + size_t need; + + need = strlen(targ) + strlen(cp) + 250; + if (need > cursize) + namebuf = xmalloc(need); + (void)sprintf(namebuf, "%s%s%s", targ, + *targ ? "/" : "", cp); + np = namebuf; + } else + np = targ; + curfile = cp; + exists = stat(np, &stb) == 0; + if (buf[0] == 'D') { + int mod_flag = pflag; + if (exists) { + if (!S_ISDIR(stb.st_mode)) { + errno = ENOTDIR; + goto bad; + } + if (pflag) + (void)chmod(np, mode); + } else { + /* Handle copying from a read-only directory */ + mod_flag = 1; + if (mkdir(np, mode | S_IRWXU) < 0) + goto bad; + } + vect[0] = np; + sink(1, vect); + if (setimes) { + setimes = 0; + if (utime(np, &ut) < 0) + run_err("%s: set times: %s", + np, strerror(errno)); + } + if (mod_flag) + (void)chmod(np, mode); + continue; + } + omode = mode; + mode |= S_IWRITE; + if ((ofd = open(np, O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0) { +bad: run_err("%s: %s", np, strerror(errno)); + continue; + } + (void)write(remout, "", 1); + if ((bp = allocbuf(&buffer, ofd, 4096)) == NULL) { + (void)close(ofd); + continue; + } + cp = bp->buf; + wrerr = NO; + + if (showprogress) { + totalbytes = size; + progressmeter(-1); + } + statbytes = 0; + for (count = i = 0; i < size; i += 4096) { + amt = 4096; + if (i + amt > size) + amt = size - i; + count += amt; + do { + j = read(remin, cp, amt); + if (j <= 0) { + run_err("%s", j ? strerror(errno) : + "dropped connection"); + exit(1); + } + amt -= j; + cp += j; + statbytes += j; + } while (amt > 0); + if (count == bp->cnt) { + /* Keep reading so we stay sync'd up. */ + if (wrerr == NO) { + j = write(ofd, bp->buf, count); + if (j != count) { + wrerr = YES; + wrerrno = j >= 0 ? EIO : errno; + } + } + count = 0; + cp = bp->buf; + } + } + if (showprogress) + progressmeter(1); + if (count != 0 && wrerr == NO && + (j = write(ofd, bp->buf, count)) != count) { + wrerr = YES; + wrerrno = j >= 0 ? EIO : errno; + } +#if 0 + if (ftruncate(ofd, size)) { + run_err("%s: truncate: %s", np, strerror(errno)); + wrerr = DISPLAYED; + } +#endif + if (pflag) { + if (exists || omode != mode) + if (fchmod(ofd, omode)) + run_err("%s: set mode: %s", + np, strerror(errno)); + } else { + if (!exists && omode != mode) + if (fchmod(ofd, omode & ~mask)) + run_err("%s: set mode: %s", + np, strerror(errno)); + } + (void)close(ofd); + (void)response(); + if (setimes && wrerr == NO) { + setimes = 0; + if (utime(np, &ut) < 0) { + run_err("%s: set times: %s", + np, strerror(errno)); + wrerr = DISPLAYED; + } + } + switch(wrerr) { + case YES: + run_err("%s: %s", np, strerror(wrerrno)); + break; + case NO: + (void)write(remout, "", 1); + break; + case DISPLAYED: + break; + } + } +screwup: + run_err("protocol error: %s", why); + exit(1); +} + +int +response() +{ + char ch, *cp, resp, rbuf[2048]; + + if (read(remin, &resp, sizeof(resp)) != sizeof(resp)) + lostconn(0); + + cp = rbuf; + switch(resp) { + case 0: /* ok */ + return (0); + default: + *cp++ = resp; + /* FALLTHROUGH */ + case 1: /* error, followed by error msg */ + case 2: /* fatal error, "" */ + do { + if (read(remin, &ch, sizeof(ch)) != sizeof(ch)) + lostconn(0); + *cp++ = ch; + } while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n'); + + if (!iamremote) + (void)write(STDERR_FILENO, rbuf, cp - rbuf); + ++errs; + if (resp == 1) + return (-1); + exit(1); + } + /* NOTREACHED */ +} + +void +usage() +{ + (void)fprintf(stderr, + "usage: scp [-pqrvC] [-P port] [-c cipher] [-i identity] f1 f2; or:\n scp [options] f1 ... fn directory\n"); + exit(1); +} + +void +run_err(const char *fmt, ...) +{ + static FILE *fp; + va_list ap; + va_start(ap, fmt); + + ++errs; + if (fp == NULL && !(fp = fdopen(remout, "w"))) + return; + (void)fprintf(fp, "%c", 0x01); + (void)fprintf(fp, "scp: "); + (void)vfprintf(fp, fmt, ap); + (void)fprintf(fp, "\n"); + (void)fflush(fp); + + if (!iamremote) + { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } + + va_end(ap); +} + +/* Stuff below is from BSD rcp util.c. */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * $Id: scp.c,v 1.1 1999/10/27 03:42:45 damien Exp $ + */ + +char * +colon(cp) + char *cp; +{ + if (*cp == ':') /* Leading colon is part of file name. */ + return (0); + + for (; *cp; ++cp) { + if (*cp == ':') + return (cp); + if (*cp == '/') + return (0); + } + return (0); +} + +void +verifydir(cp) + char *cp; +{ + struct stat stb; + + if (!stat(cp, &stb)) { + if (S_ISDIR(stb.st_mode)) + return; + errno = ENOTDIR; + } + run_err("%s: %s", cp, strerror(errno)); + exit(1); +} + +int +okname(cp0) + char *cp0; +{ + int c; + char *cp; + + cp = cp0; + do { + c = *cp; + if (c & 0200) + goto bad; + if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-') + goto bad; + } while (*++cp); + return (1); + +bad: fprintf(stderr, "%s: invalid user name", cp0); + return (0); +} + +BUF * +allocbuf(bp, fd, blksize) + BUF *bp; + int fd, blksize; +{ + size_t size; + struct stat stb; + + if (fstat(fd, &stb) < 0) { + run_err("fstat: %s", strerror(errno)); + return (0); + } + if (stb.st_blksize == 0) + size = blksize; + else + size = blksize + (stb.st_blksize - blksize % stb.st_blksize) % + stb.st_blksize; + if (bp->cnt >= size) + return (bp); + if (bp->buf == NULL) + bp->buf = xmalloc(size); + else + bp->buf = xrealloc(bp->buf, size); + bp->cnt = size; + return (bp); +} + +void +lostconn(signo) + int signo; +{ + if (!iamremote) + fprintf(stderr, "lost connection\n"); + exit(1); +} + +/* + * ensure all of data on socket comes through. f==read || f==write + */ +int +atomicio(f, fd, s, n) +int (*f)(); +char *s; +{ + int res, pos = 0; + + while (n>pos) { + res = (f)(fd, s+pos, n-pos); + switch (res) { + case -1: + if (errno==EINTR || errno==EAGAIN) + continue; + case 0: + return (res); + default: + pos += res; + } + } + return (pos); +} + +void +alarmtimer(int wait) +{ + struct itimerval itv; + + itv.it_value.tv_sec = wait; + itv.it_value.tv_usec = 0; + itv.it_interval = itv.it_value; + setitimer(ITIMER_REAL, &itv, NULL); +} + +void +updateprogressmeter(void) +{ + int save_errno = errno; + + progressmeter(0); + errno = save_errno; +} + +void +progressmeter(int flag) +{ + static const char prefixes[] = " KMGTP"; + static struct timeval lastupdate; + static off_t lastsize; + struct timeval now, td, wait; + off_t cursize, abbrevsize; + double elapsed; + int ratio, barlength, i, remaining; + char buf[256]; + + if (flag == -1) { + (void)gettimeofday(&start, (struct timezone *)0); + lastupdate = start; + lastsize = 0; + } + (void)gettimeofday(&now, (struct timezone *)0); + cursize = statbytes; + if (totalbytes != 0) { + ratio = cursize * 100 / totalbytes; + ratio = MAX(ratio, 0); + ratio = MIN(ratio, 100); + } + else + ratio = 100; + + snprintf(buf, sizeof(buf), "\r%-20.20s %3d%% ", curfile, ratio); + + barlength = getttywidth() - 51; + if (barlength > 0) { + i = barlength * ratio / 100; + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "|%.*s%*s|", i, +"*****************************************************************************" +"*****************************************************************************", + barlength - i, ""); + } + + i = 0; + abbrevsize = cursize; + while (abbrevsize >= 100000 && i < sizeof(prefixes)) { + i++; + abbrevsize >>= 10; + } + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %5qd %c%c ", + (quad_t)abbrevsize, prefixes[i], prefixes[i] == ' ' ? ' ' : + 'B'); + + timersub(&now, &lastupdate, &wait); + if (cursize > lastsize) { + lastupdate = now; + lastsize = cursize; + if (wait.tv_sec >= STALLTIME) { + start.tv_sec += wait.tv_sec; + start.tv_usec += wait.tv_usec; + } + wait.tv_sec = 0; + } + + timersub(&now, &start, &td); + elapsed = td.tv_sec + (td.tv_usec / 1000000.0); + + if (statbytes <= 0 || elapsed <= 0.0 || cursize > totalbytes) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " --:-- ETA"); + } else if (wait.tv_sec >= STALLTIME) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " - stalled -"); + } else { + remaining = (int)(totalbytes / (statbytes / elapsed) - elapsed); + i = elapsed / 3600; + if (i) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%2d:", i); + else + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " "); + i = remaining % 3600; + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%02d:%02d ETA", i / 60, i % 60); + } + atomicio(write, fileno(stdout), buf, strlen(buf)); + + if (flag == -1) { + signal(SIGALRM, (void *)updateprogressmeter); + alarmtimer(1); + } else if (flag == 1) { + alarmtimer(0); + write(fileno(stdout), "\n", 1); + statbytes = 0; + } +} + +int +getttywidth(void) +{ + struct winsize winsize; + + if (ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1) + return(winsize.ws_col ? winsize.ws_col : 80); + else + return(80); +} + + diff --git a/servconf.c b/servconf.c new file mode 100644 index 00000000..5fa48790 --- /dev/null +++ b/servconf.c @@ -0,0 +1,567 @@ +/* + +servconf.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Mon Aug 21 15:48:58 1995 ylo + +*/ + +#include "includes.h" +RCSID("$Id: servconf.c,v 1.1 1999/10/27 03:42:45 damien Exp $"); + +#include "ssh.h" +#include "servconf.h" +#include "xmalloc.h" + +/* Initializes the server options to their default values. */ + +void initialize_server_options(ServerOptions *options) +{ + memset(options, 0, sizeof(*options)); + options->port = -1; + options->listen_addr.s_addr = htonl(INADDR_ANY); + options->host_key_file = NULL; + options->server_key_bits = -1; + options->login_grace_time = -1; + options->key_regeneration_time = -1; + options->permit_root_login = -1; + options->ignore_rhosts = -1; + options->quiet_mode = -1; + options->fascist_logging = -1; + options->print_motd = -1; + options->check_mail = -1; + options->x11_forwarding = -1; + options->x11_display_offset = -1; + options->strict_modes = -1; + options->keepalives = -1; + options->log_facility = (SyslogFacility)-1; + options->rhosts_authentication = -1; + options->rhosts_rsa_authentication = -1; + options->rsa_authentication = -1; +#ifdef KRB4 + options->kerberos_authentication = -1; + options->kerberos_or_local_passwd = -1; + options->kerberos_ticket_cleanup = -1; +#endif +#ifdef AFS + options->kerberos_tgt_passing = -1; + options->afs_token_passing = -1; +#endif + options->password_authentication = -1; +#ifdef SKEY + options->skey_authentication = -1; +#endif + options->permit_empty_passwd = -1; + options->use_login = -1; + options->num_allow_users = 0; + options->num_deny_users = 0; + options->num_allow_groups = 0; + options->num_deny_groups = 0; +} + +void fill_default_server_options(ServerOptions *options) +{ + if (options->port == -1) + { + struct servent *sp; + + sp = getservbyname(SSH_SERVICE_NAME, "tcp"); + if (sp) + options->port = ntohs(sp->s_port); + else + options->port = SSH_DEFAULT_PORT; + endservent(); + } + if (options->host_key_file == NULL) + options->host_key_file = HOST_KEY_FILE; + if (options->server_key_bits == -1) + options->server_key_bits = 768; + if (options->login_grace_time == -1) + options->login_grace_time = 600; + if (options->key_regeneration_time == -1) + options->key_regeneration_time = 3600; + if (options->permit_root_login == -1) + options->permit_root_login = 1; /* yes */ + if (options->ignore_rhosts == -1) + options->ignore_rhosts = 0; + if (options->quiet_mode == -1) + options->quiet_mode = 0; + if (options->check_mail == -1) + options->check_mail = 0; + if (options->fascist_logging == -1) + options->fascist_logging = 1; + if (options->print_motd == -1) + options->print_motd = 1; + if (options->x11_forwarding == -1) + options->x11_forwarding = 1; + if (options->x11_display_offset == -1) + options->x11_display_offset = 1; + if (options->strict_modes == -1) + options->strict_modes = 1; + if (options->keepalives == -1) + options->keepalives = 1; + if (options->log_facility == (SyslogFacility)(-1)) + options->log_facility = SYSLOG_FACILITY_AUTH; + if (options->rhosts_authentication == -1) + options->rhosts_authentication = 0; + if (options->rhosts_rsa_authentication == -1) + options->rhosts_rsa_authentication = 1; + if (options->rsa_authentication == -1) + options->rsa_authentication = 1; +#ifdef KRB4 + if (options->kerberos_authentication == -1) + options->kerberos_authentication = (access(KEYFILE, R_OK) == 0); + if (options->kerberos_or_local_passwd == -1) + options->kerberos_or_local_passwd = 1; + if (options->kerberos_ticket_cleanup == -1) + options->kerberos_ticket_cleanup = 1; +#endif /* KRB4 */ +#ifdef AFS + if (options->kerberos_tgt_passing == -1) + options->kerberos_tgt_passing = 0; + if (options->afs_token_passing == -1) + options->afs_token_passing = k_hasafs(); +#endif /* AFS */ + if (options->password_authentication == -1) + options->password_authentication = 1; +#ifdef SKEY + if (options->skey_authentication == -1) + options->skey_authentication = 1; +#endif + if (options->permit_empty_passwd == -1) + options->permit_empty_passwd = 1; + if (options->use_login == -1) + options->use_login = 0; +} + +#define WHITESPACE " \t\r\n" + +/* Keyword tokens. */ +typedef enum +{ + sPort, sHostKeyFile, sServerKeyBits, sLoginGraceTime, sKeyRegenerationTime, + sPermitRootLogin, sQuietMode, sFascistLogging, sLogFacility, + sRhostsAuthentication, sRhostsRSAAuthentication, sRSAAuthentication, +#ifdef KRB4 + sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, +#endif +#ifdef AFS + sKerberosTgtPassing, sAFSTokenPassing, +#endif +#ifdef SKEY + sSkeyAuthentication, +#endif + sPasswordAuthentication, sListenAddress, + sPrintMotd, sIgnoreRhosts, sX11Forwarding, sX11DisplayOffset, + sStrictModes, sEmptyPasswd, sRandomSeedFile, sKeepAlives, sCheckMail, + sUseLogin, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups + +} ServerOpCodes; + +/* Textual representation of the tokens. */ +static struct +{ + const char *name; + ServerOpCodes opcode; +} keywords[] = +{ + { "port", sPort }, + { "hostkey", sHostKeyFile }, + { "serverkeybits", sServerKeyBits }, + { "logingracetime", sLoginGraceTime }, + { "keyregenerationinterval", sKeyRegenerationTime }, + { "permitrootlogin", sPermitRootLogin }, + { "quietmode", sQuietMode }, + { "fascistlogging", sFascistLogging }, + { "syslogfacility", sLogFacility }, + { "rhostsauthentication", sRhostsAuthentication }, + { "rhostsrsaauthentication", sRhostsRSAAuthentication }, + { "rsaauthentication", sRSAAuthentication }, +#ifdef KRB4 + { "kerberosauthentication", sKerberosAuthentication }, + { "kerberosorlocalpasswd", sKerberosOrLocalPasswd }, + { "kerberosticketcleanup", sKerberosTicketCleanup }, +#endif +#ifdef AFS + { "kerberostgtpassing", sKerberosTgtPassing }, + { "afstokenpassing", sAFSTokenPassing }, +#endif + { "passwordauthentication", sPasswordAuthentication }, +#ifdef SKEY + { "skeyauthentication", sSkeyAuthentication }, +#endif + { "checkmail", sCheckMail }, + { "listenaddress", sListenAddress }, + { "printmotd", sPrintMotd }, + { "ignorerhosts", sIgnoreRhosts }, + { "x11forwarding", sX11Forwarding }, + { "x11displayoffset", sX11DisplayOffset }, + { "strictmodes", sStrictModes }, + { "permitemptypasswords", sEmptyPasswd }, + { "uselogin", sUseLogin }, + { "randomseed", sRandomSeedFile }, + { "keepalive", sKeepAlives }, + { "allowusers", sAllowUsers }, + { "denyusers", sDenyUsers }, + { "allowgroups", sAllowGroups }, + { "denygroups", sDenyGroups }, + { NULL, 0 } +}; + +static struct +{ + const char *name; + SyslogFacility facility; +} log_facilities[] = +{ + { "DAEMON", SYSLOG_FACILITY_DAEMON }, + { "USER", SYSLOG_FACILITY_USER }, + { "AUTH", SYSLOG_FACILITY_AUTH }, + { "LOCAL0", SYSLOG_FACILITY_LOCAL0 }, + { "LOCAL1", SYSLOG_FACILITY_LOCAL1 }, + { "LOCAL2", SYSLOG_FACILITY_LOCAL2 }, + { "LOCAL3", SYSLOG_FACILITY_LOCAL3 }, + { "LOCAL4", SYSLOG_FACILITY_LOCAL4 }, + { "LOCAL5", SYSLOG_FACILITY_LOCAL5 }, + { "LOCAL6", SYSLOG_FACILITY_LOCAL6 }, + { "LOCAL7", SYSLOG_FACILITY_LOCAL7 }, + { NULL, 0 } +}; + +/* Returns the number of the token pointed to by cp of length len. + Never returns if the token is not known. */ + +static ServerOpCodes parse_token(const char *cp, const char *filename, + int linenum) +{ + unsigned int i; + + for (i = 0; keywords[i].name; i++) + if (strcmp(cp, keywords[i].name) == 0) + return keywords[i].opcode; + + fprintf(stderr, "%s line %d: Bad configuration option: %s\n", + filename, linenum, cp); + exit(1); +} + +/* Reads the server configuration file. */ + +void read_server_config(ServerOptions *options, const char *filename) +{ + FILE *f; + char line[1024]; + char *cp, **charptr; + int linenum, *intptr, i, value; + ServerOpCodes opcode; + + f = fopen(filename, "r"); + if (!f) + { + perror(filename); + exit(1); + } + + linenum = 0; + while (fgets(line, sizeof(line), f)) + { + linenum++; + cp = line + strspn(line, WHITESPACE); + if (!*cp || *cp == '#') + continue; + cp = strtok(cp, WHITESPACE); + { + char *t = cp; + for (; *t != 0; t++) + if ('A' <= *t && *t <= 'Z') + *t = *t - 'A' + 'a'; /* tolower */ + + } + opcode = parse_token(cp, filename, linenum); + switch (opcode) + { + case sPort: + intptr = &options->port; + parse_int: + cp = strtok(NULL, WHITESPACE); + if (!cp) + { + fprintf(stderr, "%s line %d: missing integer value.\n", + filename, linenum); + exit(1); + } + value = atoi(cp); + if (*intptr == -1) + *intptr = value; + break; + + case sServerKeyBits: + intptr = &options->server_key_bits; + goto parse_int; + + case sLoginGraceTime: + intptr = &options->login_grace_time; + goto parse_int; + + case sKeyRegenerationTime: + intptr = &options->key_regeneration_time; + goto parse_int; + + case sListenAddress: + cp = strtok(NULL, WHITESPACE); + if (!cp) + { + fprintf(stderr, "%s line %d: missing inet addr.\n", + filename, linenum); + exit(1); + } + options->listen_addr.s_addr = inet_addr(cp); + break; + + case sHostKeyFile: + charptr = &options->host_key_file; + cp = strtok(NULL, WHITESPACE); + if (!cp) + { + fprintf(stderr, "%s line %d: missing file name.\n", + filename, linenum); + exit(1); + } + if (*charptr == NULL) + *charptr = tilde_expand_filename(cp, getuid()); + break; + + case sRandomSeedFile: + fprintf(stderr, "%s line %d: \"randomseed\" option is obsolete.\n", + filename, linenum); + cp = strtok(NULL, WHITESPACE); + break; + + case sPermitRootLogin: + intptr = &options->permit_root_login; + cp = strtok(NULL, WHITESPACE); + if (!cp) + { + fprintf(stderr, "%s line %d: missing yes/without-password/no argument.\n", + filename, linenum); + exit(1); + } + if (strcmp(cp, "without-password") == 0) + value = 2; + else if (strcmp(cp, "yes") == 0) + value = 1; + else if (strcmp(cp, "no") == 0) + value = 0; + else + { + fprintf(stderr, "%s line %d: Bad yes/without-password/no argument: %s\n", + filename, linenum, cp); + exit(1); + } + if (*intptr == -1) + *intptr = value; + break; + + case sIgnoreRhosts: + intptr = &options->ignore_rhosts; + parse_flag: + cp = strtok(NULL, WHITESPACE); + if (!cp) + { + fprintf(stderr, "%s line %d: missing yes/no argument.\n", + filename, linenum); + exit(1); + } + if (strcmp(cp, "yes") == 0) + value = 1; + else + if (strcmp(cp, "no") == 0) + value = 0; + else + { + fprintf(stderr, "%s line %d: Bad yes/no argument: %s\n", + filename, linenum, cp); + exit(1); + } + if (*intptr == -1) + *intptr = value; + break; + + case sQuietMode: + intptr = &options->quiet_mode; + goto parse_flag; + + case sFascistLogging: + intptr = &options->fascist_logging; + goto parse_flag; + + case sRhostsAuthentication: + intptr = &options->rhosts_authentication; + goto parse_flag; + + case sRhostsRSAAuthentication: + intptr = &options->rhosts_rsa_authentication; + goto parse_flag; + + case sRSAAuthentication: + intptr = &options->rsa_authentication; + goto parse_flag; + +#ifdef KRB4 + case sKerberosAuthentication: + intptr = &options->kerberos_authentication; + goto parse_flag; + + case sKerberosOrLocalPasswd: + intptr = &options->kerberos_or_local_passwd; + goto parse_flag; + + case sKerberosTicketCleanup: + intptr = &options->kerberos_ticket_cleanup; + goto parse_flag; +#endif + +#ifdef AFS + case sKerberosTgtPassing: + intptr = &options->kerberos_tgt_passing; + goto parse_flag; + + case sAFSTokenPassing: + intptr = &options->afs_token_passing; + goto parse_flag; +#endif + + case sPasswordAuthentication: + intptr = &options->password_authentication; + goto parse_flag; + + case sCheckMail: + intptr = &options->check_mail; + goto parse_flag; + +#ifdef SKEY + case sSkeyAuthentication: + intptr = &options->skey_authentication; + goto parse_flag; +#endif + + case sPrintMotd: + intptr = &options->print_motd; + goto parse_flag; + + case sX11Forwarding: + intptr = &options->x11_forwarding; + goto parse_flag; + + case sX11DisplayOffset: + intptr = &options->x11_display_offset; + goto parse_int; + + case sStrictModes: + intptr = &options->strict_modes; + goto parse_flag; + + case sKeepAlives: + intptr = &options->keepalives; + goto parse_flag; + + case sEmptyPasswd: + intptr = &options->permit_empty_passwd; + goto parse_flag; + + case sUseLogin: + intptr = &options->use_login; + goto parse_flag; + + case sLogFacility: + cp = strtok(NULL, WHITESPACE); + if (!cp) + { + fprintf(stderr, "%s line %d: missing facility name.\n", + filename, linenum); + exit(1); + } + for (i = 0; log_facilities[i].name; i++) + if (strcmp(log_facilities[i].name, cp) == 0) + break; + if (!log_facilities[i].name) + { + fprintf(stderr, "%s line %d: unsupported log facility %s\n", + filename, linenum, cp); + exit(1); + } + if (options->log_facility == (SyslogFacility)(-1)) + options->log_facility = log_facilities[i].facility; + break; + + case sAllowUsers: + while ((cp = strtok(NULL, WHITESPACE))) + { + if (options->num_allow_users >= MAX_ALLOW_USERS) + { + fprintf(stderr, "%s line %d: too many allow users.\n", + filename, linenum); + exit(1); + } + options->allow_users[options->num_allow_users++] = xstrdup(cp); + } + break; + + case sDenyUsers: + while ((cp = strtok(NULL, WHITESPACE))) + { + if (options->num_deny_users >= MAX_DENY_USERS) + { + fprintf(stderr, "%s line %d: too many deny users.\n", + filename, linenum); + exit(1); + } + options->deny_users[options->num_deny_users++] = xstrdup(cp); + } + break; + + case sAllowGroups: + while ((cp = strtok(NULL, WHITESPACE))) + { + if (options->num_allow_groups >= MAX_ALLOW_GROUPS) + { + fprintf(stderr, "%s line %d: too many allow groups.\n", + filename, linenum); + exit(1); + } + options->allow_groups[options->num_allow_groups++] = xstrdup(cp); + } + break; + + case sDenyGroups: + while ((cp = strtok(NULL, WHITESPACE))) + { + if (options->num_deny_groups >= MAX_DENY_GROUPS) + { + fprintf(stderr, "%s line %d: too many deny groups.\n", + filename, linenum); + exit(1); + } + options->deny_groups[options->num_deny_groups++] = xstrdup(cp); + } + break; + + default: + fprintf(stderr, "%s line %d: Missing handler for opcode %s (%d)\n", + filename, linenum, cp, opcode); + exit(1); + } + if (strtok(NULL, WHITESPACE) != NULL) + { + fprintf(stderr, "%s line %d: garbage at end of line.\n", + filename, linenum); + exit(1); + } + } + fclose(f); +} diff --git a/servconf.h b/servconf.h new file mode 100644 index 00000000..22c73fd7 --- /dev/null +++ b/servconf.h @@ -0,0 +1,86 @@ +/* + +servconf.h + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Mon Aug 21 15:35:03 1995 ylo + +Definitions for server configuration data and for the functions reading it. + +*/ + +/* RCSID("$Id: servconf.h,v 1.1 1999/10/27 03:42:45 damien Exp $"); */ + +#ifndef SERVCONF_H +#define SERVCONF_H + +#define MAX_ALLOW_USERS 256 /* Max # users on allow list. */ +#define MAX_DENY_USERS 256 /* Max # users on deny list. */ +#define MAX_ALLOW_GROUPS 256 /* Max # groups on allow list. */ +#define MAX_DENY_GROUPS 256 /* Max # groups on deny list. */ + +typedef struct +{ + int port; /* Port number to listen on. */ + struct in_addr listen_addr; /* Address on which the server listens. */ + char *host_key_file; /* File containing host key. */ + int server_key_bits; /* Size of the server key. */ + int login_grace_time; /* Disconnect if no auth in this time (sec). */ + int key_regeneration_time; /* Server key lifetime (seconds). */ + int permit_root_login; /* If true, permit root login. */ + int ignore_rhosts; /* Ignore .rhosts and .shosts. */ + int quiet_mode; /* If true, don't log anything but fatals. */ + int fascist_logging; /* Perform very verbose logging. */ + int print_motd; /* If true, print /etc/motd. */ + int check_mail; /* If true, check for new mail. */ + int x11_forwarding; /* If true, permit inet (spoofing) X11 fwd. */ + int x11_display_offset; /* What DISPLAY number to start searching at */ + int strict_modes; /* If true, require string home dir modes. */ + int keepalives; /* If true, set SO_KEEPALIVE. */ + SyslogFacility log_facility; /* Facility for system logging. */ + int rhosts_authentication; /* If true, permit rhosts authentication. */ + int rhosts_rsa_authentication;/* If true, permit rhosts RSA authentication.*/ + int rsa_authentication; /* If true, permit RSA authentication. */ +#ifdef KRB4 + int kerberos_authentication; /* If true, permit Kerberos authentication. */ + int kerberos_or_local_passwd; /* If true, permit kerberos and any other + password authentication mechanism, such + as SecurID or /etc/passwd */ + int kerberos_ticket_cleanup; /* If true, destroy ticket file on logout. */ +#endif +#ifdef AFS + int kerberos_tgt_passing; /* If true, permit Kerberos tgt passing. */ + int afs_token_passing; /* If true, permit AFS token passing. */ +#endif + int password_authentication; /* If true, permit password authentication. */ +#ifdef SKEY + int skey_authentication; /* If true, permit s/key authentication. */ +#endif + int permit_empty_passwd; /* If false, do not permit empty passwords. */ + int use_login; /* If true, login(1) is used */ + unsigned int num_allow_users; + char *allow_users[MAX_ALLOW_USERS]; + unsigned int num_deny_users; + char *deny_users[MAX_DENY_USERS]; + unsigned int num_allow_groups; + char *allow_groups[MAX_ALLOW_GROUPS]; + unsigned int num_deny_groups; + char *deny_groups[MAX_DENY_GROUPS]; +} ServerOptions; + +/* Initializes the server options to special values that indicate that they + have not yet been set. */ +void initialize_server_options(ServerOptions *options); + +/* Reads the server configuration file. This only sets the values for those + options that have the special value indicating they have not been set. */ +void read_server_config(ServerOptions *options, const char *filename); + +/* Sets values for those values that have not yet been set. */ +void fill_default_server_options(ServerOptions *options); + +#endif /* SERVCONF_H */ diff --git a/serverloop.c b/serverloop.c new file mode 100644 index 00000000..552c69c2 --- /dev/null +++ b/serverloop.c @@ -0,0 +1,644 @@ +/* + +serverloop.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Sun Sep 10 00:30:37 1995 ylo + +Server main loop for handling the interactive session. + +*/ + +#include "includes.h" +#include "xmalloc.h" +#include "ssh.h" +#include "packet.h" +#include "buffer.h" +#include "servconf.h" +#include "pty.h" + +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 connection_in; /* Connection to client (input). */ +static int connection_out; /* Connection to client (output). */ +static unsigned int buffer_high;/* "Soft" max buffer size. */ +static int max_fd; /* Max file descriptor number for select(). */ + +/* 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 int child_pid; /* Pid of the child. */ +static volatile int child_terminated; /* The child has terminated. */ +static volatile int child_wait_status; /* Status from wait(). */ + +void sigchld_handler(int sig) +{ + int save_errno = errno; + int wait_pid; + debug("Received SIGCHLD."); + wait_pid = wait((int *)&child_wait_status); + if (wait_pid != -1) + { + if (wait_pid != child_pid) + error("Strange, got SIGCHLD and wait returned pid %d but child is %d", + wait_pid, child_pid); + if (WIFEXITED(child_wait_status) || + WIFSIGNALED(child_wait_status)) + child_terminated = 1; + } + signal(SIGCHLD, sigchld_handler); + errno = save_errno; +} + +/* Process any buffered packets that have been received from the client. */ + +void process_buffered_input_packets() +{ + int type; + char *data; + unsigned int data_len; + int row, col, xpixel, ypixel; + int payload_len; + + /* Process buffered packets from the client. */ + while ((type = packet_read_poll(&payload_len)) != SSH_MSG_NONE) + { + switch (type) + { + case SSH_CMSG_STDIN_DATA: + /* Stdin data from the client. Append it to the buffer. */ + if (fdin == -1) + break; /* Ignore any data if the client has closed stdin. */ + data = packet_get_string(&data_len); + packet_integrity_check(payload_len, (4 + data_len), type); + buffer_append(&stdin_buffer, data, data_len); + memset(data, 0, data_len); + xfree(data); + break; + + case SSH_CMSG_EOF: + /* 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_integrity_check(payload_len, 0, type); + stdin_eof = 1; + break; + + case SSH_CMSG_WINDOW_SIZE: + debug("Window change received."); + packet_integrity_check(payload_len, 4*4, type); + row = packet_get_int(); + col = packet_get_int(); + xpixel = packet_get_int(); + ypixel = packet_get_int(); + if (fdin != -1) + pty_change_window_size(fdin, row, col, xpixel, ypixel); + break; + + case SSH_MSG_PORT_OPEN: + debug("Received port open request."); + channel_input_port_open(payload_len); + break; + + case SSH_MSG_CHANNEL_OPEN_CONFIRMATION: + debug("Received channel open confirmation."); + packet_integrity_check(payload_len, 4 + 4, type); + channel_input_open_confirmation(); + break; + + case SSH_MSG_CHANNEL_OPEN_FAILURE: + debug("Received channel open failure."); + packet_integrity_check(payload_len, 4, type); + channel_input_open_failure(); + break; + + case SSH_MSG_CHANNEL_DATA: + channel_input_data(payload_len); + break; + + case SSH_MSG_CHANNEL_CLOSE: + debug("Received channel close."); + packet_integrity_check(payload_len, 4, type); + channel_input_close(); + break; + + case SSH_MSG_CHANNEL_CLOSE_CONFIRMATION: + debug("Received channel close confirmation."); + packet_integrity_check(payload_len, 4, type); + channel_input_close_confirmation(); + break; + + default: + /* In this phase, any unexpected messages cause a protocol + error. This is to ease debugging; also, since no + confirmations are sent messages, unprocessed unknown + messages could cause strange problems. Any compatible + protocol extensions must be negotiated before entering the + interactive session. */ + packet_disconnect("Protocol error during session: type %d", + type); + } + } +} + +/* Make packets from buffered stderr data, and buffer it for sending + to the client. */ + +void make_packets_from_stderr_data() +{ + 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 + { + if (len > 32768) + len = 32768; /* Keep the packets at reasonable size. */ + } + 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. */ + +void make_packets_from_stdout_data() +{ + 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 + { + if (len > 32768) + len = 32768; /* Keep the packets at reasonable size. */ + } + packet_start(SSH_SMSG_STDOUT_DATA); + packet_put_string(buffer_ptr(&stdout_buffer), len); + packet_send(); + buffer_consume(&stdout_buffer, len); + stdout_bytes += len; + } +} + +/* 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). */ + +void wait_until_can_do_something(fd_set *readset, fd_set *writeset, + unsigned int max_time_milliseconds) +{ + struct timeval tv, *tvp; + int ret; + + /* When select fails we restart from here. */ +retry_select: + + /* Initialize select() masks. */ + FD_ZERO(readset); + + /* Read packets from the client unless we have too much buffered stdin + or channel data. */ + if (buffer_len(&stdin_buffer) < 4096 && + channel_not_very_much_buffered_data()) + FD_SET(connection_in, readset); + + /* 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()) + { + if (!fdout_eof) + FD_SET(fdout, readset); + if (!fderr_eof) + FD_SET(fderr, readset); + } + + FD_ZERO(writeset); + + /* Set masks for channel descriptors. */ + channel_prepare_select(readset, writeset); + + /* If we have buffered packet data going to the client, mark that + descriptor. */ + if (packet_have_data_to_write()) + FD_SET(connection_out, writeset); + + /* 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, writeset); + + /* Update the maximum descriptor number if appropriate. */ + if (channel_max_fd() > max_fd) + max_fd = channel_max_fd(); + + /* If child has terminated, read as much as is available and then exit. */ + if (child_terminated) + if (max_time_milliseconds == 0) + 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(max_fd + 1, readset, writeset, NULL, tvp); + + if (ret < 0) + { + if (errno != EINTR) + error("select: %.100s", strerror(errno)); + else + goto retry_select; + } +} + +/* Processes input from the client and the program. Input data is stored + in buffers and processed later. */ + +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) + fatal("Connection closed by remote host."); + + /* There is a kernel bug on Solaris that causes select to sometimes + wake up even though there is no data available. */ + if (len < 0 && errno == EAGAIN) + len = 0; + + if (len < 0) + fatal("Read error from remote host: %.100s", strerror(errno)); + + /* Buffer any received data. */ + packet_process_incoming(buf, len); + } + + /* Read and buffer any available stdout data from the program. */ + if (!fdout_eof && FD_ISSET(fdout, readset)) + { + len = read(fdout, buf, sizeof(buf)); + if (len <= 0) + 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)) + { + len = read(fderr, buf, sizeof(buf)); + if (len <= 0) + fderr_eof = 1; + else + buffer_append(&stderr_buffer, buf, len); + } +} + +/* Sends data from internal buffers to client program stdin. */ + +void process_output(fd_set *writeset) +{ + int len; + + /* Write buffered data to program stdin. */ + if (fdin != -1 && FD_ISSET(fdin, writeset)) + { + len = write(fdin, buffer_ptr(&stdin_buffer), + buffer_len(&stdin_buffer)); + if (len <= 0) + { +#ifdef USE_PIPES + close(fdin); +#else + if (fdout == -1) + close(fdin); + else + shutdown(fdin, SHUT_WR); /* We will no longer send. */ +#endif + fdin = -1; + } + else + { + /* Successful write. 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. */ + +void drain_output() +{ + /* 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(); +} + +/* 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(int pid, int fdin_arg, int fdout_arg, int fderr_arg) +{ + int wait_status, wait_pid; /* Status and pid returned by wait(). */ + int waiting_termination = 0; /* Have displayed waiting close message. */ + unsigned int max_time_milliseconds; + unsigned int previous_stdout_buffer_bytes; + unsigned int stdout_buffer_bytes; + int type; + + debug("Entering interactive session."); + + /* Initialize the SIGCHLD kludge. */ + child_pid = pid; + child_terminated = 0; + signal(SIGCHLD, sigchld_handler); + + /* Initialize our global variables. */ + fdin = fdin_arg; + fdout = fdout_arg; + fderr = fderr_arg; + connection_in = packet_get_connection_in(); + connection_out = packet_get_connection_out(); + + previous_stdout_buffer_bytes = 0; + + /* Set approximate I/O buffer size. */ + if (packet_is_interactive()) + buffer_high = 4096; + else + buffer_high = 64 * 1024; + + /* Initialize max_fd to the maximum of the known file descriptors. */ + max_fd = fdin; + if (fdout > max_fd) + max_fd = fdout; + if (fderr != -1 && fderr > max_fd) + max_fd = fderr; + if (connection_in > max_fd) + max_fd = connection_in; + if (connection_out > max_fd) + max_fd = connection_out; + + /* 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; + + /* Main loop of the server for the interactive session mode. */ + for (;;) + { + fd_set readset, writeset; + + /* 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) + { +#ifdef USE_PIPES + close(fdin); +#else + if (fdout == -1) + close(fdin); + else + shutdown(fdin, SHUT_WR); /* We will no longer send. */ +#endif + 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) + max_time_milliseconds = 10; /* try again after a while */ + else + make_packets_from_stdout_data(); /* Send it now. */ + 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()) + goto quit; + 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); + } + } + + /* Sleep in select() until we can do something. */ + wait_until_can_do_something(&readset, &writeset, + max_time_milliseconds); + + /* 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); + } + + quit: + /* 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; + + /* Stop listening for channels; this removes unix domain sockets. */ + channel_stop_listening(); + + /* Wait for the child to exit. Get its exit status. */ + wait_pid = wait(&wait_status); + if (wait_pid < 0) + { + /* It is possible that the wait was handled by SIGCHLD handler. This + may result in either: this call returning with EINTR, or: this + call returning ECHILD. */ + if (child_terminated) + wait_status = child_wait_status; + else + packet_disconnect("wait: %.100s", strerror(errno)); + } + else + { + /* Check if it matches the process we forked. */ + if (wait_pid != pid) + error("Strange, wait returned pid %d, expected %d", wait_pid, pid); + } + + /* We no longer want our SIGCHLD handler to be called. */ + signal(SIGCHLD, SIG_DFL); + + /* 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 + { + int plen; + type = packet_read(&plen); + } + 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*/ +} + diff --git a/ssh-add.1 b/ssh-add.1 new file mode 100644 index 00000000..4c64ab2b --- /dev/null +++ b/ssh-add.1 @@ -0,0 +1,116 @@ +.\" -*- nroff -*- +.\" +.\" ssh-add.1 +.\" +.\" Author: Tatu Ylonen <ylo@cs.hut.fi> +.\" +.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland +.\" All rights reserved +.\" +.\" Created: Sat Apr 22 23:55:14 1995 ylo +.\" +.\" $Id: ssh-add.1,v 1.1 1999/10/27 03:42:45 damien Exp $ +.\" +.Dd September 25, 1999 +.Dt SSH-ADD 1 +.Os +.Sh NAME +.Nm ssh-add +.Nd adds identities for the authentication agent +.Sh SYNOPSIS +.Nm ssh-add +.Op Fl ldD +.Op Ar +.Sh DESCRIPTION +.Nm +adds identities to the authentication agent, +.Xr ssh-agent 1 . +When run without arguments, it adds the file +.Pa $HOME/.ssh/identity . +Alternative file names can be given on the +command line. If any file requires a passphrase, +.Nm +asks for the passphrase from the user. +The Passphrase it is read from the user's tty. +.Pp +The authentication agent must be running and must be an ancestor of +the current process for +.Nm +to work. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl l +Lists all identities currently represented by the agent. +.It Fl d +Instead of adding the identity, removes the identity from the agent. +.It Fl D +Deletes all identities from the agent. +.El +.Sh FILES +.Bl -tag -width Ds +.Pa $HOME/.ssh/identity +Contains the RSA authentication identity of the user. This file +should not be readable by anyone but the user. +Note that +.Nm +ignores this file if it is accessible by others. +It is possible to +specify a passphrase when generating the key; that passphrase will be +used to encrypt the private part of this file. This is the +default file added by +.Nm +when no other files have been specified. +.Pp +If +.Nm +needs a passphrase, it will read the passphrase from the current +terminal if it was run from a terminal. If +.Nm +does not have a terminal associated with it but +.Ev DISPLAY +is set, it +will open an X11 window to read the passphrase. This is particularly +useful when calling +.Nm +from a +.Pa .Xsession +or related script. (Note that on some machines it +may be necessary to redirect the input from +.Pa /dev/null +to make this work.) +.Sh AUTHOR +Tatu Ylonen <ylo@cs.hut.fi> +.Pp +OpenSSH +is a derivative of the original (free) ssh 1.2.12 release, but with bugs +removed and newer features re-added. Rapidly after the 1.2.12 release, +newer versions bore successively more restrictive licenses. This version +of OpenSSH +.Bl -bullet +.It +has all components of a restrictive nature (ie. patents, see +.Xr ssl 8 ) +directly removed from the source code; any licensed or patented components +are chosen from +external libraries. +.It +has been updated to support ssh protocol 1.5. +.It +contains added support for +.Xr kerberos 8 +authentication and ticket passing. +.It +supports one-time password authentication with +.Xr skey 1 . +.El +.Pp +The libraries described in +.Xr ssl 8 +are required for proper operation. +.Sh SEE ALSO +.Xr ssh 1 , +.Xr ssh-agent 1 , +.Xr ssh-keygen 1 , +.Xr sshd 8 , +.Xr ssl 8 diff --git a/ssh-add.c b/ssh-add.c new file mode 100644 index 00000000..5ac3c303 --- /dev/null +++ b/ssh-add.c @@ -0,0 +1,254 @@ +/* + +ssh-add.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Thu Apr 6 00:52:24 1995 ylo + +Adds an identity to the authentication server, or removes an identity. + +*/ + +#include "includes.h" +RCSID("$Id: ssh-add.c,v 1.1 1999/10/27 03:42:45 damien Exp $"); + +#include "rsa.h" +#include "ssh.h" +#include "xmalloc.h" +#include "authfd.h" + +void +delete_file(const char *filename) +{ + RSA *key; + char *comment; + AuthenticationConnection *ac; + + key = RSA_new(); + if (!load_public_key(filename, key, &comment)) + { + printf("Bad key file %s: %s\n", filename, strerror(errno)); + return; + } + + /* Send the request to the authentication agent. */ + ac = ssh_get_authentication_connection(); + if (!ac) + { + fprintf(stderr, + "Could not open a connection to your authentication agent.\n"); + RSA_free(key); + xfree(comment); + return; + } + if (ssh_remove_identity(ac, key)) + fprintf(stderr, "Identity removed: %s (%s)\n", filename, comment); + else + fprintf(stderr, "Could not remove identity: %s\n", filename); + RSA_free(key); + xfree(comment); + ssh_close_authentication_connection(ac); +} + +void +delete_all() +{ + AuthenticationConnection *ac; + + /* Get a connection to the agent. */ + ac = ssh_get_authentication_connection(); + if (!ac) + { + fprintf(stderr, + "Could not open a connection to your authentication agent.\n"); + return; + } + + /* Send a request to remove all identities. */ + if (ssh_remove_all_identities(ac)) + fprintf(stderr, "All identities removed.\n"); + else + fprintf(stderr, "Failed to remove all identitities.\n"); + + /* Close the connection to the agent. */ + ssh_close_authentication_connection(ac); +} + +void +add_file(const char *filename) +{ + RSA *key; + RSA *public_key; + AuthenticationConnection *ac; + char *saved_comment, *comment, *pass; + int first; + + key = RSA_new(); + public_key = RSA_new(); + if (!load_public_key(filename, public_key, &saved_comment)) + { + printf("Bad key file %s: %s\n", filename, strerror(errno)); + return; + } + RSA_free(public_key); + + pass = xstrdup(""); + first = 1; + while (!load_private_key(filename, pass, key, &comment)) + { + /* Free the old passphrase. */ + memset(pass, 0, strlen(pass)); + xfree(pass); + + /* Ask for a passphrase. */ + if (getenv("DISPLAY") && !isatty(fileno(stdin))) + { + xfree(saved_comment); + return; + } + else + { + if (first) + printf("Need passphrase for %s (%s).\n", filename, saved_comment); + else + printf("Bad passphrase.\n"); + pass = read_passphrase("Enter passphrase: ", 1); + if (strcmp(pass, "") == 0) + { + xfree(saved_comment); + xfree(pass); + return; + } + } + first = 0; + } + memset(pass, 0, strlen(pass)); + xfree(pass); + + xfree(saved_comment); + + /* Send the key to the authentication agent. */ + ac = ssh_get_authentication_connection(); + if (!ac) + { + fprintf(stderr, + "Could not open a connection to your authentication agent.\n"); + RSA_free(key); + xfree(comment); + return; + } + if (ssh_add_identity(ac, key, comment)) + fprintf(stderr, "Identity added: %s (%s)\n", filename, comment); + else + fprintf(stderr, "Could not add identity: %s\n", filename); + RSA_free(key); + xfree(comment); + ssh_close_authentication_connection(ac); +} + +void +list_identities() +{ + AuthenticationConnection *ac; + BIGNUM *e, *n; + int bits, status; + char *comment; + int had_identities; + + ac = ssh_get_authentication_connection(); + if (!ac) + { + fprintf(stderr, "Could not connect to authentication server.\n"); + return; + } + e = BN_new(); + n = BN_new(); + had_identities = 0; + for (status = ssh_get_first_identity(ac, &bits, e, n, &comment); + status; + status = ssh_get_next_identity(ac, &bits, e, n, &comment)) + { + char *buf; + had_identities = 1; + printf("%d ", bits); + buf = BN_bn2dec(e); + assert(buf != NULL); + printf("%s ", buf); + free (buf); + buf = BN_bn2dec(n); + assert(buf != NULL); + printf("%s %s\n", buf, comment); + free (buf); + xfree(comment); + } + BN_clear_free(e); + BN_clear_free(n); + if (!had_identities) + printf("The agent has no identities.\n"); + ssh_close_authentication_connection(ac); +} + +int +main(int ac, char **av) +{ + struct passwd *pw; + char buf[1024]; + int no_files = 1; + int i; + int deleting = 0; + + /* check if RSA support exists */ + if (rsa_alive() == 0) { + extern char *__progname; + + fprintf(stderr, + "%s: no RSA support in libssl and libcrypto. See ssl(8).\n", + __progname); + exit(1); + } + + for (i = 1; i < ac; i++) + { + if (strcmp(av[i], "-l") == 0) + { + list_identities(); + no_files = 0; /* Don't default-add/delete if -l. */ + continue; + } + if (strcmp(av[i], "-d") == 0) + { + deleting = 1; + continue; + } + if (strcmp(av[i], "-D") == 0) + { + delete_all(); + no_files = 0; + continue; + } + no_files = 0; + if (deleting) + delete_file(av[i]); + else + add_file(av[i]); + } + if (no_files) + { + pw = getpwuid(getuid()); + if (!pw) + { + fprintf(stderr, "No user found with uid %d\n", (int)getuid()); + exit(1); + } + snprintf(buf, sizeof buf, "%s/%s", pw->pw_dir, SSH_CLIENT_IDENTITY); + if (deleting) + delete_file(buf); + else + add_file(buf); + } + exit(0); +} diff --git a/ssh-agent.1 b/ssh-agent.1 new file mode 100644 index 00000000..01c43cde --- /dev/null +++ b/ssh-agent.1 @@ -0,0 +1,124 @@ +.\" -*- nroff -*- +.\" +.\" ssh-agent.1 +.\" +.\" Author: Tatu Ylonen <ylo@cs.hut.fi> +.\" +.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland +.\" All rights reserved +.\" +.\" Created: Sat Apr 23 20:10:43 1995 ylo +.\" +.\" $Id: ssh-agent.1,v 1.1 1999/10/27 03:42:45 damien Exp $ +.\" +.Dd September 25, 1999 +.Dt SSH-AGENT 1 +.Os +.Sh NAME +.Nm ssh-agent +.Nd authentication agent +.Sh SYNOPSIS +.Nm ssh-agent +.Ar command +.Sh DESCRIPTION +.Nm +is a program to hold authentication private keys. The +idea is that +.Nm +is started in the beginning of an X-session or a login session, and +all other windows or programs are started as children of the ssh-agent +program (the +.Ar command +normally starts X or is the user shell). Programs started under +the agent inherit a connection to the agent, and the agent is +automatically used for RSA authentication when logging to other +machines using +.Xr ssh 1 . +.Pp +The agent initially does not have any private keys. Keys are added +using +.Xr ssh-add 1 . +When executed without arguments, +.Xr ssh-add 1 +adds the +.Pa $HOME/.ssh/identity +file. If the identity has a passphrase, +.Xr ssh-add 1 +asks for the passphrase (using a small X11 application if running +under X11, or from the terminal if running without X). It then sends +the identity to the agent. Several identities can be stored in the +agent; the agent can automatically use any of these identities. +.Ic ssh-add -l +displays the identities currently held by the agent. +.Pp +The idea is that the agent is run in the user's local PC, laptop, or +terminal. Authentication data need not be stored on any other +machine, and authentication passphrases never go over the network. +However, the connection to the agent is forwarded over SSH +remote logins, and the user can thus use the privileges given by the +identities anywhere in the network in a secure way. +.Pp +A connection to the agent is inherited by child programs: +A unix-domain socket is created +.Pq Pa /tmp/ssh-XXXX/agent.<pid> , +and the name of this socket is stored in the +.Ev SSH_AUTH_SOCK +environment +variable. The socket is made accessible only to the current user. +This method is easily abused by root or another instance of the same +user. +.Pp +The agent exits automatically when the command given on the command +line terminates. +.Sh FILES +.Bl -tag -width Ds +.It Pa $HOME/.ssh/identity +Contains the RSA authentication identity of the user. This file +should not be readable by anyone but the user. It is possible to +specify a passphrase when generating the key; that passphrase will be +used to encrypt the private part of this file. This file +is not used by +.Nm +but is normally added to the agent using +.Xr ssh-add 1 +at login time. +.It Pa /tmp/ssh-XXXX/agent.<pid> , +Unix-domain sockets used to contain the connection to the +authentication agent. These sockets should only be readable by the +owner. The sockets should get automatically removed when the agent +exits. +.Sh AUTHOR +Tatu Ylonen <ylo@cs.hut.fi> +.Pp +OpenSSH +is a derivative of the original (free) ssh 1.2.12 release, but with bugs +removed and newer features re-added. Rapidly after the 1.2.12 release, +newer versions bore successively more restrictive licenses. This version +of OpenSSH +.Bl -bullet +.It +has all components of a restrictive nature (ie. patents, see +.Xr ssl 8 ) +directly removed from the source code; any licensed or patented components +are chosen from +external libraries. +.It +has been updated to support ssh protocol 1.5. +.It +contains added support for +.Xr kerberos 8 +authentication and ticket passing. +.It +supports one-time password authentication with +.Xr skey 1 . +.El +.Pp +The libraries described in +.Xr ssl 8 +are required for proper operation. +.Sh SEE ALSO +.Xr ssh 1 , +.Xr ssh-add 1 , +.Xr ssh-keygen 1 , +.Xr sshd 8 , +.Xr ssl 8 diff --git a/ssh-agent.c b/ssh-agent.c new file mode 100644 index 00000000..19165b8f --- /dev/null +++ b/ssh-agent.c @@ -0,0 +1,572 @@ +/* + +ssh-agent.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Wed Mar 29 03:46:59 1995 ylo + +The authentication agent program. + +*/ + +#include "includes.h" +RCSID("$Id: ssh-agent.c,v 1.1 1999/10/27 03:42:45 damien Exp $"); + +#include "ssh.h" +#include "rsa.h" +#include "authfd.h" +#include "buffer.h" +#include "bufaux.h" +#include "xmalloc.h" +#include "packet.h" +#include "getput.h" +#include "mpaux.h" + +#include <openssl/md5.h> + +typedef struct +{ + int fd; + enum { AUTH_UNUSED, AUTH_SOCKET, AUTH_CONNECTION } type; + Buffer input; + Buffer output; +} SocketEntry; + +unsigned int sockets_alloc = 0; +SocketEntry *sockets = NULL; + +typedef struct +{ + RSA *key; + char *comment; +} Identity; + +unsigned int num_identities = 0; +Identity *identities = NULL; + +int max_fd = 0; + +/* pid of shell == parent of agent */ +int parent_pid = -1; + +/* pathname and directory for AUTH_SOCKET */ +char socket_name[1024]; +char socket_dir[1024]; + +void +process_request_identity(SocketEntry *e) +{ + Buffer msg; + int i; + + buffer_init(&msg); + buffer_put_char(&msg, SSH_AGENT_RSA_IDENTITIES_ANSWER); + buffer_put_int(&msg, num_identities); + for (i = 0; i < num_identities; i++) + { + buffer_put_int(&msg, BN_num_bits(identities[i].key->n)); + buffer_put_bignum(&msg, identities[i].key->e); + buffer_put_bignum(&msg, identities[i].key->n); + buffer_put_string(&msg, identities[i].comment, + strlen(identities[i].comment)); + } + buffer_put_int(&e->output, buffer_len(&msg)); + buffer_append(&e->output, buffer_ptr(&msg), buffer_len(&msg)); + buffer_free(&msg); +} + +void +process_authentication_challenge(SocketEntry *e) +{ + int i, pub_bits, len; + BIGNUM *pub_e, *pub_n, *challenge; + Buffer msg; + MD5_CTX md; + unsigned char buf[32], mdbuf[16], session_id[16]; + unsigned int response_type; + + buffer_init(&msg); + pub_e = BN_new(); + pub_n = BN_new(); + challenge = BN_new(); + pub_bits = buffer_get_int(&e->input); + buffer_get_bignum(&e->input, pub_e); + buffer_get_bignum(&e->input, pub_n); + buffer_get_bignum(&e->input, challenge); + if (buffer_len(&e->input) == 0) + { + /* Compatibility code for old servers. */ + memset(session_id, 0, 16); + response_type = 0; + } + else + { + /* New code. */ + buffer_get(&e->input, (char *)session_id, 16); + response_type = buffer_get_int(&e->input); + } + for (i = 0; i < num_identities; i++) + if (pub_bits == BN_num_bits(identities[i].key->n) && + BN_cmp(pub_e, identities[i].key->e) == 0 && + BN_cmp(pub_n, identities[i].key->n) == 0) + { + /* Decrypt the challenge using the private key. */ + rsa_private_decrypt(challenge, challenge, identities[i].key); + + /* Compute the desired response. */ + switch (response_type) + { + case 0: /* As of protocol 1.0 */ + /* This response type is no longer supported. */ + log("Compatibility with ssh protocol 1.0 no longer supported."); + buffer_put_char(&msg, SSH_AGENT_FAILURE); + goto send; + + case 1: /* As of protocol 1.1 */ + /* The response is MD5 of decrypted challenge plus session id. */ + len = BN_num_bytes(challenge); + assert(len <= 32 && len); + memset(buf, 0, 32); + BN_bn2bin(challenge, buf + 32 - len); + MD5_Init(&md); + MD5_Update(&md, buf, 32); + MD5_Update(&md, session_id, 16); + MD5_Final(mdbuf, &md); + break; + + default: + fatal("process_authentication_challenge: bad response_type %d", + response_type); + break; + } + + /* Send the response. */ + buffer_put_char(&msg, SSH_AGENT_RSA_RESPONSE); + for (i = 0; i < 16; i++) + buffer_put_char(&msg, mdbuf[i]); + + goto send; + } + /* Unknown identity. Send failure. */ + buffer_put_char(&msg, SSH_AGENT_FAILURE); + send: + buffer_put_int(&e->output, buffer_len(&msg)); + buffer_append(&e->output, buffer_ptr(&msg), + buffer_len(&msg)); + buffer_free(&msg); + BN_clear_free(pub_e); + BN_clear_free(pub_n); + BN_clear_free(challenge); +} + +void +process_remove_identity(SocketEntry *e) +{ + unsigned int bits; + unsigned int i; + BIGNUM *dummy, *n; + + dummy = BN_new(); + n = BN_new(); + + /* Get the key from the packet. */ + bits = buffer_get_int(&e->input); + buffer_get_bignum(&e->input, dummy); + buffer_get_bignum(&e->input, n); + + /* Check if we have the key. */ + for (i = 0; i < num_identities; i++) + if (BN_cmp(identities[i].key->n, n) == 0) + { + /* We have this key. Free the old key. Since we don\'t want to leave + empty slots in the middle of the array, we actually free the + key there and copy data from the last entry. */ + RSA_free(identities[i].key); + xfree(identities[i].comment); + if (i < num_identities - 1) + identities[i] = identities[num_identities - 1]; + num_identities--; + BN_clear_free(dummy); + BN_clear_free(n); + + /* Send success. */ + buffer_put_int(&e->output, 1); + buffer_put_char(&e->output, SSH_AGENT_SUCCESS); + return; + } + /* We did not have the key. */ + BN_clear(dummy); + BN_clear(n); + + /* Send failure. */ + buffer_put_int(&e->output, 1); + buffer_put_char(&e->output, SSH_AGENT_FAILURE); +} + +/* Removes all identities from the agent. */ + +void +process_remove_all_identities(SocketEntry *e) +{ + unsigned int i; + + /* Loop over all identities and clear the keys. */ + for (i = 0; i < num_identities; i++) + { + RSA_free(identities[i].key); + xfree(identities[i].comment); + } + + /* Mark that there are no identities. */ + num_identities = 0; + + /* Send success. */ + buffer_put_int(&e->output, 1); + buffer_put_char(&e->output, SSH_AGENT_SUCCESS); + return; +} + +/* Adds an identity to the agent. */ + +void +process_add_identity(SocketEntry *e) +{ + RSA *k; + int i; + BIGNUM *aux; + BN_CTX *ctx; + + if (num_identities == 0) + identities = xmalloc(sizeof(Identity)); + else + identities = xrealloc(identities, (num_identities + 1) * sizeof(Identity)); + + identities[num_identities].key = RSA_new(); + k = identities[num_identities].key; + buffer_get_int(&e->input); /* bits */ + k->n = BN_new(); + buffer_get_bignum(&e->input, k->n); + k->e = BN_new(); + buffer_get_bignum(&e->input, k->e); + k->d = BN_new(); + buffer_get_bignum(&e->input, k->d); + k->iqmp = BN_new(); + buffer_get_bignum(&e->input, k->iqmp); + /* SSH and SSL have p and q swapped */ + k->q = BN_new(); + buffer_get_bignum(&e->input, k->q); /* p */ + k->p = BN_new(); + buffer_get_bignum(&e->input, k->p); /* q */ + + /* Generate additional parameters */ + aux = BN_new(); + ctx = BN_CTX_new(); + + BN_sub(aux, k->q, BN_value_one()); + k->dmq1 = BN_new(); + BN_mod(k->dmq1, k->d, aux, ctx); + + BN_sub(aux, k->p, BN_value_one()); + k->dmp1 = BN_new(); + BN_mod(k->dmp1, k->d, aux, ctx); + + BN_clear_free(aux); + BN_CTX_free(ctx); + + identities[num_identities].comment = buffer_get_string(&e->input, NULL); + + /* Check if we already have the key. */ + for (i = 0; i < num_identities; i++) + if (BN_cmp(identities[i].key->n, k->n) == 0) + { + /* We already have this key. Clear and free the new data and + return success. */ + RSA_free(k); + xfree(identities[num_identities].comment); + + /* Send success. */ + buffer_put_int(&e->output, 1); + buffer_put_char(&e->output, SSH_AGENT_SUCCESS); + return; + } + + /* Increment the number of identities. */ + num_identities++; + + /* Send a success message. */ + buffer_put_int(&e->output, 1); + buffer_put_char(&e->output, SSH_AGENT_SUCCESS); +} + +void +process_message(SocketEntry *e) +{ + unsigned int msg_len; + unsigned int type; + unsigned char *cp; + if (buffer_len(&e->input) < 5) + return; /* Incomplete message. */ + cp = (unsigned char *)buffer_ptr(&e->input); + msg_len = GET_32BIT(cp); + if (msg_len > 256 * 1024) + { + shutdown(e->fd, SHUT_RDWR); + close(e->fd); + e->type = AUTH_UNUSED; + return; + } + if (buffer_len(&e->input) < msg_len + 4) + return; + buffer_consume(&e->input, 4); + type = buffer_get_char(&e->input); + + switch (type) + { + case SSH_AGENTC_REQUEST_RSA_IDENTITIES: + process_request_identity(e); + break; + case SSH_AGENTC_RSA_CHALLENGE: + process_authentication_challenge(e); + break; + case SSH_AGENTC_ADD_RSA_IDENTITY: + process_add_identity(e); + break; + case SSH_AGENTC_REMOVE_RSA_IDENTITY: + process_remove_identity(e); + break; + case SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES: + process_remove_all_identities(e); + break; + default: + /* Unknown message. Respond with failure. */ + error("Unknown message %d", type); + buffer_clear(&e->input); + buffer_put_int(&e->output, 1); + buffer_put_char(&e->output, SSH_AGENT_FAILURE); + break; + } +} + +void +new_socket(int type, int fd) +{ + unsigned int i, old_alloc; + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) + error("fcntl O_NONBLOCK: %s", strerror(errno)); + + if (fd > max_fd) + max_fd = fd; + + for (i = 0; i < sockets_alloc; i++) + if (sockets[i].type == AUTH_UNUSED) + { + sockets[i].fd = fd; + sockets[i].type = type; + buffer_init(&sockets[i].input); + buffer_init(&sockets[i].output); + return; + } + old_alloc = sockets_alloc; + sockets_alloc += 10; + if (sockets) + sockets = xrealloc(sockets, sockets_alloc * sizeof(sockets[0])); + else + sockets = xmalloc(sockets_alloc * sizeof(sockets[0])); + for (i = old_alloc; i < sockets_alloc; i++) + sockets[i].type = AUTH_UNUSED; + sockets[old_alloc].type = type; + sockets[old_alloc].fd = fd; + buffer_init(&sockets[old_alloc].input); + buffer_init(&sockets[old_alloc].output); +} + +void +prepare_select(fd_set *readset, fd_set *writeset) +{ + unsigned int i; + for (i = 0; i < sockets_alloc; i++) + switch (sockets[i].type) + { + case AUTH_SOCKET: + case AUTH_CONNECTION: + FD_SET(sockets[i].fd, readset); + if (buffer_len(&sockets[i].output) > 0) + FD_SET(sockets[i].fd, writeset); + break; + case AUTH_UNUSED: + break; + default: + fatal("Unknown socket type %d", sockets[i].type); + break; + } +} + +void after_select(fd_set *readset, fd_set *writeset) +{ + unsigned int i; + int len, sock; + char buf[1024]; + struct sockaddr_un sunaddr; + + for (i = 0; i < sockets_alloc; i++) + switch (sockets[i].type) + { + case AUTH_UNUSED: + break; + case AUTH_SOCKET: + if (FD_ISSET(sockets[i].fd, readset)) + { + len = sizeof(sunaddr); + sock = accept(sockets[i].fd, (struct sockaddr *)&sunaddr, &len); + if (sock < 0) + { + perror("accept from AUTH_SOCKET"); + break; + } + new_socket(AUTH_CONNECTION, sock); + } + break; + case AUTH_CONNECTION: + if (buffer_len(&sockets[i].output) > 0 && + FD_ISSET(sockets[i].fd, writeset)) + { + len = write(sockets[i].fd, buffer_ptr(&sockets[i].output), + buffer_len(&sockets[i].output)); + if (len <= 0) + { + shutdown(sockets[i].fd, SHUT_RDWR); + close(sockets[i].fd); + sockets[i].type = AUTH_UNUSED; + break; + } + buffer_consume(&sockets[i].output, len); + } + if (FD_ISSET(sockets[i].fd, readset)) + { + len = read(sockets[i].fd, buf, sizeof(buf)); + if (len <= 0) + { + shutdown(sockets[i].fd, SHUT_RDWR); + close(sockets[i].fd); + sockets[i].type = AUTH_UNUSED; + break; + } + buffer_append(&sockets[i].input, buf, len); + process_message(&sockets[i]); + } + break; + default: + fatal("Unknown type %d", sockets[i].type); + } +} + +void +check_parent_exists(int sig) +{ + if (kill(parent_pid, 0) < 0) + { + /* printf("Parent has died - Authentication agent exiting.\n"); */ + exit(1); + } + signal(SIGALRM, check_parent_exists); + alarm(10); +} + +void cleanup_socket(void) { + remove(socket_name); + rmdir(socket_dir); +} + +int +main(int ac, char **av) +{ + fd_set readset, writeset; + int sock; + struct sockaddr_un sunaddr; + + /* check if RSA support exists */ + if (rsa_alive() == 0) { + extern char *__progname; + fprintf(stderr, + "%s: no RSA support in libssl and libcrypto. See ssl(8).\n", + __progname); + exit(1); + } + + if (ac < 2) + { + fprintf(stderr, "ssh-agent version %s\n", SSH_VERSION); + fprintf(stderr, "Usage: %s command\n", av[0]); + exit(1); + } + + parent_pid = getpid(); + + /* Create private directory for agent socket */ + strlcpy(socket_dir, "/tmp/ssh-XXXXXXXX", sizeof socket_dir); + if (mkdtemp(socket_dir) == NULL) { + perror("mkdtemp: private socket dir"); + exit(1); + } + snprintf(socket_name, sizeof socket_name, "%s/agent.%d", socket_dir, parent_pid); + + /* Fork, and have the parent execute the command. The child continues as + the authentication agent. */ + if (fork() != 0) + { /* Parent - execute the given command. */ + setenv(SSH_AUTHSOCKET_ENV_NAME, socket_name, 1); + execvp(av[1], av + 1); + perror(av[1]); + exit(1); + } + + if (atexit(cleanup_socket) < 0) { + perror("atexit"); + cleanup_socket(); + exit(1); + } + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) + { + perror("socket"); + exit(1); + } + memset(&sunaddr, 0, sizeof(sunaddr)); + sunaddr.sun_family = AF_UNIX; + strlcpy(sunaddr.sun_path, socket_name, sizeof(sunaddr.sun_path)); + if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) < 0) + { + perror("bind"); + exit(1); + } + if (listen(sock, 5) < 0) + { + perror("listen"); + exit(1); + } + new_socket(AUTH_SOCKET, sock); + signal(SIGALRM, check_parent_exists); + alarm(10); + + signal(SIGINT, SIG_IGN); + while (1) + { + FD_ZERO(&readset); + FD_ZERO(&writeset); + prepare_select(&readset, &writeset); + if (select(max_fd + 1, &readset, &writeset, NULL, NULL) < 0) + { + if (errno == EINTR) + continue; + perror("select"); + exit(1); + } + after_select(&readset, &writeset); + } + /*NOTREACHED*/ +} diff --git a/ssh-keygen.1 b/ssh-keygen.1 new file mode 100644 index 00000000..67fbfd2c --- /dev/null +++ b/ssh-keygen.1 @@ -0,0 +1,155 @@ +.\" -*- nroff -*- +.\" +.\" ssh-keygen.1 +.\" +.\" Author: Tatu Ylonen <ylo@cs.hut.fi> +.\" +.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland +.\" All rights reserved +.\" +.\" Created: Sat Apr 22 23:55:14 1995 ylo +.\" +.\" $Id: ssh-keygen.1,v 1.1 1999/10/27 03:42:45 damien Exp $ +.\" +.Dd September 25, 1999 +.Dt SSH-KEYGEN 1 +.Os +.Sh NAME +.Nm ssh-keygen +.Nd authentication key generation +.Sh SYNOPSIS +.Nm ssh-keygen +.Op Fl q +.Op Fl b Ar bits +.Op Fl N Ar new_passphrase +.Op Fl C Ar comment +.Nm ssh-keygen +.Fl p +.Op Fl P Ar old_passphrase +.Op Fl N Ar new_passphrase +.Nm ssh-keygen +.Fl c +.Op Fl P Ar passphrase +.Op Fl C Ar comment +.Sh DESCRIPTION +.Nm +generates and manages authentication keys for +.Xr ssh 1 . +Normally each user wishing to use SSH +with RSA authentication runs this once to create the authentication +key in +.Pa $HOME/.ssh/identity . +Additionally, the system administrator may use this to generate host keys. +.Pp +Normally this program generates the key and asks for a file in which +to store the private key. The public key is stored in a file with the +same name but +.Dq .pub +appended. The program also asks for a +passphrase. The passphrase may be empty to indicate no passphrase +(host keys must have empty passphrase), or it may be a string of +arbitrary length. Good passphrases are 10-30 characters long and are +not simple sentences or otherwise easily guessable (English +prose has only 1-2 bits of entropy per word, and provides very bad +passphrases). The passphrase can be changed later by using the +.Fl p +option. +.Pp +There is no way to recover a lost passphrase. If the passphrase is +lost or forgotten, you will have to generate a new key and copy the +corresponding public key to other machines. +.Pp +There is also a comment field in the key file that is only for +convenience to the user to help identify the key. The comment can +tell what the key is for, or whatever is useful. The comment is +initialized to +.Dq user@host +when the key is created, but can be changed using the +.Fl c +option. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl b Ar bits +Specifies the number of bits in the key to create. Minimum is 512 +bits. Generally 1024 bits is considered sufficient, and key sizes +above that no longer improve security but make things slower. The +default is 1024 bits. +.It Fl c +Requests changing the comment in the private and public key files. +The program will prompt for the file containing the private keys, for +passphrase if the key has one, and for the new comment. +.It Fl p +Requests changing the passphrase of a private key file instead of +creating a new private key. The program will prompt for the file +containing the private key, for the old passphrase, and twice for the +new passphrase. +.It Fl q +Silence +.Nm ssh-keygen . +Used by +.Pa /etc/rc +when creating a new key. +.It Fl C Ar comment +Provides the new comment. +.It Fl N Ar new_passphrase +Provides the new passphrase. +.It Fl P Ar passphrase +Provides the (old) passphrase. +.El +.Sh FILES +.Bl -tag -width Ds +.It Pa $HOME/.ssh/random_seed +Used for seeding the random number generator. This file should not be +readable by anyone but the user. This file is created the first time +the program is run, and is updated every time. +.It Pa $HOME/.ssh/identity +Contains the RSA authentication identity of the user. This file +should not be readable by anyone but the user. It is possible to +specify a passphrase when generating the key; that passphrase will be +used to encrypt the private part of this file using 3DES. This file +is not automatically accessed by +.Nm +but it is offered as the default file for the private key. +.It Pa $HOME/.ssh/identity.pub +Contains the public key for authentication. The contents of this file +should be added to +.Pa $HOME/.ssh/authorized_keys +on all machines +where you wish to log in using RSA authentication. There is no +need to keep the contents of this file secret. +.Sh AUTHOR +Tatu Ylonen <ylo@cs.hut.fi> +.Pp +OpenSSH +is a derivative of the original (free) ssh 1.2.12 release, but with bugs +removed and newer features re-added. Rapidly after the 1.2.12 release, +newer versions bore successively more restrictive licenses. This version +of OpenSSH +.Bl -bullet +.It +has all components of a restrictive nature (ie. patents, see +.Xr ssl 8 ) +directly removed from the source code; any licensed or patented components +are chosen from +external libraries. +.It +has been updated to support ssh protocol 1.5. +.It +contains added support for +.Xr kerberos 8 +authentication and ticket passing. +.It +supports one-time password authentication with +.Xr skey 1 . +.El +.Pp +The libraries described in +.Xr ssl 8 +are required for proper operation. +.Sh SEE ALSO +.Xr ssh 1 , +.Xr ssh-add 1 , +.Xr ssh-agent 1, +.Xr sshd 8 , +.Xr ssl 8 diff --git a/ssh-keygen.c b/ssh-keygen.c new file mode 100644 index 00000000..2ba64e75 --- /dev/null +++ b/ssh-keygen.c @@ -0,0 +1,552 @@ +/* + +ssh-keygen.c + +Author: Tatu Ylonen <ylo@cs.hut.fi> + +Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + All rights reserved + +Created: Mon Mar 27 02:26:40 1995 ylo + +Identity and host key generation and maintenance. + +*/ + +#include "includes.h" +RCSID("$Id: ssh-keygen.c,v 1.1 1999/10/27 03:42:45 damien Exp $"); + +#include "rsa.h" +#include "ssh.h" +#include "xmalloc.h" + +/* Generated private key. */ +RSA *private_key; + +/* Generated public key. */ +RSA *public_key; + +/* Number of bits in the RSA key. This value can be changed on the command + line. */ +int bits = 1024; + +/* Flag indicating that we just want to change the passphrase. This can be + set on the command line. */ +int change_passphrase = 0; + +/* Flag indicating that we just want to change the comment. This can be set + on the command line. */ +int change_comment = 0; + +int quiet = 0; + +/* This is set to the identity file name if given on the command line. */ +char *identity_file = NULL; + +/* This is set to the passphrase if given on the command line. */ +char *identity_passphrase = NULL; + +/* This is set to the new passphrase if given on the command line. */ +char *identity_new_passphrase = NULL; + +/* This is set to the new comment if given on the command line. */ +char *identity_comment = NULL; + +/* Perform changing a passphrase. The argument is the passwd structure + for the current user. */ + +void +do_change_passphrase(struct passwd *pw) +{ + char buf[1024], *comment; + char *old_passphrase, *passphrase1, *passphrase2; + struct stat st; + RSA *private_key; + + /* Read key file name. */ + if (identity_file != NULL) { + strncpy(buf, identity_file, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + } else { + printf("Enter file in which the key is ($HOME/%s): ", SSH_CLIENT_IDENTITY); + fflush(stdout); + if (fgets(buf, sizeof(buf), stdin) == NULL) + exit(1); + if (strchr(buf, '\n')) + *strchr(buf, '\n') = 0; + if (strcmp(buf, "") == 0) + snprintf(buf, sizeof buf, "%s/%s", pw- |