--- /sys/man/1/ssh2 Fri Mar 30 07:47:54 2012 +++ /sys/man/1/ssh2 Fri Mar 30 00:00:00 2012 @@ -0,0 +1,767 @@ +.TH SSH2 1 +.SH NAME +ssh, sshsession, sshtun, scp, rsa2ssh2 \- Plan 9 support for SSHv2 +.SH SYNOPSIS +.B ssh +[ +.B -driIvaxkKm +] +[ +.B -l +.I user +] +[ +.B -n +.I dir +] +[ +.B -z +.I attribute=value +] +addr +[ +.I cmd +[ +.I args +]] +.PP +.B scp +[host:]file [host:]file +.br +.B scp +[host:]file ... [host:]dir +.PP +.B sshsession +[ +.B -t +] +[ +.B -n +.I namespace +] +[ +.B -R +.I dir +] +[ +.B -r +.I dir +] +[ +.B -s +.I command +] +[ +.B -S +.I srvpt +] +.PP +.B sshtun +[ +.B -9dk +] +[ +.B -m +.I mntpt +] +[ +.B -s +.I srvpt +] +.PP +.B rsa2ssh2 +[ +.I file +] +.SH DESCRIPTION +These programs collectively provide support for SSHv2 for Plan 9. +It supports only SSHv2 and will reject connections with a remote +system that supports only SSHv1. +Both client and server operation is provided. +All of the encryption, authentication, and SSH protocol are handled +by a tunnel that is presented as a protocol directory in +.B /net. +.SH "CLIENT OPERATION" +.I Ssh +and +.I scp +are the applications that provide normal client access to the +SSH tunnel. +.I Ssh +dials a remote system and runs a shell (or some other command) there. +In its simplest usage, it works like any other +.I ssh +command line application. +So the command: +.IP +.EX +ssh root@hannibal +.EE +.LP +will result in a command prompt on the machine +.B hannibal +logged in as +.B root. +To accomplish this, +.I ssh +first looks to see if there is an ssh tunnel in +.B /net, +and if not, it runs +.I sshtun +with no options. +Using the usual technique of opening the +.B clone +file and writing a +.B connect +message, +.I ssh +dials the remote ssh server and exchanges encryption keys with +the server using Diffie-Hellman key exchange. +A similar +.B clone +file and +.B connect +message protocol creates a session in the established connection. +In the course of session creation, +.I ssh +first attempts to authenticate the user with the server using +public key authentication. +If that fails, it the prompts for a password, and attempts to +authenticate with password authentication. +It also passes across the value of the environment variable +.B TERM +as would be set if +.I ssh +is run inside of +.IR vt (1). +.LP +Following the convention in other Plan 9 communications applications, +typing a control-\\ will result in a +.B >>> +prompt. +There are currently only four commands that can be issued at that +prompt: +.B c +to continue the +.I ssh +session, +.B h +to print a list of the available commands, +.B r +to toggle the supression of carriage returns, and +.B q +to close the session started by this instance of +.I ssh. +.LP +.I Ssh +can take the following command-line options: +.TP +.B -d +Increase the amount of debugging output. +.PD 0 +.TP +.B -l +A deprecated method of specifying the user name on the remote +system. +.PD 0 +.TP +.B -r +Strip carriage return characters coming from the remote system. +This will normally be desired when running in a raw +.IR rio (1) +window or from within +.IR win (1) +in +.IR acme (1). +It is normally not used when running +.I ssh +from within +.IR vt (1). +.PD 0 +.TP +.B -k +Skip the attempt to authenticate using public key authentication. +.PD 0 +.TP +.B -K +Don't fall back to password authentication. +If the public key authentication fails, +.I ssh +will exit. +.PD 0 +.TP +.B -m +Remove the special meaning of control-\\. +This is needed by +.I scp +to prevent that character in files being copied from triggering the +special command mode. +.PD 0 +.TP +.B -n +Specify the network directory of an alternate network to use. +The default is +.B /net. +.PD 0 +.TP +.B -z +Used to specify which of several possible keys to use. +.PD 0 +.TP +.B -i -I +Sets +.I ssh +to interactive +.RB ( -i ) +or non-interactive +.RI ( -I ) +mode. +This determines whether the user is prompted for a password +if none is found in factotum. +Without either of these options, +.I ssh +uses interactive mode if run in a term window. +.PD 0 +.TP +.B -v -a -x +All no-ops but included for compatibility with +.I scp. +.LP +The +.I scp +program is the orignal Plan 9 +.I scp +unchanged. +Since it does all its work by running +.I ssh +to execute an instance of +.I scp +on the server, it functions normally with this implementation of +.I ssh. +.SH "SERVER OPERATION" +The +.I sshsession +program provides the server functionality for SSHv2. +It is suitable for running by +.IR listen (1) +or +.IR listen1 (1). +Therefore, it is not normally run directly by the user. +Like +.I ssh, +it does all of its communication through the tunnel. +.I Sshsession +handles running a shell or a requested command when a remote +system requests a new connection and session. +.LP +One can run a private ssh server by first setting up the tunnel +and then running the command: +.IP +.EX +aux/listen1 -t ssh!*!2222 sshsession +.EE +.LP +Similarly, a system-wide ssh server can be run by including +a file called +.B ssh22 +in +.B /rc/bin/service.auth. +.LP +With no command-line options, +.I sshsession +runs in a way suitable for the typical ssh server. +Several aspects of its behavior can be changed, however, +via the following options: +.TP +.B -s +Specify an alternative to +.B /bin/rc +for shell sessions. +.PD 0 +.TP +.B -r +Specify a starting directory for the ssh session to run in. +.PD 0 +.TP +.B -R +Same as +.B -r +but additionally prevent any arguments on the command line to +be executed from referencing anything outside this directory. +This is mostly used to limit where +.I scp +can deposit or get files. +.PD 0 +.TP +.B -n +Specify a +.IR namespace (6) +file to be used before starting the shell or running the requested +command. +The default is +.B /lib/namespace. +.PD 0 +.TP +.B -S +Specify an alternative file in +.B /srv +where the tunnel can be found if it is not already mounted in +.B /net. +.PD 0 +.TP +.B -t +Specify that the sshsession instance is trusted and should run in the +same name space as the listen that started it. +.LP +For shell channels, +.I sshsession +will print the contents of +.B /sys/lib/motd +to the client, if that file is present. +.SH TUNNEL +.I Sshtun +is the program that implements the ssh tunnel used by +.I ssh +and +.I sshsession. +It handles all the necessary work to implement SSHv2. +The following options may be given to +.I sshtun +.TP +.B -d +Increase the amount of debugging output. +.PD 0 +.TP +.B -k +Use +.IR keyfs (4) +for password validation. +.PD 0 +.TP +.B -m +Mount point for the ssh protocol directory; defaults to +.B /net. +.PD 0 +.TP +.B -s +Name to post in +.B /srv. +If +.B -s +is not given, no file is posted to +.B /srv. +.LP +Access to the tunnel is provided though a protocol directory +.B /net/ssh. +This directory contains a set of numbered directories, each of which +is an ssh connection that is curently active or has been used in the +past. +It also serves the files +.B clone, +.B ctl, +and +.B keys. +.B Clone +behaves like the +.B clone +files in other protocol directories. +In particular, opening it reserves an ssh connection, reading from +it gets the connection number reserved, and writing to it writes +to the +.B ctl +file in the numbered connection directory. +Reading the +.B ctl +file returns the most active state of any connection. +There are currently no commands that can be written to +.B /net/ssh/ctl. +Finally, the +.B keys +file is used by +.I ssh +to relay information about keys and passwords between a user +and the tunnel. +.LP +Each of the numbered connection directories also contains +a set of numbered directories, one for each channel used on +that connection. +It also contains the files +.B clone, +.B ctl, +.B data, +.B listen, +.B local, +.B remote, +and +.B status. +Similar to the top-level +.B clone +file, opening a connection's +.B clone +file reserves a channel and gives access to its +.B ctl +file. +Reading from the +.B ctl +file gives the connection number (also the name of that +directory). +Several commands are available to write into a connection's +.B ctl +file: +.TP +.B connect +This command dials the remote system and carries out the initial +handshake to exchange versions, lists of supported algorithms, +and to establish the encryption keys to use. +.PD 0 +.TP +.B ssh-userauth +Attempt to authenticate a user with the remote system, with either +public key authentication or a password. +.PD 0 +.TP +.B ssh-connection +Currently unsupported. +.PD 0 +.TP +.B hangup +Shut down a connection and all of its channels. +.PD 0 +.TP +.B announce +Announce the tunnel's willingness to accept connection requests from +remote systems. +.PD 0 +.TP +.B accept +Do the initial connection handshake with the remote system. +.PD 0 +.TP +.B reject +Send back a connection rejection message and shut down the connection. +.LP +Because data is always carried over a channel, the connection data file +is not used for usual data. +However, reads from the connection data file do return the capability +needed for +.I sshsession +to change identity to the user logging in. +As with other protocol directories, opens on +.B listen +block until a remote system establishes a connection, at which point, +the application should write either an +.B accept +or +.B reject +message to the +.B ctl +file. +The +.B local +and +.B remote +files give the IP addresses and port numbers of the local and remote +systems. +The connection +.B status +file gives the status of the most established channel. +.LP +Each channel directory contains the files +.B ctl, +.B data, +.B listen, +.B request, +and +.B status. +As with connections, reads from channel +.B ctl +files return the channel number. +Commands that may be written to a channel +.B ctl +file include: +.TP +.B connect +Initiate the establishment of a new channel over this connection. +SSHv2 defines +.B session, +.B x11, +.B forwarded-tcpip, +and +.B direct-tcpip +channels. +The +.B connect +command defaults to a +.B session +channel if no argument is given. +(This implementation correctly handles only session channel +requests.) +.PD 0 +.TP +.B global +Reserved for future development. +In particular, this is necessary to support TCP/IP forwarding. +.PD 0 +.TP +.B hangup +Shut down a channel. +If this is the last open channel on this connection, then shut down +the connection too. +.PD 0 +.TP +.B announce +Announce the willingness to accept new channel requests from the +remote end. +.LP +The channel +.B data +file is the file over which all application data is carried. +Opens of the channel +.B listen +file block until a channel is opened by the remote end. +Unlike the connection +.B listen +file, the listening application should not write an accept or +reject message to the +.B ctl +file. +SSHv2 defines a number of out of band channel requests. +These are sent and received through the +.B request +file. +Among these are +.B pty-req, +.B x11-req, +.B env, shell, +.B exec, +.B subsystem, +.B window-change, +.B xon-xoff, +.B signal, +.B exit-status, +and +.B exit-signal. +.I Sshsession +only fully handles the +.B shell +and +.B exec +requests. +Others are either blithly acknowledged, rejected or ignored, +depending on whether they are expected to be available by +the remote system. +Finally, the channel +.B status +file gives the current status of the channel. +The possible statuses are +.B Empty, +.B Allocated, +.B Initting, +.B Listening, +.B Opening, +.B Negotiating, +.B Authing, +.B Established, +.B Eof, +.B Closing, +and +.B Closed. +.SH "CRYPTOGRAPHIC FEATURES" +During the initial connection exchange, both parties send lists of +supported algorithms. +The first algorithms listed are for key exchange. +This implementation supports the +.B diffie-hellman-group1-sha1 +and +.B diffie-hellman-group14-sha1 +key exchange algorithms. +The second list is the set of algorithms for which corresponding host +keys exist. +Both the +.B ssh-rsa +and +.B ssh-dss +algorithms are supported. +The next lists are encryption algorithms, which may be negotiated +independently for the server-to-client and client-to-server directions. +This implementation supports the +.B aes128-cbc, +.B aes192-cbc, +.B aes256-cbc, +.B 3des-cbc, +and +.B arcfour +algorithms with preference given in that order. +Finally, the message authentication code algorithms are listed, +and only the +.B hmac-sha1 +algorithm is supported here. +.SH "KEY MANAGEMENT" +There are a number of different keys that are used by the SSH tunnel. +Most of them are expected to be stored in the instance of +.IR factotum (4) +running in the name space of that tunnel instance. +However, in some cases, there are alternative locations available. +.LP +The first key needed is the host key for server operation. +In the case of the keys being stored in +.IR factotum (4), +these keys will be the first ones listed with +.B proto=rsa +and +.B proto=dss. +Alternatively, these keys can be specified in the environment +variables +.B rsakey +and +.B dsskey +or in the same named files located in the directory where +.I sshtun +is started. +.LP +The next set of keys are the public host keys used by clients to +verify the identities of servers. +As with the original Plan 9 SSH implementation, there is a system-wide +list of these in +.B /sys/lib/ssh/keyring +and each user may have a list in +.B $home/lib/keyring. +If a public key for a remote server is listed and matches the one +offered by the server, the connection proceeds. +If a public key for a remote server is listed but does not match +the one offered by the server, or +if no public key is listed for a remote server, +.I ssh +presents the key to the user and asks whether to reject the +key, accept the key only for that session, or accept the key +permanently. +The last option causes the key to be written to the user's +keyring. +In the case of a mismatching key, the accept option can +either be to add to or replace the old key. +.LP +The next key is a user's private key to be used for public key +authentication. +Currently, only RSA keys are supported for this, and the key +must be in the factotum instance running in the name space +of the tunnel instance. +Creating a key and putting it in factotum can be done by: +.IP +.EX +rsagen > key +cp key /mnt/factotum/ctl +.EE +.LP +Of course, the key file will normally be loaded when factotum +is started either by way of +.IR secstore (1) +or directly in the user's +.B lib/profile. +The following command will extract the public part of the +key and add it to the +.B authorized_keys +file on a remote UNIX system. +.IP +.EX +grep 'proto=rsa' /mnt/factotum/ctl | rsa2ssh2 | + ssh user@unix 'cat >> .ssh/authorized_keys' +.EE +.LP +The command +.IP +.EX +auth/pemdecode 'RSA PRIVATE KEY' id_rsa | auth/asn12rsa > key +.EE +.LP +will translate a private key used with OpenSSH to one suitable +for loading into factotum. +.LP +To disambiguate when a user has more than one private key stored in factotum, +the following selection criteria are applied: +.TP +1. +The selected key must have both +.B proto=rsa +and +.B !dk= +attributes present. +.PD 0 +.TP +2. +Among those keys, the attributes +.B user=, +.B sys=, +and +the attribute/value pair specified in the +.B -z +option to +.I ssh, +if any, are examined. +The value of the +.B user +attribute is expected to be the user name being authenticated on the remote +system, and the value of the +.B sys +attribute is expected to be the remote system as specified on the +.I ssh +command line. +.PD 0 +.TP +3. +The key with the greatest number of matches is selected. +Among keys with equal number of matches, the first is chosen. +.LP +An SSH server must also have a list of public keys that +can be used for public key authentication. +Again, these keys must be stored in the factotum instance running +in the name space of the server's tunnel. +Each such key must have the attributes +.B role=verify, proto=rsa, +and either +.B user= +or +.B sys=. +.LP +For password-based user authentication, +.I sshtun +can operation in one of two modes. +If given the +.B -k +option, it will validate passwords against those stored in +.B /mnt/keys +provided by +.IR keyfs (4). +If run without +.B -k, +.I sshtun +will attempt to validate passwords with an authentication +server using the +.BR auth_userpasswd (2) +call. +.SH FILES +.TP +.B /sys/lib/ssh/keyring +System-wide known host public keys. +.PD 0 +.TP +.B $home/lib/keyring +Per-user known host public keys. +.PD 0 +.TP +.B /sys/lib/motd +Message of the day file. +.SH "SEE ALSO" +RFCs 4250, 4251, 4252, 4253, 4254, and 4419, +.IR secstore (1), +.IR vt (1), +.IR factotum (4), +.IR keyfs (4), +.IR authsrv (6), +.IR listen (8), +.IR rsa (8) +.SH BUGS +TCP/IP forwarding and some potentially useful channel requests have not +been implemented. +.B Zlib +compression is not supported, also probably not needed. +Several aspects of key management still need some work. diff -Nru /sys/src/cmd/ssh2/cipher3des.c /sys/src/cmd/ssh2/cipher3des.c --- /sys/src/cmd/ssh2/cipher3des.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/cipher3des.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include <9p.h> +#include +#include "sshtun.h" + +struct CipherState +{ + DES3state state; +}; + +static CipherState* +init3des(Conn *c, int dir) +{ + CipherState *cs; + uchar key[3][8]; + + cs = emalloc9p(sizeof(CipherState)); + if(dir){ + memmove(key, c->s2cek, 24); + setupDES3state(&cs->state, key, c->s2civ); + } + else{ + memmove(key, c->c2sek, 24); + setupDES3state(&cs->state, key, c->c2siv); + } + return cs; +} + +static void +encrypt3des(CipherState *cs, uchar *buf, int nbuf) +{ + des3CBCencrypt(buf, nbuf, &cs->state); +} + +static void +decrypt3des(CipherState *cs, uchar *buf, int nbuf) +{ + des3CBCdecrypt(buf, nbuf, &cs->state); +} + +Cipher cipher3des = +{ + "3des-cbc", + 8, + init3des, + encrypt3des, + decrypt3des, +}; + diff -Nru /sys/src/cmd/ssh2/cipheraes.c /sys/src/cmd/ssh2/cipheraes.c --- /sys/src/cmd/ssh2/cipheraes.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/cipheraes.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include <9p.h> +#include +#include "sshtun.h" + +static QLock aeslock; + +struct CipherState +{ + AESstate state; +}; + +static CipherState * +initaes(Conn *c, int dir, int bits) +{ + CipherState *cs; + + qlock(&aeslock); + cs = emalloc9p(sizeof(CipherState)); + if(dir){ + setupAESstate(&cs->state, c->s2cek, bits/8, c->s2civ); + } + else{ + setupAESstate(&cs->state, c->c2sek, bits/8, c->c2siv); + } + qunlock(&aeslock); + return cs; +} + +static CipherState* +initaes128(Conn *c, int dir) +{ + return initaes(c, dir, 128); +} + +static CipherState* +initaes192(Conn *c, int dir) +{ + return initaes(c, dir, 192); +} + +static CipherState* +initaes256(Conn *c, int dir) +{ + return initaes(c, dir, 256); +} + +static void +encryptaes(CipherState *cs, uchar *buf, int nbuf) +{ + if(cs->state.setup != 0xcafebabe || cs->state.rounds > AESmaxrounds) + return; + qlock(&aeslock); + aesCBCencrypt(buf, nbuf, &cs->state); + qunlock(&aeslock); +} + +static void +decryptaes(CipherState *cs, uchar *buf, int nbuf) +{ + if(cs->state.setup != 0xcafebabe || cs->state.rounds > AESmaxrounds) + return; + qlock(&aeslock); + aesCBCdecrypt(buf, nbuf, &cs->state); + qunlock(&aeslock); +} + +Cipher cipheraes128 = +{ + "aes128-cbc", + AESbsize, + initaes128, + encryptaes, + decryptaes, +}; + +Cipher cipheraes192 = +{ + "aes192-cbc", + AESbsize, + initaes192, + encryptaes, + decryptaes, +}; + +Cipher cipheraes256 = +{ + "aes256-cbc", + AESbsize, + initaes256, + encryptaes, + decryptaes, +}; diff -Nru /sys/src/cmd/ssh2/cipherblowfish.c /sys/src/cmd/ssh2/cipherblowfish.c --- /sys/src/cmd/ssh2/cipherblowfish.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/cipherblowfish.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include <9p.h> +#include +#include "sshtun.h" + +struct CipherState +{ + BFstate state; +}; + +static CipherState* +initblowfish(Conn *c, int dir) +{ + CipherState *cs; + +int i; +fprint(2, "initblowfish dir:%d\ns2cek: ", dir); +for(i = 0; i < 16; ++i) fprint(2, "%02x", c->s2cek[i]); +fprint(2, "\nc2sek: "); +for(i = 0; i < 16; ++i) fprint(2, "%02x", c->c2sek[i]); +fprint(2, "\ns2civ: "); +for(i = 0; i < 8; ++i) fprint(2, "%02x", c->s2civ[i]); +fprint(2, "\nc2siv: "); +for(i = 0; i < 8; ++i) fprint(2, "%02x", c->c2siv[i]); +fprint(2, "\n"); + + cs = emalloc9p(sizeof(CipherState)); +memset(cs, '\0', sizeof(CipherState)); +fprint(2,"cs:%p\n", cs); + if(dir) + setupBFstate(&cs->state, c->s2cek, 16, c->s2civ); + else + setupBFstate(&cs->state, c->c2sek, 16, c->c2siv); + return cs; +} + +static void +encryptblowfish(CipherState *cs, uchar *buf, int nbuf) +{ + bfCBCencrypt(buf, nbuf, &cs->state); +} + +static void +decryptblowfish(CipherState *cs, uchar *buf, int nbuf) +{ +fprint(2,"cs:%p, nb:%d\n", cs, nbuf); +fprint(2, "before decrypt: %02ux %02ux %02ux %02ux\n", buf[0], buf[1], buf[2], buf[3]); + bfCBCdecrypt(buf, nbuf, &cs->state); +fprint(2, "after decrypt: %02ux %02ux %02ux %02ux\n", buf[0], buf[1], buf[2], buf[3]); +} + +Cipher cipherblowfish = +{ + "blowfish-cbc", + 8, + initblowfish, + encryptblowfish, + decryptblowfish, +}; diff -Nru /sys/src/cmd/ssh2/cipherrc4.c /sys/src/cmd/ssh2/cipherrc4.c --- /sys/src/cmd/ssh2/cipherrc4.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/cipherrc4.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include <9p.h> +#include +#include "sshtun.h" + +struct CipherState +{ + RC4state state; +}; + +static CipherState* +initrc4(Conn *c, int dir) +{ + CipherState *cs; + + cs = emalloc9p(sizeof(CipherState)); + if(dir) + setupRC4state(&cs->state, c->s2cek, 16); + else + setupRC4state(&cs->state, c->c2sek, 16); + return cs; +} + +static void +encryptrc4(CipherState *cs, uchar *buf, int nbuf) +{ + rc4(&cs->state, buf, nbuf); +} + +static void +decryptrc4(CipherState *cs, uchar *buf, int nbuf) +{ + rc4(&cs->state, buf, nbuf); +} + +Cipher cipherrc4 = +{ + "arcfour", + 8, + initrc4, + encryptrc4, + decryptrc4, +}; + diff -Nru /sys/src/cmd/ssh2/dh.c /sys/src/cmd/ssh2/dh.c --- /sys/src/cmd/ssh2/dh.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/dh.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,915 @@ +#include +#include +#include +#include +#include +#include +#include <9p.h> +#include +#include +#include +#include "sshtun.h" + +static int dh_server(Conn *, Packet *, mpint *, int); +static void genkeys(Conn *, uchar [], mpint *); + +/* + * Second Oakley Group from RFC 2409 + */ +static char *group1p = + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" + "FFFFFFFFFFFFFFFF"; + +/* + * 2048-bit MODP group (id 14) from RFC 3526 +*/ +static char *group14p = + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AACAA68FFFFFFFFFFFFFFFF"; + + +mpint *two, *p1, *p14; +static DSApriv mydsskey; +static RSApriv myrsakey; + +void +dh_init(PKA *pkas[]) +{ + char *p, *st, *end; + int fd, n, k; + char *buf; + + buf = emalloc9p(4096); + k = 0; + pkas[k] = nil; + st = buf; + end = buf + 4096; + fmtinstall('M', mpfmt); + two = strtomp("2", nil, 10, nil); + p1 = strtomp(group1p, nil, 16, nil); + p14 = strtomp(group14p, nil, 16, nil); + /* + * this really should be done through factotum + */ + p = getenv("rsakey"); + if (p == nil) { + fd = open("rsakey", OREAD); + if (fd < 0) { + fd = open("/mnt/factotum/ctl", OREAD); + if (fd < 0) + goto initdss; + } + n = read(fd, buf, 4095); + buf[n] = 0; + close(fd); + st = strstr(buf, "proto=rsa"); + if (st == nil) + goto initdss; + end = st; + for (; st > buf && *st != '\n'; --st) ; + for (; end < buf+4096 && *end != '\n'; ++end) ; + } + else { + strncpy(buf, p, 4095); + remove("/env/rsakey"); + } + p = strstr(st, " n="); + if (p == nil || p > end) { + fprint(2, "No key (n) found\n"); + free(buf); + return; + } + myrsakey.pub.n = strtomp(p+3, nil, 16, nil); + if (debug > 1) + fprint(2, "n=%M\n", myrsakey.pub.n); + p = strstr(st, " ek="); + if (p == nil || p > end) { + fprint(2, "No key (ek) found\n"); + free(buf); + return; + } + pkas[k++] = &rsa_pka; + pkas[k] = nil; + myrsakey.pub.ek = strtomp(p+4, nil, 16, nil); + if (debug > 1) + fprint(2, "ek=%M\n", myrsakey.pub.ek); + p = strstr(st, " !dk="); + if (p == nil) { + p = strstr(st, "!dk?"); + if (p == nil || p > end) { + // fprint(2, "No key (dk) found\n"); + free(buf); + return; + } + else + goto initdss; + } + myrsakey.dk = strtomp(p+5, nil, 16, nil); + if (debug > 1) + fprint(2, "dk=%M\n", myrsakey.dk); + +initdss: + p = getenv("dsskey"); + if (p == nil) { + fd = open("dsskey", OREAD); + if (fd < 0) { + fd = open("/mnt/factotum/ctl", OREAD); + if (fd < 0) + goto initdss; + } + n = read(fd, buf, 4095); + buf[n] = 0; + close(fd); + st = strstr(buf, "proto=dsa"); + if (st == nil) { + free(buf); + return; + } + end = st; + for (; st > buf && *st != '\n'; --st) ; + for (; end < buf+4096 && *end != '\n'; ++end) ; + } + else { + strncpy(buf, p, 4095); + remove("/env/dsskey"); + } + p = strstr(buf, " p="); + if (p == nil || p > end) { + fprint(2, "No key (p) found\n"); + free(buf); + return; + } + mydsskey.pub.p = strtomp(p+3, nil, 16, nil); + p = strstr(buf, " q="); + if (p == nil || p > end) { + fprint(2, "No key (q) found\n"); + free(buf); + return; + } + mydsskey.pub.q = strtomp(p+3, nil, 16, nil); + p = strstr(buf, " alpha="); + if (p == nil || p > end) { + fprint(2, "No key (g) found\n"); + free(buf); + return; + } + mydsskey.pub.alpha = strtomp(p+7, nil, 16, nil); + p = strstr(buf, " key="); + if (p == nil || p > end) { + fprint(2, "No key (y) found\n"); + free(buf); + return; + } + mydsskey.pub.key = strtomp(p+5, nil, 16, nil); + pkas[k++] = &dss_pka; + pkas[k] = nil; + p = strstr(buf, " !secret="); + if (p == nil) { + p = strstr(buf, "!secret?"); + if (p == nil || p > end) + fprint(2, "No key (x) found\n"); + free(buf); + return; + } + mydsskey.secret = strtomp(p+9, nil, 16, nil); + free(buf); +} + +static Packet * +rsa_ks(Conn *c) +{ + Packet *ks; + + if (myrsakey.pub.ek == nil || myrsakey.pub.n == nil) { + fprint(2, "No public RSA key info\n"); + return nil; + } + ks = new_packet(c); + add_string(ks, "ssh-rsa"); + add_mp(ks, myrsakey.pub.ek); + add_mp(ks, myrsakey.pub.n); + return ks; +} + +static void +esma_encode(uchar *h, uchar *em, int nb) +{ + int n, i; + uchar hh[SHA1dlen]; + static uchar sha1der[] = {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, + 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}; + + sha1(h, SHA1dlen, hh, nil); + n = nb - (15 + SHA1dlen) - 3; + i = 0; + em[i++] = 0; + em[i++] = 1; + memset(em + i, 0xff, n); + i += n; + em[i++] = 0; + memmove(em + i, sha1der, sizeof(sha1der)); + i += sizeof(sha1der); + memmove(em + i, hh, SHA1dlen); +} + +static Packet * +rsa_sign(Conn *c, uchar *m, int nm) +{ + AuthRpc *ar; + Packet *sig; + mpint *s, *mm; + int fd, n, nbit; + uchar hh[SHA1dlen]; + uchar *sstr, *em; + + if (myrsakey.dk) { + nbit = mpsignif (myrsakey.pub.n); + n = (nbit + 7) / 8; + sstr = emalloc9p(n); + em = emalloc9p(n); + /* Compute s: RFC 3447 */ + esma_encode(m, em, n); + mm = betomp(em, n, nil); + s = mpnew(nbit); + mpexp(mm, myrsakey.dk, myrsakey.pub.n, s); + mptobe(s, sstr, n, nil); + mpfree(mm); + mpfree(s); + free(em); + } + else { + fd = open("/mnt/factotum/rpc", ORDWR); + if (fd < 0) + return nil; + sha1(m, nm, hh, nil); + ar = auth_allocrpc(fd); + if (ar == nil || auth_rpc(ar, "start", "role=sign proto=rsa", 19) != ARok + || auth_rpc(ar, "write", hh, SHA1dlen) != ARok + || auth_rpc(ar, "read", nil, 0) != ARok) { + if (debug) + fprint(2, "got error in factotum: %r\n"); + auth_freerpc(ar); + close(fd); + return nil; + } + close(fd); + if (ar->arg == nil) + return nil; + sstr = emalloc9p(ar->narg); + memmove(sstr, ar->arg, ar->narg); + n = ar->narg; + auth_freerpc(ar); + } + sig = new_packet(c); + add_string(sig, pkas[c->pkalg]->name); + add_block(sig, sstr, n); + free(sstr); + return sig; +} + +/* + * 0 - If factotum failed, e.g. no key + * 1 - If key is verified + * -1 - If factotum found a key, but the verification fails + */ +static int +rsa_verify(Conn *c, uchar *m, int nm, char *user, char *sig, int) +{ + AuthRpc *ar; +// mpint *s, *mm; + char *p; + int fd, n, retval; +// int nbit; + uchar hh[SHA1dlen]; +// uchar *sstr, *em; + char *sigblob; + char *buf; + + sigblob = emalloc9p(512); + buf = emalloc9p(256); + if (debug) + fprint(2, "In rsa_verify for connection: %d\n", c->id); +#ifdef UNDEF + if (rsa_exponent) { + nbit = mpsignif (host_modulus); + n = (nbit + 7) / 8; + em = emalloc9p(n); + /* Compute s: RFC 3447 */ + esma_encode(m, em, n); + mm = betomp(em, n, nil); + s = mpnew(1024); + mpexp(mm, rsa_exponent, host_modulus, s); + sstr = emalloc9p(n); + mptobe(s, sstr, n, nil); + free(em); + mpfree(mm); + mpfree(s); + retval = memcmp(sig, sstr, n); + free(sstr); + free(sigblob); + free(buf); + if (retval == 0) + return 1; + return 0; + } + else { +#endif + retval = 1; + fd = open("/mnt/factotum/rpc", ORDWR); + if (fd < 0) { + if (debug) + fprint(2, "Could not open factotum RPC: %r\n"); + free(sigblob); + free(buf); + return 0; + } + p = (char *)get_string(nil, (uchar *)sig, buf, 256, nil); + get_string(nil, (uchar *)p, sigblob, 512, &n); + sha1(m, nm, hh, nil); + if (user != nil) + p = smprint("role=verify proto=rsa user=%s", user); + else + p = smprint("role=verify proto=rsa sys=%s", c->remote); + ar = auth_allocrpc(fd); + if (ar == nil || auth_rpc(ar, "start", p, strlen(p)) != ARok + || auth_rpc(ar, "write", hh, SHA1dlen) != ARok + || auth_rpc(ar, "write", sigblob, n) != ARok + || auth_rpc(ar, "read", nil, 0) != ARok) { + if (debug) + fprint(2, "got error in factotum: %r\n"); + auth_freerpc(ar); + free(p); + close(fd); + free(sigblob); + free(buf); + return 0; + } + if (debug) + fprint(2, "Factotum returned %s\n", ar->ibuf); + if (strstr(ar->ibuf, "does not verify")) + retval = -1; + if (ar != nil) + auth_freerpc(ar); + free(p); + close(fd); + free(sigblob); + free(buf); + return retval; +#ifdef UNDEF + } +#endif +} + +static Packet * +dss_ks(Conn *c) +{ + Packet *ks; + + if (mydsskey.pub.p == nil) + return nil; + ks = new_packet(c); + add_string(ks, "ssh-dss"); + add_mp(ks, mydsskey.pub.p); + add_mp(ks, mydsskey.pub.q); + add_mp(ks, mydsskey.pub.alpha); + add_mp(ks, mydsskey.pub.key); + return ks; +} + +static Packet * +dss_sign(Conn *c, uchar *m, int nm) +{ + AuthRpc *ar; + DSAsig *s; + Packet *sig; + mpint *mm; + int fd; + uchar sstr[2*SHA1dlen]; + + sha1(m, nm, sstr, nil); + sig = new_packet(c); + add_string(sig, pkas[c->pkalg]->name); + if (mydsskey.secret) { + mm = betomp(sstr, SHA1dlen, nil); + s = dsasign(&mydsskey, mm); + mptobe(s->r, sstr, SHA1dlen, nil); + mptobe(s->s, sstr+SHA1dlen, SHA1dlen, nil); + dsasigfree(s); + mpfree(mm); + } + else { + fd = open("/mnt/factotum/rpc", ORDWR); + if (fd < 0) + return nil; + ar = auth_allocrpc(fd); + if (ar == nil || auth_rpc(ar, "start", "role=sign proto=dsa", 19) != ARok + || auth_rpc(ar, "write", sstr, SHA1dlen) != ARok + || auth_rpc(ar, "read", nil, 0) != ARok) { + if (debug) + fprint(2, "got error in factotum: %r\n"); + auth_freerpc(ar); + close(fd); + return nil; + } + close(fd); + memmove(sstr, ar->arg, ar->narg); + auth_freerpc(ar); + } + add_block(sig, sstr, 2*SHA1dlen); + return sig; +} + +static int +dss_verify(Conn *c, uchar *m, int nm, char *user, char *sig, int nsig) +{ + if (debug) + fprint(2, "In dss_verify for connection: %d\n", c->id); + USED(c); + USED(m); + USED(nm); + USED(user); + USED(sig); + USED(nsig); + return 0; +} + +static int +dh_server1(Conn *c, Packet *pack1) +{ + return dh_server(c, pack1, p1, 1024); +} + +static int +dh_server14(Conn *c, Packet *pack1) +{ + return dh_server(c, pack1, p14, 2048); +} + +static int +dh_server(Conn *c, Packet *pack1, mpint *grp, int nbit) +{ + Packet *pack2, *ks, *sig; + mpint *y, *e, *f, *k; + int n; + uchar h[SHA1dlen]; + + qlock(&c->l); + f = mpnew(nbit); + k = mpnew(nbit); + /* Compute f: RFC4253 */ + y = mprand(nbit / 8, genrandom, nil); + if (debug > 1) + fprint(2, "y=%M\n", y); + mpexp(two, y, grp, f); + if (debug > 1) + fprint(2, "f=%M\n", f); + /* Compute k: RFC4253 */ + if (debug > 1) + dump_packet(pack1); + e = get_mp(pack1->payload+1); + if (debug > 1) + fprint(2, "e=%M\n", e); + mpexp(e, y, grp, k); + if (debug > 1) + fprint(2, "k=%M\n", k); + /* Compute H: RFC 4253 */ + pack2 = new_packet(c); + if (debug) + fprint(2, "ID strings: %s---%s\n", c->otherid, MYID); + add_string(pack2, c->otherid); + add_string(pack2, MYID); + if (debug > 1) { + fprint(2, "received kexinit:"); + dump_packet(c->rkexinit); + fprint(2, "\nsent kexinit:"); + dump_packet(c->skexinit); + } + add_block(pack2, c->rkexinit->payload, c->rkexinit->rlength - 1); + add_block(pack2, c->skexinit->payload, c->skexinit->rlength - c->skexinit->pad_len - 1); + ks = pkas[c->pkalg]->ks(c); + if (ks == nil) { + free(pack2); + mpfree(y); + mpfree(e); + mpfree(f); + mpfree(k); + return -1; + } + add_block(pack2, ks->payload, ks->rlength - 1); + add_mp(pack2, e); + add_mp(pack2, f); + add_mp(pack2, k); + sha1(pack2->payload, pack2->rlength - 1, h, nil); + if (c->got_sessid == 0) { + memmove(c->sessid, h, SHA1dlen); + c->got_sessid = 1; + } + sig = pkas[c->pkalg]->sign(c, h, SHA1dlen); + if (sig == nil) { + fprint(2, "Failed to generate signature\n"); + mpfree(f); + mpfree(e); + mpfree(k); + mpfree(y); + free(sig); + free(ks); + free(pack2); + qunlock(&c->l); + return -1; + } + /* Send (K_s || f || s) to client: RFC4253 */ + init_packet(pack2); + pack2->c = c; + add_byte(pack2, SSH_MSG_KEXDH_REPLY); + add_block(pack2, ks->payload, ks->rlength - 1); + add_mp(pack2, f); + add_block(pack2, sig->payload, sig->rlength - 1); + if (debug) + dump_packet(pack2); + n = finish_packet(pack2); + if (debug > 1) { + fprint(2, "Writing %d bytes: len:%d\n", n, nhgetl(pack2->nlength)); + dump_packet(pack2); + } + iowrite(c->dio, c->datafd, pack2->nlength, n); + + genkeys(c, h, k); + + /* Send SSH_MSG_NEWKEYS */ + init_packet(pack2); + pack2->c = c; + add_byte(pack2, SSH_MSG_NEWKEYS); + n = finish_packet(pack2); + iowrite(c->dio, c->datafd, pack2->nlength, n); + + mpfree(f); + mpfree(e); + mpfree(k); + mpfree(y); + free(sig); + free(ks); + free(pack2); + qunlock(&c->l); + return 0; +} + +static int +dh_client11(Conn *c, Packet *) +{ + Packet *p; + int n; + + if (c->e) + mpfree(c->e); + c->e = mpnew(1024); + /* Compute e: RFC4253 */ + if (c->x) + mpfree(c->x); + c->x = mprand(128, genrandom, nil); + mpexp(two, c->x, p1, c->e); + p = new_packet(c); + add_byte(p, SSH_MSG_KEXDH_INIT); + add_mp(p, c->e); + n = finish_packet(p); + iowrite(c->dio, c->datafd, p->nlength, n); + free(p); + return 0; +} + +static int +dh_client12(Conn *c, Packet *p) +{ + Packet *ks, *sig, *pack2; + RSApub *srvkey; + mpint *f, *k; + char *newkey, *r, *home; + uchar *q; + int n, fd, retval; + uchar h[SHA1dlen]; + char buf[10]; + + ks = new_packet(c); + sig = new_packet(c); + pack2 = new_packet(c); + q = get_string(p, p->payload+1, (char *)ks->payload, 35000, &n); + ks->rlength = n + 1; + f = get_mp(q); + q += nhgetl(q) + 4; + get_string(p, q, (char *)sig->payload, 35000, &n); + sig->rlength = n; + k = mpnew(1024); + mpexp(f, c->x, p1, k); + /* Compute H: RFC 4253 */ + init_packet(pack2); + pack2->c = c; + if (debug > 1) + fprint(2, "ID strings: %s---%s\n", c->otherid, MYID); + add_string(pack2, MYID); + add_string(pack2, c->otherid); + if (debug > 1) { + fprint(2, "received kexinit:"); + dump_packet(c->rkexinit); + fprint(2, "\nsent kexinit:"); + dump_packet(c->skexinit); + } + add_block(pack2, c->skexinit->payload, c->skexinit->rlength - c->skexinit->pad_len - 1); + add_block(pack2, c->rkexinit->payload, c->rkexinit->rlength - 1); + add_block(pack2, ks->payload, ks->rlength - 1); + add_mp(pack2, c->e); + add_mp(pack2, f); + add_mp(pack2, k); + sha1(pack2->payload, pack2->rlength - 1, h, nil); + mpfree(f); + if (c->got_sessid == 0) { + memmove(c->sessid, h, SHA1dlen); + c->got_sessid = 1; + } + if (debug) + fprint(2, "Verifying server signature\n"); + q = get_string(ks, ks->payload, buf, 10, nil); + srvkey = emalloc9p(sizeof (RSApub)); + srvkey->ek = get_mp(q); + q += nhgetl(q) + 4; + srvkey->n = get_mp(q); + retval = 0; + if (findkey("/sys/lib/ssh/keyring", c->remote, srvkey) != KeyOk) { + home = getenv("home"); + if (home == nil) { + newkey = "No home directory for key file"; + if (keymbox.msg) + free(keymbox.msg); + keymbox.msg = smprint("b%04ld%s", strlen(newkey), newkey); + nbsendul(keymbox.mchan, 1); + mpfree(srvkey->ek); + mpfree(srvkey->n); + mpfree(k); + free(ks); + free(sig); + free(pack2); + free(srvkey); + return -1; + } + r = smprint("%s/lib/keyring", home); + free(home); + if ((n = findkey(r, c->remote, srvkey)) != KeyOk) { + newkey = smprint("ek=%M n=%M", srvkey->ek, srvkey->n); + if (keymbox.msg) + free(keymbox.msg); + if (n == NoKeyFile || n == NoKey) + keymbox.msg = smprint("c%04ld%s", strlen(newkey), newkey); + else + keymbox.msg = smprint("b%04ld%s", strlen(newkey), newkey); + free(newkey); + nbsendul(keymbox.mchan, 1); + recvul(keymbox.mchan); + if (keymbox.msg == nil || keymbox.msg[0] == 'n') { + free(keymbox.msg); + keymbox.msg = nil; + newkey = "Server key reject"; + keymbox.msg = smprint("f%04ld%s", strlen(newkey), newkey); + nbsendul(keymbox.mchan, 1); + free(r); + mpfree(k); + mpfree(srvkey->ek); + mpfree(srvkey->n); + free(ks); + free(sig); + free(pack2); + free(srvkey); + return -1; + } + else { + if (debug) + fprint(2, "Adding key\n"); + if (keymbox.msg[0] == 'y') + appendkey(r, c->remote, srvkey); + else if (keymbox.msg[0] == 'r') + replacekey(r, c->remote, srvkey); + } + } + free(r); + } + newkey = smprint("key proto=rsa role=verify sys=%s size=%d ek=%M n=%M", + c->remote, mpsignif (srvkey->n), srvkey->ek, srvkey->n); + fd = open("/mnt/factotum/ctl", OWRITE); + if (fd >= 0) { + write(fd, newkey, strlen(newkey)); + close(fd); + } + else + if (debug) + fprint(2, "Factotum open failed: %r\n"); + free(newkey); + mpfree(srvkey->ek); + mpfree(srvkey->n); + if (keymbox.msg) + free(keymbox.msg); + keymbox.msg = nil; + n = pkas[c->pkalg]->verify(c, h, SHA1dlen, nil, (char *)sig->payload, sig->rlength); + newkey = smprint("delkey proto=rsa role=verify sys=%s", c->remote); + fd = open("/mnt/factotum/ctl", OWRITE); + if (fd >= 0) { + write(fd, newkey, strlen(newkey)); + close(fd); + } + free(newkey); + switch (n) { + case -1: + newkey = "Signature verifcation failed"; + keymbox.msg = smprint("f%04ld%s", strlen(newkey), newkey); + retval = -1; + break; + case 1: + keymbox.msg = smprint("o0000"); + break; + case 0: + newkey = "Key verification dialog failed"; + keymbox.msg = smprint("f%04ld%s", strlen(newkey), newkey); + retval = -1; + break; + } + nbsendul(keymbox.mchan, 1); + if (retval == 0) + genkeys(c, h, k); + mpfree(k); + free(ks); + free(sig); + free(pack2); + free(srvkey); + return retval; +} + +static int +dh_client141(Conn *c, Packet *) +{ + Packet *p; + mpint *e, *x; + int n; + + e = mpnew(2048); + /* Compute e: RFC4253 */ + x = mprand(256, genrandom, nil); + mpexp(two, x, p14, e); + p = new_packet(c); + add_byte(p, SSH_MSG_KEXDH_INIT); + add_mp(p, e); + n = finish_packet(p); + iowrite(c->dio, c->datafd, p->nlength, n); + free(p); + mpfree(e); + mpfree(x); + return 0; +} + +static int +dh_client142(Conn *, Packet *) +{ + return 0; +} + +static void +genkeys(Conn *c, uchar h[], mpint *k) +{ + Packet *pack2; + char buf[82], *bp, *be; + int n; + + pack2 = new_packet(c); + /* Compute 40 bytes (320 bits) of keys: each alg can use what it needs */ + /* Client to server IV */ + if (debug > 1) { + fprint(2, "k=%M\nh=", k); + for (n = 0; n < SHA1dlen; ++n) fprint(2, "%02ux", h[n]); + fprint(2, "\nsessid="); + for (n = 0; n < SHA1dlen; ++n) fprint(2, "%02ux", c->sessid[n]); + fprint(2, "\n"); + } + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_byte(pack2, 'A'); + add_packet(pack2, c->sessid, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->nc2siv, nil); + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_packet(pack2, c->nc2siv, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->nc2siv + SHA1dlen, nil); + /* Server to client IV */ + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_byte(pack2, 'B'); + add_packet(pack2, c->sessid, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->ns2civ, nil); + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_packet(pack2, c->ns2civ, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->ns2civ + SHA1dlen, nil); + /* Client to server encryption key */ + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_byte(pack2, 'C'); + add_packet(pack2, c->sessid, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->nc2sek, nil); + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_packet(pack2, c->nc2sek, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->nc2sek + SHA1dlen, nil); + /* Server to client encryption key */ + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_byte(pack2, 'D'); + add_packet(pack2, c->sessid, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->ns2cek, nil); + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_packet(pack2, c->ns2cek, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->ns2cek + SHA1dlen, nil); + /* Client to server integrity key */ + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_byte(pack2, 'E'); + add_packet(pack2, c->sessid, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->nc2sik, nil); + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_packet(pack2, c->nc2sik, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->nc2sik + SHA1dlen, nil); + /* Server to client integrity key */ + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_byte(pack2, 'F'); + add_packet(pack2, c->sessid, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->ns2cik, nil); + init_packet(pack2); + add_mp(pack2, k); + add_packet(pack2, h, SHA1dlen); + add_packet(pack2, c->ns2cik, SHA1dlen); + sha1(pack2->payload, pack2->rlength - 1, c->ns2cik + SHA1dlen, nil); + if (debug > 1) { + be = buf + 82; + fprint(2, "Client to server IV:\n"); + for (n = 0, bp = buf; n < SHA1dlen*2; ++n) bp = seprint(bp, be, "%02x", c->nc2siv[n]); + fprint(2, "%s\n", buf); + fprint(2, "Server to client IV:\n"); + for (n = 0, bp = buf; n < SHA1dlen*2; ++n) bp = seprint(bp, be, "%02x", c->ns2civ[n]); + fprint(2, "%s\n", buf); + fprint(2, "Client to server EK:\n"); + for (n = 0, bp = buf; n < SHA1dlen*2; ++n) bp = seprint(bp, be, "%02x", c->nc2sek[n]); + fprint(2, "%s\n", buf); + fprint(2, "Server to client EK:\n"); + for (n = 0, bp = buf; n < SHA1dlen*2; ++n) bp = seprint(bp, be, "%02x", c->ns2cek[n]); + fprint(2, "%s\n", buf); + } + free(pack2); +} + +Kex dh1sha1 = { + "diffie-hellman-group1-sha1", + dh_server1, + dh_client11, + dh_client12 +}; + +Kex dh14sha1 = { + "diffie-hellman-group14-sha1", + dh_server14, + dh_client141, + dh_client142 +}; + +PKA rsa_pka = { + "ssh-rsa", + rsa_ks, + rsa_sign, + rsa_verify +}; + +PKA dss_pka = { + "ssh-dss", + dss_ks, + dss_sign, + dss_verify +}; diff -Nru /sys/src/cmd/ssh2/dial.c /sys/src/cmd/ssh2/dial.c --- /sys/src/cmd/ssh2/dial.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/dial.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,224 @@ +#include +#include +#include + +typedef struct DS DS; + +static int call(char*, char*, DS*); +static int csdial(DS*); +static void _dial_string_parse(char*, DS*); + +enum +{ + Maxstring = 128, + Maxpath = 256, +}; + +struct DS { + /* dist string */ + char buf[Maxstring]; + char *netdir; + char *proto; + char *rem; + + /* other args */ + char *local; + char *dir; + int *cfdp; +}; + + +/* + * the dialstring is of the form '[/net/]proto!dest' + */ +int +dial(char *dest, char *local, char *dir, int *cfdp) +{ + DS ds; + int rv; + char err[ERRMAX], alterr[ERRMAX]; + + ds.local = local; + ds.dir = dir; + ds.cfdp = cfdp; + + _dial_string_parse(dest, &ds); + if(ds.netdir) + return csdial(&ds); + + ds.netdir = "/net"; + rv = csdial(&ds); + if(rv >= 0) + return rv; + err[0] = '\0'; + errstr(err, sizeof err); + if(strstr(err, "refused") != 0){ + werrstr("%s", err); + return rv; + } + ds.netdir = "/net.alt"; + rv = csdial(&ds); + if(rv >= 0) + return rv; + + alterr[0] = 0; + errstr(alterr, sizeof alterr); + if(strstr(alterr, "translate") || strstr(alterr, "does not exist")) + werrstr("%s", err); + else + werrstr("%s", alterr); + return rv; +} + +static int +csdial(DS *ds) +{ + int n, fd, rv; + char *p, buf[Maxstring], clone[Maxpath], err[ERRMAX], besterr[ERRMAX]; + + /* + * open connection server + */ + snprint(buf, sizeof(buf), "%s/cs", ds->netdir); + fd = open(buf, ORDWR); + if(fd < 0){ + /* no connection server, don't translate */ + snprint(clone, sizeof(clone), "%s/%s/clone", ds->netdir, ds->proto); + return call(clone, ds->rem, ds); + } + + /* + * ask connection server to translate + */ + snprint(buf, sizeof(buf), "%s!%s", ds->proto, ds->rem); + if(write(fd, buf, strlen(buf)) < 0){ + close(fd); + return -1; + } + + /* + * loop through each address from the connection server till + * we get one that works. + */ + *besterr = 0; + rv = -1; + seek(fd, 0, 0); + while((n = read(fd, buf, sizeof(buf) - 1)) > 0){ + buf[n] = 0; + p = strchr(buf, ' '); + if(p == 0) + continue; + *p++ = 0; + rv = call(buf, p, ds); + if(rv >= 0) + break; + err[0] = '\0'; + errstr(err, sizeof err); + if(strstr(err, "does not exist") == 0) + strcpy(besterr, err); + } + close(fd); + + if(rv < 0 && *besterr) + werrstr("%s", besterr); + else + werrstr("%s", err); + return rv; +} + +static int +call(char *clone, char *dest, DS *ds) +{ + int fd, cfd, n; + char cname[Maxpath], name[Maxpath], data[Maxpath], *p; + + /* because cs is in a different name space, replace the mount point */ + if(*clone == '/'){ + p = strchr(clone+1, '/'); + if(p == nil) + p = clone; + else + p++; + } else + p = clone; + snprint(cname, sizeof cname, "%s/%s", ds->netdir, p); + + cfd = open(cname, ORDWR); + if(cfd < 0) + return -1; + + /* get directory name */ + n = read(cfd, name, sizeof(name)-1); + if(n < 0){ + close(cfd); + return -1; + } + name[n] = 0; + for(p = name; *p == ' '; p++) + ; + snprint(name, sizeof(name), "%ld", strtoul(p, 0, 0)); + p = strrchr(cname, '/'); + *p = 0; + if(ds->dir) + snprint(ds->dir, NETPATHLEN, "%s/%s", cname, name); + snprint(data, sizeof(data), "%s/%s/data", cname, name); + + /* connect */ + if(ds->local) + snprint(name, sizeof(name), "connect %s %s", dest, ds->local); + else + snprint(name, sizeof(name), "connect %s", dest); + if(write(cfd, name, strlen(name)) < 0){ + close(cfd); + return -1; + } + + /* open data connection */ + fd = open(data, ORDWR); + if(fd < 0){ + close(cfd); + return -1; + } + if(ds->cfdp) + *ds->cfdp = cfd; + else + close(cfd); + return fd; +} + +/* + * parse a dial string + */ +static void +_dial_string_parse(char *str, DS *ds) +{ + char *p, *p2; + int nondig; + + strncpy(ds->buf, str, Maxstring); + ds->buf[Maxstring-1] = 0; + + p = strchr(ds->buf, '!'); + if(p == 0) { + ds->netdir = 0; + ds->proto = "net"; + ds->rem = ds->buf; + } else { + if(*ds->buf != '/' && *ds->buf != '#'){ + ds->netdir = 0; + ds->proto = ds->buf; + } else { + nondig = 0; + do { + for(p2 = ds->buf+1; *p2 != '/'; p2++) + if(!isdigit(*p2)) + nondig=1; + } while(!nondig); + *p2++ = 0; + ds->netdir = ds->buf; + ds->proto = p2; + } + *p = 0; + ds->rem = p + 1; + } +} diff -Nru /sys/src/cmd/ssh2/mkfile /sys/src/cmd/ssh2/mkfile --- /sys/src/cmd/ssh2/mkfile Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/mkfile Fri Mar 30 00:00:00 2012 @@ -0,0 +1,48 @@ + +#include +#include +#include +#include +#include +#include +#include <9p.h> +#include "sshtun.h" + +static int +parsepubkey(char *s, RSApub *key, char **sp, int base) +{ + int n; + char *host, *p, *z; + + z = nil; + n = strtoul(s, &p, 10); + host = nil; + if(n < 256 || !isspace(*p)){ /* maybe this is a host name */ + host = s; + s = strpbrk(s, " \t"); + if(s == nil) + return -1; + z = s; + *s++ = '\0'; + s += strspn(s, " \t"); + + n = strtoul(s, &p, 10); + if(n < 256 || !isspace(*p)){ + if(z) + *z = ' '; + return -1; + } + } + + if((key->ek = strtomp(p, &p, base, nil)) == nil + || (key->n = strtomp(p, &p, base, nil)) == nil + || (*p != '\0' && !isspace(*p)) + || mpsignif(key->n) < 256){ /* 256 is just a sanity check */ + mpfree(key->ek); + mpfree(key->n); + key->ek = nil; + key->n = nil; + if(z) + *z = ' '; + return -1; + } + if(host == nil){ + if(*p != '\0'){ + p += strspn(p, " \t"); + if(*p != '\0'){ + host = emalloc9p(strlen(p)+1); + strcpy(host, p); + } + } + free(s); + } + *sp = host; + return 0; +} + +RSApub* +readpublickey(Biobuf *b, char **sp) +{ + char *s; + RSApub *key; + + key = emalloc9p(sizeof(RSApub)); + if(key == nil) + return nil; + + for(;;){ + if((s = Brdstr(b, '\n', 1)) == nil){ + free(key); + return nil; + } + if(s[0]=='#'){ + free(s); + continue; + } + if(parsepubkey(s, key, sp, 10)==0 + || parsepubkey(s, key, sp, 16)==0) + return key; + fprint(2, "warning: skipping line '%s'; cannot parse\n", s); + free(s); + } +} + +static int +match(char *pattern, char *aliases) +{ + char *s, *snext; + char *a, *anext, *ae; + + for(s=pattern; s && *s; s=snext){ + if((snext=strchr(s, ',')) != nil) + *snext++ = '\0'; + for(a=aliases; a && *a; a=anext){ + if((anext=strchr(a, ',')) != nil){ + ae = anext; + anext++; + }else + ae = a+strlen(a); + if(ae-a == strlen(s) && memcmp(s, a, ae-a)==0) + return 0; + } + } + return 1; +} + +int +findkey(char *keyfile, char *host, RSApub *key) +{ + char *h; + Biobuf *b; + RSApub *k; + int res; + + if ((b = Bopen(keyfile, OREAD)) == nil) + return NoKeyFile; + + for (res = NoKey; res != KeyOk;) { + if ((k = readpublickey(b, &h)) == nil) + break; + if (match(h, host) == 0) { + if (mpcmp(k->n, key->n) == 0 && mpcmp(k->ek, key->ek) == 0) + res = KeyOk; + else + res = KeyWrong; + } + free(h); + free(k->ek); + free(k->n); + free(k); + } + Bterm(b); + return res; +} + +int +replacekey(char *keyfile, char *host, RSApub *hostkey) +{ + char *h, *nkey, *p; + Biobuf *br, *bw; + Dir *d, nd; + RSApub *k; + + nkey = smprint("%s.new", keyfile); + if(nkey == nil) + return -1; + + if((br = Bopen(keyfile, OREAD)) == nil){ + free(nkey); + return -1; + } + if((bw = Bopen(nkey, OWRITE)) == nil){ + Bterm(br); + free(nkey); + return -1; + } + + while((k = readpublickey(br, &h)) != nil){ + if(match(h, host) != 0){ + Bprint(bw, "%s %d %.10M %.10M\n", + h, mpsignif(k->n), k->ek, k->n); + } + free(h); + rsapubfree(k); + } + Bprint(bw, "%s %d %.10M %.10M\n", host, mpsignif(hostkey->n), hostkey->ek, hostkey->n); + Bterm(bw); + Bterm(br); + + d = dirstat(nkey); + if(d == nil){ + fprint(2, "new key file disappeared?\n"); + free(nkey); + return -1; + } + + p = strrchr(d->name, '.'); + if(p==nil || strcmp(p, ".new")!=0){ + fprint(2, "new key file changed names? %s to %s\n", nkey, d->name); + free(d); + free(nkey); + return -1; + } + + *p = '\0'; + nulldir(&nd); + nd.name = d->name; + if(remove(keyfile) < 0){ + fprint(2, "error removing %s: %r\n", keyfile); + free(d); + free(nkey); + return -1; + } + if(dirwstat(nkey, &nd) < 0){ + fprint(2, "error renaming %s to %s: %r\n", nkey, d->name); + free(nkey); + free(d); + return -1; + } + free(d); + free(nkey); + return 0; +} + +int +appendkey(char *keyfile, char *host, RSApub *key) +{ + int fd; + + if((fd = open(keyfile, OWRITE)) < 0){ + fd = create(keyfile, OWRITE, 0666); + if(fd < 0){ + fprint(2, "cannot open nor create %s: %r\n", keyfile); + return -1; + } + } + if(seek(fd, 0, 2) < 0 + || fprint(fd, "%s %d %.10M %.10M\n", host, mpsignif(key->n), key->ek, key->n) < 0){ + close(fd); + return -1; + } + close(fd); + return 0; +} diff -Nru /sys/src/cmd/ssh2/rsa2ssh2.c /sys/src/cmd/ssh2/rsa2ssh2.c --- /sys/src/cmd/ssh2/rsa2ssh2.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/rsa2ssh2.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include <9p.h> +#include +#include +#include +#include "sshtun.h" + +Cipher *cryptos[1]; +int debug; + +void +usage(void) +{ + fprint(2, "usage: rsa2ssh2 [file]\n"); + exits("usage"); +} + +void +main(int argc, char *argv[]) +{ + Packet *p; + char *ep, *np, *user; + mpint *e, *n; + int fd, m; + char key[8192], encpub[8192]; + + ARGBEGIN { + default: + usage(); + } ARGEND + if (argc > 1) + usage(); + user = getenv("user"); + if (argc == 0) + fd = 0; + else { + fd = open(argv[0], OREAD); + if (fd < 0) + usage(); + } + m = read(fd, key, 8191); + close(fd); + key[m] = 0; + ep = strstr(key, " ek="); + np = strstr(key, " n="); + if (ep == nil || np == nil) { + fprint(2, "Invalid key file\n"); + exits("invalid"); + } + e = strtomp(ep+4, nil, 16, nil); + n = strtomp(np+3, nil, 16, nil); + p = new_packet(nil); + add_string(p, "ssh-rsa"); + add_mp(p, e); + add_mp(p, n); + if ((m = enc64(encpub, 8192, p->payload, p->rlength-1)) < 0) { + fprint(2, "Base 64 encoding failed\n"); + exits("fail"); + } + print("ssh-rsa "); + write(1, encpub, m); + if (user) + print(" %s\n", user); +} diff -Nru /sys/src/cmd/ssh2/ssh.c /sys/src/cmd/ssh2/ssh.c --- /sys/src/cmd/ssh2/ssh.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/ssh.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,435 @@ +#include +#include +#include + +int isatty(int); +int doauth(int, char *); + +char *user, *remote; +char *netdir; +int debug = 0; +static int stripcr = 0; +static int mflag = 0; +static int iflag = -1; +static int nopw = 0, nopka = 0; +static int chpid; +static int reqfd, dfd1, cfd1, dfd2, cfd2, consfd, kconsfd, cctlfd, notefd, keyfd; + +void +usage(void) +{ + fprint(2, "usage: ssh [-dkKmr] [-l user] [-n dir] [-z attr=val] addr [cmd [args]]\n"); + exits("usage"); +} + +int +handler(void *, char *) +{ + char *p; + int fd; + + if (chpid) { + p = smprint("/proc/%d/note", chpid); + fd = open(p, OWRITE); + free(p); + fprint(fd, "interrupt"); + close(fd); + } + fprint(cctlfd, "rawoff"); + close(cctlfd); + close(consfd); + fprint(reqfd, "close"); + close(reqfd); + close(dfd2); + close(dfd1); + close(cfd2); + close(cfd1); + write(notefd, "kill", 4); + close(notefd); + return 1; +} + +int +cmdmode(void) +{ + int n, m; + char buf[256]; + + while (1) { +reprompt: + write(1, "\n>>> ", 5); + n = 0; + do { + m = read(0, buf + n, 255 - n); + write(1, buf + n, m); + n += m; + buf[n] = '\0'; + if (buf[n-1] == 0x15) + goto reprompt; + } while (buf[n-1] != '\n' && buf[n-1] != '\r'); + switch (buf[0]) { + case '\n': + case '\r': + break; + case 'q': + return 1; + case 'c': + return 0; + case 'r': + stripcr = 1 - stripcr; + return 0; + case 'h': + print("c - continue\n"); + print("h - help\n"); + print("q - quit\n"); + print("r - toggle carriage return stripping\n"); + break; + default: + print("Unknown command\n"); + } + } +} + +main(int argc, char *argv[]) +{ + char *p, *q, *path; + char *whichkey; + int conn, chan, n, i, lstart, nfd; + char buf[4096]; + + keyfd = -1; + whichkey = nil; + ARGBEGIN { + case 'd': + debug++; + break; + case 'l': + user = EARGF(usage()); + break; + case 'r': + stripcr = 1; + break; + case 'I': + iflag = 0; + break; + case 'i': /* Used by scp */ + iflag = 1; + break; + case 'v': + case 'a': + case 'x': + break; + case 'k': + nopka = 1; + break; + case 'K': + nopw = 1; + break; + case 'm': + mflag = 1; + break; + case 'n': + netdir = EARGF(usage()); + break; + case 'z': + whichkey = EARGF(usage()); + break; + default: + usage(); + break; + } ARGEND; + if (argc == 0) + usage(); + if (iflag == -1) + iflag = isatty(0); + remote = *argv; + ++argv; + --argc; + if (q = strchr(remote, '@')) { + *q = 0; + user = remote; + remote = q+1; + } + if (!netdir) { + q = strchr(remote, '!'); + if (q ) { + n = q-remote; + netdir = malloc(n+1); + strncpy(netdir, remote, n); + netdir[n] = '\0'; + p = strrchr(netdir, '/'); + if (p) { + if (strcmp(p+1, "ssh") == 0) + *p = '\0'; + else + remote = smprint("%s/ssh", netdir); + } + else { + free(netdir); + netdir = nil; + } + } + } + if (!user) + user = getuser(); + if (netdir) + p = smprint("%s/ssh", netdir); + else + p = smprint("/net/ssh"); + if (access(p, OREAD) < 0) { + if ((n = rfork(RFPROC|RFMEM|RFNOTEG)) == 0) { + if (netdir) + execl("/bin/sshtun", "sshtun", "-m", netdir, nil); + else + execl("/bin/sshtun", "sshtun", nil); + exits(nil); + } + do { + i = waitpid(); + } while (i != n && i >= 0); + } + free(p); + if ((n = rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT)) == 0) { + if (netdir) + p = smprint("%s/ssh/keys", netdir); + else + p = smprint("/net/ssh/keys"); + keyfd = open(p, ORDWR); + free(p); + if (keyfd < 0) { + // fprint(2, "failed to open sskeys: %r\n"); + chpid = 0; + exits(nil); + } + kconsfd = open("/dev/cons", ORDWR); + if (kconsfd < 0) + nopw = 1; + n = read(keyfd, buf, 5); + buf[5] = 0; + if (n < 0) + exits(nil); + n = strtol(buf+1, nil, 10); + n = readn(keyfd, buf+5, n); + buf[n+5] = 0; + switch (*buf) { + case 'f': + if (kconsfd >= 0) + fprint(kconsfd, "%s\n", buf+5); + case 'o': + close(keyfd); + if (kconsfd >= 0) + close(kconsfd); + break; + default: + if (kconsfd >= 0) { + if (*buf == 'c') { + fprint(kconsfd, "The following key has been offered by the server:\n"); + write(kconsfd, buf+5, n); + fprint(kconsfd, "\n\n"); + fprint(kconsfd, "Add this key? (yes, no, session) "); + } + else { + fprint(kconsfd, "The following key does NOT match the known key(s) for the server:\n"); + write(kconsfd, buf+5, n); + fprint(kconsfd, "\n\n"); + fprint(kconsfd, "Add this key? (yes, no, session, replace) "); + } + n = read(kconsfd, buf, 10); + write(keyfd, buf, n); + seek(keyfd, 0, 2); + readn(keyfd, buf, 5); + buf[5] = 0; + n = strtol(buf+1, nil, 10); + n = readn(keyfd, buf+5, n); + buf[n+5] = 0; + switch (*buf) { + case 'b': + case 'f': + fprint(kconsfd, "%s\n", buf+5); + case 'o': + close(keyfd); + close(kconsfd); + } + } + else { + fprint(keyfd, "n"); + close(keyfd); + } + } + chpid = 0; + exits(nil); + } + chpid = n; + atnotify(handler,1); + if (netdir) + p = smprint("%s/ssh", netdir); + else + p = smprint("ssh"); + q = netmkaddr(remote, p, "22"); + free(p); + dfd1 = dial(q, nil, nil, &cfd1); + if (dfd1 < 0) { + fprint(2, "%s: dial: %r\n", argv0); + if (chpid) { + p = smprint("/proc/%d/note", chpid); + nfd = open(p, OWRITE); + fprint(nfd, "interrupt"); + } + exits(nil); + } + seek(cfd1, 0, 0); + n = read(cfd1, buf, 10); + buf[n] = 0; + conn = atoi(buf); + consfd = open("/dev/cons", ORDWR); + cctlfd = open("/dev/consctl", OWRITE); + fprint(cctlfd, "rawon"); + if (doauth(cfd1, whichkey) < 0) + goto bail; + + if (netdir) + path = smprint("%s/ssh/%d!session",netdir, conn); + else + path = smprint("/net/ssh/%d!session", conn); + + dfd2 = dial(path, nil, nil, &cfd2); + if (dfd2 < 0) { + fprint(2, "%s: dial: %r\n", argv0); + goto bail; + } + n = read(cfd2, buf, 10); + buf[n] = 0; + chan = atoi(buf); + free(path); + if (netdir) + path = smprint("%s/ssh/%d/%d/request", netdir, conn, chan); + else + path = smprint("/net/ssh/%d/%d/request", conn, chan); + + reqfd = open(path, OWRITE); + if (argc == 0) { + if ((i = open("/env/TERM", OREAD)) < 0) + fprint(reqfd, "shell"); + else { + n = read(i, buf, 32); + buf[n] = 0; + fprint(reqfd, "shell %s", buf); + close(i); + } + } + else { + q = buf; + for (i = 0; i < argc; ++i) { + q = seprint(q, buf+1024, " %s", argv[i]); + if (q == nil) + break; + } + if (q != nil) + fprint(reqfd, "exec%s", buf); + else { + fprint(2, "Command too long\n"); + fprint(reqfd, "close"); + goto bail; + } + } + rfork(RFNOTEG); + path = smprint("/proc/%d/notepg", getpid()); + notefd = open(path, OWRITE); + switch (rfork(RFPROC|RFMEM|RFNOWAIT)) { + case 0: + while (1) { + n = read(dfd2, buf, 1024); + if (n <= 0) + break; + if (stripcr) { + for (i = 0, p = buf, q = buf; i < n; ++i, ++q) + if (*q != '\r') + *p++ = *q; + } + else + p = buf + n; + write(1, buf, p-buf); + } + fprint(2, "Connection closed by server\n"); + break; + case -1: + fprint(2, "fork error: %r\n"); + goto bail; + default: + lstart = 1; + while (1) { + n = read(0, buf, 1024); + if (n <= 0) + break; + if (!mflag && lstart && buf[0] == 0x1c) { + if (cmdmode()) + break; + else + continue; + } + lstart = (buf[n-1] == '\n' || buf[n-1] == '\r'); + write(dfd2, buf, n); + } + fprint(2, "EOF on client side\n"); + break; + } +bail: + fprint(cctlfd, "rawoff"); + close(cctlfd); + close(consfd); + fprint(reqfd, "close"); + close(reqfd); + close(dfd2); + close(dfd1); + close(cfd2); + close(cfd1); + write(notefd, "kill", 4); + close(notefd); + exits(nil); + return 0; +} + +int +isatty(int fd) +{ + char buf[64]; + + buf[0] = '\0'; + fd2path(fd, buf, sizeof buf); + if(strlen(buf)>=9 && strcmp(buf+strlen(buf)-9, "/dev/cons")==0) + return 1; + return 0; +} + +int +doauth(int cfd1, char *whichkey) +{ + UserPasswd *up; + int n; + + if (!nopka) { + if (whichkey) + n = fprint(cfd1, "ssh-userauth K %s %s", user, whichkey); + else + n = fprint(cfd1, "ssh-userauth K %s", user); + if (n >= 0) + return 0; + } + if (nopw) + return -1; +/* + up = auth_getuserpasswd(iflag ? auth_getkey : nil, "proto=pass service=ssh server=%q user=%q !password?", + remote, user); +*/ + up = auth_getuserpasswd(iflag ? auth_getkey : nil, "proto=pass service=ssh server=%q user=%q", + remote, user); + if (up == nil) { + fprint(2, "Failure to get password: %r\n"); + return -1; + } + n = fprint(cfd1, "ssh-userauth k %s %s", user, up->passwd); + if (n >= 0) + return 0; + fprint(2, "auth %r\n"); + return -1; +} diff -Nru /sys/src/cmd/ssh2/ssh2key.c /sys/src/cmd/ssh2/ssh2key.c --- /sys/src/cmd/ssh2/ssh2key.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/ssh2key.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include <9p.h> +#include +#include +#include +#include "sshtun.h" + +Cipher *cryptos[1]; +int debug; + +void +usage(void) +{ + fprint(2, "usage: ssh2key [file]\n"); + exits("usage"); +} + +void +main(int argc, char *argv[]) +{ + Packet *p; + char *ep, *np, *user; + mpint *e, *n; + int fd, m; + char key[8192], encpub[8192]; + + ARGBEGIN { + default: + usage(); + } ARGEND + if (argc > 1) + usage(); + user = getenv("user"); + if (argc == 0) + fd = 0; + else { + fd = open(argv[0], OREAD); + if (fd < 0) + usage(); + } + m = read(fd, key, 8191); + close(fd); + key[m] = 0; + ep = strstr(key, " ek="); + np = strstr(key, " n="); + if (ep == nil || np == nil) { + fprint(2, "Invalid key file\n"); + exits("invalid"); + } + e = strtomp(ep+4, nil, 16, nil); + n = strtomp(np+3, nil, 16, nil); + p = new_packet(nil); + add_string(p, "ssh-rsa"); + add_mp(p, e); + add_mp(p, n); + if ((m = enc64(encpub, 8192, p->payload, p->rlength-1)) < 0) { + fprint(2, "Base 64 encoding failed\n"); + exits("fail"); + } + print("ssh-rsa "); + write(1, encpub, m); + if (user) + print(" %s\n", user); +} diff -Nru /sys/src/cmd/ssh2/sshsession.c /sys/src/cmd/ssh2/sshsession.c --- /sys/src/cmd/ssh2/sshsession.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/sshsession.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,409 @@ +#include +#include +#include +#include + +void newchannel(int, char *, int); +char *get_string(char *, char *); +char *confine(char *, char *); +void runcmd(int, int, char *, char *, char *, char *); + +int errfd, slfd, toppid, sflag, tflag, prevent; +char *shell; +char *restdir; +char *srvpt; +char *nsfile = nil; +char *uname; + +void +usage(void) +{ + fprint(2, "usage: sshsession [-s shell] [-r restdir] [-R restdir] [-S srvpt] [-n namespace] [-t]\n"); + exits("usage"); +} + +main(int argc, char *argv[]) +{ + char *netdir, *filnam, *p, *q; + int ctlfd, fd, n; + char buf[128]; + + rfork(RFNOTEG); + toppid = getpid(); + errfd = create("/tmp/ssh.err", OWRITE, 0664); + slfd = open("/dev/syslog", OWRITE); + shell = "/bin/rc -il"; + ARGBEGIN { + case 'n': + nsfile = EARGF(usage()); + break; + case 'R': + prevent = 1; + case 'r': + restdir = EARGF(usage()); + break; + case 's': + sflag = 1; + shell = EARGF(usage()); + break; + case 't': + tflag = 1; + break; + case 'S': + srvpt = EARGF(usage()); + break; + default: + usage(); + break; + } ARGEND; + + uname = getenv("user"); + if (uname == nil) + uname = "none"; + netdir = getenv("net"); + fprint(errfd, "net is %s\n", netdir); + filnam = smprint("%s/clone", netdir); + ctlfd = open(filnam, ORDWR); + if (ctlfd < 0) { + fprint(errfd, "could not clone: %s: %r\n", filnam); + exits(nil); + } + free(filnam); + filnam = smprint("%s/data", netdir); + fd = open(filnam, OREAD); + if (fd < 0) { + fprint(errfd, "Couldn't open data: %r\n"); + fprint(ctlfd, "hangup"); + exits(nil); + } + n = read(fd, buf, 128); + close(fd); + free(filnam); + if (n < 0) { + fprint(errfd, "Read error for cap: %r\n"); + fprint(ctlfd, "hangup"); + exits(nil); + } + else if (n > 0) { + buf[n] = '\0'; + if (strcmp(buf, "n/a") != 0) { + fd = open("#ยค/capuse", OWRITE); + if (fd < 0) { + fprint(errfd, "Couldn't open capuse: %r\n"); + fprint(ctlfd, "hangup"); + exits(nil); + } + if (write(fd, buf, n) < 0) { + fprint(errfd, "Write to capuse failed: %r\n"); + fprint(ctlfd, "hangup"); + exits(nil); + } + close(fd); + p = strchr(buf, '@'); + if (p) { + ++p; + q = strchr(p, '@'); + if (q) { + *q = '\0'; + uname = strdup(p); + } + if (!tflag) { + if (newns(p, nsfile) < 0) + fprint(errfd, "newns failed: %r\n"); + } + } + } + } + n = read(ctlfd, buf, 128); + buf[n] = '\0'; + fprint(ctlfd, "announce session"); + filnam = smprint("%s/%s/listen", netdir, buf); + fprint(errfd, "listen is %s\n", filnam); + if (access(netdir, AEXIST) < 0) { + p = smprint("/srv/%s", srvpt ? srvpt : "sshtun"); + fd = open(p, ORDWR); + if (fd < 0) { + fprint(errfd, "srv open failed; %r\n"); + fprint(ctlfd, "hangup"); + exits(nil); + } + mount(fd, -1, "/net", MBEFORE, ""); + } + while (1) { + fd = open(filnam, ORDWR); + if (fd < 0) { + fprint(errfd, "listen failed: %r\n"); + fprint(ctlfd, "hangup"); + exits(nil); + } + n = read(fd, buf, 128); + fprint(errfd, "read from listen file returned %d\n", n); + if (n <= 0) { + fprint(errfd, "read on listen failed: %r\n"); + fprint(ctlfd, "hangup"); + exits(nil); + } + buf[n] = '\0'; + fprint(errfd, "read %s\n", buf); + switch (fork()) { + case 0: + close(ctlfd); + newchannel(fd, netdir, atoi(buf)); + break; + case -1: + fprint(errfd, "fork failed: %r\n"); + fprint(ctlfd, "hangup"); + exits(nil); + break; + default: + close(fd); + break; + } + } +} + +void +newchannel(int fd, char *conndir, int channum) +{ + char *p, *q, *reqfile, *datafile; + int n, reqfd, datafd, motdfd, want_reply, already_done; + char buf[32768], buf2[10240], cmd[1024]; + + close(fd); + already_done = 0; + reqfile = smprint("%s/%d/request", conndir, channum); + reqfd = open(reqfile, ORDWR); + if (reqfd < 0) { + fprint(errfd, "Couldn't open request file: %r\n"); + exits(nil); + } + datafile = smprint("%s/%d/data", conndir, channum); + datafd = open(datafile, ORDWR); + if (datafd < 0) { + fprint(errfd, "Couldn't open data file: %r\n"); + exits(nil); + } + while (1) { + n = read(reqfd, buf, 32768); + fprint(errfd, "read from request file returned %d\n", n); + if (n == 0) { + exits(nil); + } + else if (n < 0) { + fprint(errfd, "Read failed: %r\n"); + exits(nil); + } + for (p = buf; p < buf + n && *p != ' '; ++p) ; + *p = '\0'; + ++p; + want_reply = (*p == 't'); + if (strcmp(buf, "pty-req") == 0) { + if (want_reply) + fprint(reqfd, "success"); + } + else if (strcmp(buf, "x11-req") == 0) { + if (want_reply) + fprint(reqfd, "failure"); + } + else if (strcmp(buf, "env") == 0) { + if (want_reply) + fprint(reqfd, "failure"); + } + else if (strcmp(buf, "shell") == 0) { + if (already_done) { + if (want_reply) + fprint(reqfd, "failure"); + continue; + } + switch (fork()) { + case 0: + if (sflag) + snprint(cmd, 1024, "-s%s", shell); + else + snprint(cmd, 1024, ""); + if (slfd > 0) + fprint(slfd, "starting ssh shell for %s\n", uname); + else + syslog(1, "ssh", "starting ssh shell for %s", uname); + motdfd = open("/sys/lib/motd", OREAD); + if (motdfd >= 0) { + while ((n = read(motdfd, buf, 8192)) > 0) { + p = buf2; + for (q = buf; q < buf+n; ++q) { + if (*q == '\n') + *p++ = '\r'; + *p++ = *q; + } + write(datafd, buf2, p-buf2); + } + close(motdfd); + } + //runcmd(reqfd, datafd, "con", "/bin/conssim", cmd, nil); + runcmd(reqfd, datafd, "con", "/bin/ip/telnetd", "-nt", nil); + exits(nil); + case -1: + if (want_reply) + fprint(reqfd, "failure"); + fprint(2, "Cannot fork: %r\n"); + exits(nil); + break; + default: + already_done = 1; + if (want_reply) + fprint(reqfd, "success"); + break; + } + } + else if (strcmp(buf, "exec") == 0) { + if (already_done) { + if (want_reply) + fprint(reqfd, "failure"); + continue; + } + switch (fork()) { + case 0: + if (restdir) + chdir(restdir); + if (!prevent || (q = getenv("sshsession")) && strcmp(q, "allow") == 0) + get_string(p+1, cmd); + else + confine(p+1, cmd); + if (slfd > 0) + fprint(slfd, "running %s for %s\n", cmd, uname); + else + syslog(1, "ssh", "running %s for %s", cmd, uname); + runcmd(reqfd, datafd, "rx", "/bin/rc", "-lc", cmd); + exits(nil); + case -1: + if (want_reply) + fprint(reqfd, "failure"); + fprint(errfd, "Cannot fork: %r\n"); + exits(nil); + break; + default: + already_done = 1; + if (want_reply) + fprint(reqfd, "success"); + break; + } + } + else if (strcmp(buf, "subsystem") == 0) { + if (want_reply) + fprint(reqfd, "failure"); + } + else if (strcmp(buf, "window-change") == 0) { + if (want_reply) + fprint(reqfd, "success"); + } + else if (strcmp(buf, "xon-xoff") == 0) { + } + else if (strcmp(buf, "signal") == 0) { + } + else if (strcmp(buf, "exit-status") == 0) { + } + else if (strcmp(buf, "exit-signal") == 0) { + } + else + fprint(errfd, "Unknown channel request: %s\n", buf); + } +} + +char * +get_string(char *q, char *s) +{ + int n; + + n = nhgetl(q); + q += 4; + memmove(s, q, n); + s[n] = '\0'; + q += n; + return q; +} + +char * +confine(char *q, char *s) +{ + int i, n, m; + char *p, *e, *r, *buf, *toks[32]; + + n = nhgetl(q); + q += 4; + buf = malloc(n+1); + memmove(buf, q, n); + buf[n] = 0; + m = tokenize(buf, toks, 32); + e = s + n + 1; + for (i = 0, r = s; i < m; ++i) { + p = strrchr(toks[i], '/'); + if (p) { + if (*(p+1)) + r = seprint(r, e, "%s ", p+1); + else + r = seprint(r, e, ". "); + } + else + r = seprint(r, e, "%s ", toks[i]); + } + free(buf); + q += n; + return q; +} + +void +runcmd(int reqfd, int datafd, char *svc, char *cmd, char *arg1, char *arg2) +{ + char *p; + int fd, cmdpid, child; + + cmdpid = rfork(RFPROC|RFMEM|RFNOTEG|RFFDG); + switch (cmdpid) { + case -1: + fprint(errfd, "fork failed: %r\n"); + break; + case 0: + if (restdir == nil) { + p = smprint("/usr/%s", uname); + if (access(p, AREAD) == 0) + chdir(p); + free(p); + } + p = strrchr(cmd, '/'); + if (p) + ++p; + else + p = cmd; + dup(datafd, 0); + dup(datafd, 1); + dup(datafd, 2); + close(datafd); + putenv("service", svc); + fprint(errfd, "starting %s\n", cmd); + execl(cmd, p, arg1, arg2, nil); + fprint(errfd, "cannot exec %s: %r\n", cmd); + break; + default: + close(datafd); + while (1) { + fprint(errfd, "waiting for child %d\n", cmdpid); + child = waitpid(); + fprint(errfd, "child %d passed\n", child); + if (child == cmdpid || child == -1) + break; + } + if (child == -1) + fprint(errfd, "wait failed: %r\n"); + if (slfd > 0) + fprint(slfd, "closing ssh session for %s\n", uname); + else + syslog(1, "ssh", "closing ssh session for %s", uname); + fprint(errfd, "closing connection\n"); + write(reqfd, "close", 5); + p = smprint("/proc/%d/notepg", toppid); + fd = open(p, OWRITE); + write(fd, "interrupt", 3); + close(fd); + break; + } + exits(nil); +} diff -Nru /sys/src/cmd/ssh2/sshtun.c /sys/src/cmd/ssh2/sshtun.c --- /sys/src/cmd/ssh2/sshtun.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/sshtun.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,2861 @@ +#include +#include +#include +#include +#include +#include <9p.h> +#include +#include +#include +#include +#include "sshtun.h" + +void stend(Srv *); +void server(char *, char *); +void stopen(Req *); +void stlisten1(void *); +void stlisten2(void *); +void stread(Req *); +void readreqrem(void *); +void readdata(void *); +void stwrite(Req *); +void writectl(void *); +void writereqrem(void *); +void writedata(void *); +void stclunk(Fid *); +void stflush(Req *); +void filedup(Req *, File *); +Conn *alloc_conn(void); +SSHChan *alloc_chan(Conn *); +int dohandshake(Conn *, char *); +void send_kexinit(Conn *); +void reader(void *); +int validatekex(Conn *, Packet *); +int validatekexs(Packet *); +int validatekexc(Packet *); +int auth_req(Packet *, Conn *); +int client_auth(Conn *, Ioproc *); +char *factlookup(int, int, char *[]); +void shutdown(Conn *); + +Srv sshtunsrv = { + .open = stopen, + .read = stread, + .write = stwrite, + .flush = stflush, + .destroyfid = stclunk, + .end = stend, +}; + +Cipher *cryptos[] = { + &cipheraes128, + &cipheraes192, + &cipheraes256, +// &cipherblowfish, + &cipher3des, + &cipherrc4, +}; + +Kex *kexes[] = { + &dh1sha1, + &dh14sha1, +}; + +PKA *pkas[3]; + +char *macnames[] = { + "hmac-sha1", +}; + +char *st_names[] = { +[Empty] "Empty", +[Allocated] "Allocated", +[Initting] "Initting", +[Listening] "Listening", +[Opening] "Opening", +[Negotiating] "Negotiating", +[Authing] "Authing", +[Established] "Established", +[Eof] "Eof", +[Closing] "Closing", +[Closed] "Closed", +}; + +File *rootfile, *clonefile, *ctlfile, *keysfile; +Conn *connections[MAXCONN]; +char *mntpt = "/net"; +int debug; +int kflag; +int slfd; +char uid[32]; +MBox keymbox; +QLock availlck; +Rendez availrend; + +void +usage(void) +{ + fprint(2, "usage: sshtun [-d] [-k] [-m mntpt] [-s srvpt]\n"); + exits("usage"); +} + +void +threadmain(int argc, char *argv[]) +{ + char *srvpt = nil; + int fd, n; + + slfd = open("/dev/syslog", OWRITE); + ARGBEGIN { + case '9': + chatty9p = 1; + break; + case 'd': + debug++; + break; + case 'k': + kflag = 1; + break; + case 'm': + mntpt = EARGF(usage()); + break; + case 's': + srvpt = EARGF(usage()); + break; + default: + usage(); + break; + } ARGEND; + + fd = open("/dev/user", OREAD); + if (fd < 0) + strcpy(uid, "none"); + else { + n = read(fd, uid, 31); + if (n < 0) + strcpy(uid, "none"); + else + uid[n] = '\0'; + close(fd); + } + + keymbox.mchan = chancreate(4, 0); + availrend.l = &availlck; + dh_init(pkas); + + if (rfork(RFNOTEG) < 0) + fprint(2, "Failed to set process attributes: %r\n"); + server(mntpt, srvpt); +} + +Ioproc *io9p; + +int +read9pmsg(int fd, void *abuf, uint n) +{ + int m, len; + uchar *buf; + + if (io9p == nil) + io9p = ioproc(); + + buf = abuf; + + /* read count */ + m = ioreadn(io9p, fd, buf, BIT32SZ); + if(m != BIT32SZ){ + if(m < 0) + return -1; + return 0; + } + + len = GBIT32(buf); + if(len <= BIT32SZ || len > n){ + werrstr("bad length in 9P2000 message header"); + return -1; + } + len -= BIT32SZ; + m = ioreadn(io9p, fd, buf+BIT32SZ, len); + if(m < len) + return 0; + return BIT32SZ+m; +} + +void +stend(Srv *) +{ + closeioproc(io9p); + threadkillgrp(threadgetgrp()); +} + +void +server(char *mntpt, char *srvpt) +{ + Dir d; + char *p; + int fd; + + sshtunsrv.tree = alloctree(uid, uid, 0777, nil); + rootfile = createfile(sshtunsrv.tree->root, "ssh", uid, 0555|DMDIR, (void*)RootFile); + clonefile = createfile(rootfile, "clone", uid, 0666, (void*)CloneFile); + ctlfile = createfile(rootfile, "ctl", uid, 0666, (void*)CtlFile); + keysfile = createfile(rootfile, "keys", uid, 0600, (void *)ReqRemFile); + threadpostmountsrv(&sshtunsrv, srvpt, mntpt, MAFTER); + p = smprint("%s/cs", mntpt); + fd = open(p, OWRITE); + free(p); + if (fd >= 0) { + fprint(fd, "add ssh"); + close(fd); + } + if (srvpt) { + nulldir(&d); + d.mode = 0666; + p = smprint("/srv/%s", srvpt); + dirwstat(p, &d); + free(p); + } +} + +void +stopen(Req *r) +{ + Conn *c; + SSHChan *sc; + char *p; + int lev, xconn, fnum, fd; + char buf[10]; + + fnum = (uintptr)r->fid->file->aux; + lev = fnum >> LEVSHIFT; + switch (fnum & FileMask) { + case CloneFile: + switch (lev) { + case 0: + p = smprint("%s/tcp/clone", mntpt); + fd = open(p, ORDWR); + free(p); + if (fd < 0) { + responderror(r); + return; + } + c = alloc_conn(); + if (c == nil) { + respond(r, "No more connections"); + return; + } + c->ctlfd = fd; + filedup(r, c->ctlfile); + if (debug) + fprint(2, "new connection: %d\n", c->id); + break; + case 1: + xconn = (fnum >> CONNSHIFT) & ConnMask; + c = connections[xconn]; + if (c == nil) { + respond(r, "Invalid connection"); + return; + } + sc = alloc_chan(c); + if (sc == nil) { + respond(r, "No more channels"); + return; + } + filedup(r, sc->ctl); + break; + default: + snprint(buf, 10, "bad %d", lev); + readstr(r, buf); + break; + } + respond(r, nil); + break; + case ListenFile: + switch (lev) { + case 1: + r->aux = (void *)threadcreate(stlisten1, r, 8192); + break; + case 2: + r->aux = (void *)threadcreate(stlisten2, r, 8192); + break; + default: + respond(r, "not possible"); + break; + } + break; + default: + respond(r, nil); + break; + } +} + +void +stlisten1(void *a) +{ + Req *r; + Conn *c, *cl; + Ioproc *io; + char *msg; + int fnum, xconn, fd, n; + char buf[10], path[40]; + + r = a; + fnum = (uintptr)r->fid->file->aux; + xconn = (fnum >> CONNSHIFT) & ConnMask; + cl = connections[xconn]; + if (cl == nil) { + respond(r, "Invalid connection"); + threadexits(nil); + } + memset(buf, '\0', sizeof(buf)); + io = ioproc(); + seek(cl->ctlfd, 0, 0); + if ((n = ioread(io, cl->ctlfd, buf, 10)) <= 0) + fprint(2, "read failed: %r\n"); + buf[n] = '\0'; + cl->state = Listening; + snprint(path, 40, "%s/tcp/%s/listen", mntpt, buf); + while (1) { + fd = ioopen(io, path, ORDWR); + if (fd < 0) { + r->aux = 0; + responderror(r); + closeioproc(io); + shutdown(cl); + threadexits(nil); + } + c = alloc_conn(); + if (c) + break; + n = ioread(io, fd, buf, 10); + if (n <= 0) { + r->aux = 0; + responderror(r); + closeioproc(io); + shutdown(cl); + threadexits(nil); + } + else { + buf[n] = '\0'; + msg = smprint("reject %s No available connections", buf); + iowrite(io, fd, msg, strlen(msg)); + free(msg); + } + close(fd); + } + c->ctlfd = fd; + filedup(r, c->ctlfile); + if (debug) + fprint(2, "**** responding to listen open ***\n"); + r->aux = 0; + respond(r, nil); + closeioproc(io); + threadexits(nil); +} + +void +stlisten2(void *a) +{ + Req *r; + Packet *p2; + Ioproc *io; + Conn *c; + SSHChan *sc; + int i, n, xconn, fnum; + + r = a; + fnum = (uintptr)r->fid->file->aux; + xconn = (fnum >> CONNSHIFT) & ConnMask; + c = connections[xconn]; + if (c == nil) { + respond(r, "Invalid connection"); + threadexits(nil); + } + if (c->state == Closed || c->state == Closing) { + r->aux = 0; + respond(r, "listen on a closed connection"); + threadexits(nil); + } + sc = c->chans[fnum & ConnMask]; + qlock(&c->l); + sc->lreq = r; + for (i = 0; i < c->nchan; ++i) + if (c->chans[i] && c->chans[i]->state == Opening && c->chans[i]->ann + && strcmp(c->chans[i]->ann, sc->ann) == 0) + break; + if (i >= c->nchan) { + sc->state = Listening; + rsleep(&sc->r); + i = sc->waker; + if (i < 0) { + qunlock(&c->l); + r->aux = 0; + responderror(r); + threadexits(nil); + } + } + else + rwakeup(&c->chans[i]->r); + qunlock(&c->l); + if (c->state == Closed || c->state == Closing || c->state == Eof) { + r->aux = 0; + respond(r, "Listen on a closed connection"); + threadexits(nil); + } + c->chans[i]->state = Established; + p2 = new_packet(c); + c->chans[i]->rwindow = 32*1024; + add_byte(p2, SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + hnputl(p2->payload + 1, c->chans[i]->otherid); + hnputl(p2->payload + 5, c->chans[i]->id); + hnputl(p2->payload + 9, 32*1024); + hnputl(p2->payload + 13, 8192); + p2->rlength = 18; + n = finish_packet(p2); + filedup(r, c->chans[i]->ctl); + io = ioproc(); + n = iowrite(io, c->datafd, p2->nlength, n); + free(p2); + closeioproc(io); + if (debug) + fprint(2, "*** Responding to chan listen open ***\n"); + r->aux = 0; + if (n < 0) + responderror(r); + else + respond(r, nil); + threadexits(nil); +} + +void +getdata(Conn *c, SSHChan *sc, Req *r) +{ + Packet *p; + Plist *d; + int n; + + n = r->ifcall.count; + if (sc->dataq->rem < n) + n = sc->dataq->rem; + if (n > 8192) + n = 8192; + r->ifcall.offset = 0; + readbuf(r, sc->dataq->st, n); + sc->dataq->st += n; + sc->dataq->rem -= n; + sc->inrqueue -= n; + if (sc->dataq->rem <= 0) { + d = sc->dataq; + sc->dataq = sc->dataq->next; + if (d->pack->tlength > sc->rwindow) + sc->rwindow = 0; + else + sc->rwindow -= d->pack->tlength; + free(d->pack); + free(d); + } + if (sc->rwindow < 16*1024) { + sc->rwindow += 32*1024; + if (debug) + fprint(2, "Increasing receive window to %lud, inq %lud\n", sc->rwindow, sc->inrqueue); + p = new_packet(c); + add_byte(p, SSH_MSG_CHANNEL_WINDOW_ADJUST); + hnputl(p->payload+1, sc->otherid); + hnputl(p->payload+5, 32*1024); + p->rlength += 8; + n = finish_packet(p); + iowrite(c->dio, c->datafd, p->nlength, n); + free(p); + } + r->aux = 0; + respond(r, nil); +} + +void +stread(Req *r) +{ + Conn *c; + SSHChan *sc; + int fd, n, lev, cnum, xconn, fnum; + char buf[256], path[40]; + + fnum = (uintptr)r->fid->file->aux; + lev = fnum >> LEVSHIFT; + xconn = (fnum >> CONNSHIFT) & ConnMask; + c = connections[xconn]; + if (c == nil) { + if (lev != 0 || (fnum & FileMask) != ReqRemFile) { + respond(r, "Invalid connection"); + return; + } + cnum = 0; + sc = nil; + } + else { + cnum = fnum & ConnMask; + sc = c->chans[cnum]; + } + switch (fnum & FileMask) { + case CtlFile: + case ListenFile: + if (r->ifcall.offset != 0) { + respond(r, nil); + break; + } + switch (lev) { + case 0: + readstr(r, st_names[c->state]); + break; + case 1: + snprint(buf, 256, "%d", xconn); + readstr(r, buf); + break; + case 2: + snprint(buf, 256, "%d", cnum); + readstr(r, buf); + break; + default: + snprint(buf, 256, "Internal error: level %d", lev); + respond(r, buf); + return; + break; + } + respond(r, nil); + break; + case CloneFile: + if (r->ifcall.offset != 0) { + respond(r, nil); + break; + } + readstr(r, "Congratulations, you've achieved the impossible\n"); + respond(r, nil); + break; + case DataFile: + if (lev == 0) { + respond(r, nil); + break; + } + if (lev == 1) { + if (c->cap) + readstr(r, c->cap); + respond(r, nil); + break; + } + + r->aux = (void *)threadcreate(readdata, r, 8192); + break; + case LocalFile: + if (lev == 1) { + if (c->ctlfd >= 0) { + n = pread(c->ctlfd, buf, 10, 0); + buf[n] = '\0'; + snprint(path, 40, "%s/tcp/%s/local", mntpt, buf); + fd = open(path, OREAD); + n = pread(fd, buf, 255, 0); + close(fd); + buf[n] = '\0'; + readstr(r, buf); + } + else + readstr(r, "::!0\n"); + } + respond(r, nil); + break; + case ReqRemFile: + r->aux = (void *)threadcreate(readreqrem, r, 8192); + break; + case StatusFile: + switch (lev) { + case 0: + readstr(r, "Impossible"); + break; + case 1: + if (c->state < 0 || c->state > Closed) + readstr(r, "Unknown"); + else + readstr(r, st_names[c->state]); + break; + case 2: + if (sc->state < 0 || sc->state > Closed) + readstr(r, "Unknown"); + else + readstr(r, st_names[sc->state]); + break; + } + respond(r, nil); + break; + default: + respond(r, nil); + break; + } +} + +void +readreqrem(void *a) +{ + Ioproc *io; + Req *r; + Conn *c; + SSHChan *sc; + int fd, n, lev, cnum, xconn, fnum; + char buf[256], path[40]; + + r = a; + fnum = (uintptr)r->fid->file->aux; + lev = fnum >> LEVSHIFT; + xconn = (fnum >> CONNSHIFT) & ConnMask; + c = connections[xconn]; + if (c == nil) { + if (lev != 0) { + respond(r, "Invalid connection"); + return; + } + sc = nil; + } + else { + cnum = fnum & ConnMask; + sc = c->chans[cnum]; + } + switch (lev) { + case 0: + if (r->ifcall.offset == 0 && keymbox.state != Empty) { + r->aux = 0; + respond(r, "Key file collision"); + break; + } + if (r->ifcall.offset != 0) { + readstr(r, keymbox.msg); + r->aux = 0; + respond(r, nil); + if (r->ifcall.offset + r->ifcall.count >= strlen(keymbox.msg)) + keymbox.state = Empty; + else + keymbox.state = Allocated; + break; + } + keymbox.state = Allocated; + while (1) { + if (keymbox.msg == nil) { + if (recv(keymbox.mchan, nil) < 0) { + r->aux = 0; + responderror(r); + keymbox.state = Empty; + threadexits(nil); + } + } + if (keymbox.state == Empty) { + break; + } + else if (keymbox.state == Allocated) { + if (keymbox.msg) { + readstr(r, keymbox.msg); + if (r->ifcall.offset + r->ifcall.count >= strlen(keymbox.msg)) { + free(keymbox.msg); + keymbox.msg = nil; + keymbox.state = Empty; + } + } + break; + } + } + r->aux = 0; + respond(r, nil); + break; + case 1: + if (c->ctlfd >= 0) { + io = ioproc(); + seek(c->ctlfd, 0, 0); + n = ioread(io, c->ctlfd, buf, 10); + if (n < 0) { + r->aux = 0; + responderror(r); + closeioproc(io); + break; + } + buf[n] = '\0'; + snprint(path, 40, "%s/tcp/%s/remote", mntpt, buf); + if ((fd = ioopen(io, path, OREAD)) < 0 || (n = ioread(io, fd, buf, 255)) < 0) { + r->aux = 0; + responderror(r); + if (fd >= 0) + ioclose(io, fd); + closeioproc(io); + break; + } + ioclose(io, fd); + closeioproc(io); + buf[n] = '\0'; + readstr(r, buf); + } + else + readstr(r, "::!0\n"); + r->aux = 0; + respond(r, nil); + break; + case 2: + if ((sc->state == Closed || sc->state == Closing || sc->state == Eof) && sc->reqq == nil && sc->dataq == nil) { + if (debug) + fprint(2, "Sending EOF1 to channel request listener\n"); + r->aux = 0; + respond(r, nil); + break; + } + while (sc->reqq == nil) { + if (recv(sc->reqchan, nil) < 0) { + r->aux = 0; + responderror(r); + threadexits(nil); + } + if ((sc->state == Closed || sc->state == Closing || sc->state == Eof) && sc->reqq == nil && sc->dataq == nil) { + if (debug) + fprint(2, "Sending EOF2 to channel request listener\n"); + r->aux = 0; + respond(r, nil); + threadexits(nil); + } + } + n = r->ifcall.count; + if (sc->reqq->rem < n) + n = sc->reqq->rem; + if (n > 8192) + n = 8192; + r->ifcall.offset = 0; + readbuf(r, sc->reqq->st, n); + sc->reqq->st += n; + sc->reqq->rem -= n; + if (sc->reqq->rem <= 0) { + Plist *d = sc->reqq; + sc->reqq = sc->reqq->next; + free(d->pack); + free(d); + } + r->aux = 0; + respond(r, nil); + break; + } + threadexits(nil); +} + +void +readdata(void *a) +{ + Req *r; + Conn *c; + SSHChan *sc; + int cnum, xconn, fnum; + + r = a; + fnum = (uintptr)r->fid->file->aux; + xconn = (fnum >> CONNSHIFT) & ConnMask; + c = connections[xconn]; + if (c == nil) { + respond(r, "Invalid connection"); + threadexits(nil); + } + cnum = fnum & ConnMask; + sc = c->chans[cnum]; + if ((sc->state == Closed || sc->state == Closing || sc->state == Eof) && sc->dataq == nil) { + if (debug) + fprint(2, "Sending EOF1 to channel listener\n"); + r->aux = 0; + respond(r, nil); + threadexits(nil); + } + if (sc->dataq != nil) { + getdata(c, sc, r); + threadexits(nil); + } + while (sc->dataq == nil) { + if (recv(sc->inchan, nil) < 0) { + if (debug) + fprint(2, "Got intterrupt/error in readdata %r\n"); + r->aux = 0; + responderror(r); + threadexits(nil); + } + if ((sc->state == Closed || sc->state == Closing || sc->state == Eof) && sc->dataq == nil) { + if (debug) + fprint(2, "Sending EOF2 to channel listener\n"); + r->aux = 0; + respond(r, nil); + threadexits(nil); + } + } + getdata(c, sc, r); + threadexits(nil); +} + +void +stwrite(Req *r) +{ + Conn *c; + SSHChan *ch; + int lev, fnum, xconn; + + fnum = (uintptr)r->fid->file->aux; + lev = fnum >> LEVSHIFT; + xconn = (fnum >> CONNSHIFT) & ConnMask; + c = connections[xconn]; + if (c == nil) { + respond(r, "Invalid connection"); + return; + } + ch = c->chans[fnum & ConnMask]; + switch (fnum & FileMask) { + case CloneFile: + case CtlFile: + r->aux = (void *)threadcreate(writectl, r, 8192); + break; + case DataFile: + r->ofcall.count = r->ifcall.count; + if (lev < 2) { + respond(r, nil); + break; + } + if (c->state == Closed || c->state == Closing || ch->state == Closed || ch->state == Closing) { + respond(r, nil); + break; + } + r->aux = (void *)threadcreate(writedata, r, 8192); + break; + case ReqRemFile: + r->aux = (void *)threadcreate(writereqrem, r, 8192); + break; + default: + respond(r, nil); + break; + } +} + +void +writectl(void *a) +{ + Req *r; + Packet *p; + Conn *c; + SSHChan *ch; + char *q, *buf, *toks[4],*attrs[5]; + int n, ntok, lev, fnum, xconn; + char path[40], buf2[10]; + + r = a; + fnum = (uintptr)r->fid->file->aux; + lev = fnum >> LEVSHIFT; + xconn = (fnum >> CONNSHIFT) & ConnMask; + c = connections[xconn]; + if (c == nil) { + respond(r, "Invalid connection"); + threadexits(nil); + } + ch = c->chans[fnum & ConnMask]; + if (r->ifcall.count <= 10) + buf = emalloc9p(11); + else + buf = emalloc9p(r->ifcall.count + 1); + memmove(buf, r->ifcall.data, r->ifcall.count); + buf[r->ifcall.count] = '\0'; + ntok = tokenize(buf, toks, 4); + switch (lev) { + case 0: + break; + case 1: + if (strcmp(toks[0], "connect") == 0) { + if (ntok < 2) { + r->aux = 0; + free(buf); + respond(r, "Invalid connect request"); + threadexits(nil); + } + memset(buf2, '\0', sizeof(buf2)); + pread(c->ctlfd, buf2, 10, 0); + fprint(c->ctlfd, "connect %s %s", toks[1], ntok > 3 ? toks[2] : ""); + c->role = Client; + /* Override the PKA list; we can take any in */ + pkas[0] = &rsa_pka; + pkas[1] = &dss_pka; + pkas[2] = nil; + q = estrdup9p(buf2); + if (dohandshake(c, q) < 0) { + r->aux = 0; + respond(r, "handshake failed"); + free(q); + free(buf); + threadexits(nil); + } + free(q); + keymbox.state = Empty; + nbsendul(keymbox.mchan, 1); + break; + } + if (c->state == Closed || c->state == Closing) { + r->aux = 0; + respond(r, "connection closed"); + free(buf); + threadexits(nil); + } + if (strcmp(toks[0], "ssh-userauth") == 0) { + if (ntok < 3 || ntok > 4) { + r->aux = 0; + respond(r, "Invalid connection command"); + free(buf); + threadexits(nil); + } + if (!c->service) + c->service = estrdup9p(toks[0]); + if (c->user) + free(c->user); + c->user = estrdup9p(toks[2]); + if (ntok == 4 && strcmp(toks[1], "k") == 0) { + if (c->authkey) { + free(c->authkey); + c->authkey = nil; + } + if (c->password) + free(c->password); + c->password = estrdup9p(toks[3]); + } + else { + if (c->password) { + free(c->password); + c->password = nil; + } + attrs[0] = "proto=rsa"; + attrs[1] = "!dk?"; + attrs[2] = smprint("user=%s", c->user); + attrs[3] = smprint("sys=%s", c->remote); + if (c->authkey) + free(c->authkey); + if (ntok == 3) + c->authkey = factlookup(4, 2, attrs); + else { + attrs[4] = toks[3]; + c->authkey = factlookup(5, 2, attrs); + } + free(attrs[2]); + free(attrs[3]); + } + if (!c->password && !c->authkey) { + r->aux = 0; + respond(r, "no auth info"); + free(buf); + threadexits(nil); + } + else if (c->state != Authing) { + p = new_packet(c); + add_byte(p, SSH_MSG_SERVICE_REQUEST); + add_string(p, c->service); + n = finish_packet(p); + if (c->dio) { + if (iowrite(c->dio, c->datafd, p->nlength, n) < 0) { + r->aux = 0; + responderror(r); + free(p); + free(buf); + threadexits(nil); + } + } + else { + if (write(c->datafd, p->nlength, n) < 0) { + r->aux = 0; + responderror(r); + free(p); + free(buf); + threadexits(nil); + } + } + free(p); + } + else { + if (client_auth(c, c->dio) < 0) { + r->aux = 0; + respond(r, "auth failure"); + free(buf); + threadexits(nil); + } + } + qlock(&c->l); + if (c->state != Established) + rsleep(&c->r); + qunlock(&c->l); + if (c->state != Established) { + r->aux = 0; + respond(r, "Authentication failed"); + free(buf); + threadexits(nil); + } + break; + } + else if (strcmp(toks[0], "ssh-connection") == 0) { + } + else if (strcmp(toks[0], "hangup") == 0) { + if (c->rpid >= 0) { + threadint(c->rpid); + } + shutdown(c); + } + else if (strcmp(toks[0], "announce") == 0) { + if (debug) + print("Got %s argument for announce\n", toks[1]); + write(c->ctlfd, r->ifcall.data, r->ifcall.count); + } + else if (strcmp(toks[0], "accept") == 0) { + memset(buf2, '\0', sizeof(buf2)); + pread(c->ctlfd, buf2, 10, 0); + fprint(c->ctlfd, "accept %s", buf2); + c->role = Server; + q = estrdup9p(buf2); + if (dohandshake(c, q) < 0) { + r->aux = 0; + respond(r, "handshake failed"); + free(q); + shutdown(c); + free(buf); + threadexits(nil); + } + free(q); + } + else if (strcmp(toks[0], "reject") == 0) { + memset(buf2, '\0', sizeof(buf2)); + pread(c->ctlfd, buf2, 10, 0); + snprint(path, 40, "%s/tcp/%s/data", mntpt, buf2); + c->datafd = open(path, ORDWR); + p = new_packet(c); + add_byte(p, SSH_MSG_DISCONNECT); + add_byte(p, SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT); + add_string(p, toks[2]); + add_string(p, "EN"); + n = finish_packet(p); + if (c->dio && c->datafd >= 0) + iowrite(c->dio, c->datafd, p->nlength, n); + free(p); + if (c->ctlfd >= 0) + fprint(c->ctlfd, "reject %s %s", buf, toks[2]); + if (c->rpid >= 0) { + threadint(c->rpid); + } + shutdown(c); + } + break; + case 2: + if (c->state == Closed || c->state == Closing) { + r->aux = 0; + respond(r, "connection closed"); + free(buf); + threadexits(nil); + } + if (strcmp(toks[0], "connect") == 0) { + p = new_packet(c); + add_byte(p, SSH_MSG_CHANNEL_OPEN); + if (ntok > 1) + add_string(p, toks[1]); + else + add_string(p, "session"); + add_uint32(p, ch->id); + add_uint32(p, 32*1024); + add_uint32(p, 8192); + /* more stuff if it's an x11 session */ + n = finish_packet(p); + iowrite(c->dio, c->datafd, p->nlength, n); + free(p); + qlock(&c->l); + if (ch->otherid == -1) + rsleep(&ch->r); + qunlock(&c->l); + break; + } + else if (strcmp(toks[0], "global") == 0) { + } + else if (strcmp(toks[0], "hangup") == 0) { + if (ch->state != Closed && ch->state != Closing) { + ch->state = Closing; + if (ch->otherid != -1) { + p = new_packet(c); + add_byte(p, SSH_MSG_CHANNEL_CLOSE); + add_uint32(p, ch->otherid); + n = finish_packet(p); + iowrite(c->dio, c->datafd, p->nlength, n); + free(p); + } + qlock(&c->l); + rwakeup(&ch->r); + qunlock(&c->l); + nbsendul(ch->inchan, 1); + nbsendul(ch->reqchan, 1); + } + for (n = 0; n < MAXCONN + && (!c->chans[n] || c->chans[n]->state == Empty || c->chans[n]->state == Closing + || c->chans[n]->state == Closed); ++n) ; + if (n >= MAXCONN) { + if (c->rpid >= 0) { + threadint(c->rpid); + } + shutdown(c); + } + } + else if (strcmp(toks[0], "announce") == 0) { + if (debug) + print("Got %s argument for announce\n", toks[1]); + if (ch->ann) + free(ch->ann); + ch->ann = estrdup9p(toks[1]); + } + break; + } + r->ofcall.count = r->ifcall.count; + r->aux = 0; + respond(r, nil); + free(buf); + threadexits(nil); +} + +void +writereqrem(void *a) +{ + Req *r; + Packet *p; + Conn *c; + SSHChan *ch; + char *q, *buf, *toks[4]; + int n, ntok, lev, fnum, xconn; + char *cmd; + + r = a; + fnum = (uintptr)r->fid->file->aux; + lev = fnum >> LEVSHIFT; + xconn = (fnum >> CONNSHIFT) & ConnMask; + c = connections[xconn]; + if (c == nil) { + respond(r, "Invalid connection"); + threadexits(nil); + } + ch = c->chans[fnum & ConnMask]; + if (r->ifcall.count <= 10) + buf = emalloc9p(11); + else + buf = emalloc9p(r->ifcall.count + 1); + memmove(buf, r->ifcall.data, r->ifcall.count); + buf[r->ifcall.count] = '\0'; + ntok = tokenize(buf, toks, 4); + + if (lev == 0) { + if (keymbox.msg) + free(keymbox.msg); + keymbox.msg = buf; + nbsendul(keymbox.mchan, 1); + r->ofcall.count = r->ifcall.count; + r->aux = 0; + respond(r,nil); + threadexits(nil); + } + + r->ofcall.count = r->ifcall.count; + if (c->state == Closed || c->state == Closing || ch->state == Closed || ch->state == Closing) { + r->aux = 0; + respond(r, nil); + free(buf); + threadexits(nil); + } + p = new_packet(c); + if (strcmp(toks[0], "success") == 0) { + add_byte(p, SSH_MSG_CHANNEL_SUCCESS); + add_uint32(p, ch->otherid); + } + else if (strcmp(toks[0], "failure") == 0) { + add_byte(p, SSH_MSG_CHANNEL_FAILURE); + add_uint32(p, ch->otherid); + } + else if (strcmp(toks[0], "close") == 0) { + ch->state = Closing; + add_byte(p, SSH_MSG_CHANNEL_CLOSE); + add_uint32(p, ch->otherid); + } + else if (strcmp(toks[0], "shell") == 0) { + ch->state = Established; + /* + * Some servers *cough*OpenSSH*cough* don't seem to be able + * to intelligently handle a shell with no pty. + */ + add_byte(p, SSH_MSG_CHANNEL_REQUEST); + add_uint32(p, ch->otherid); + add_string(p, "pty-req"); + add_byte(p, 0); + if (ntok == 1) + add_string(p, "dumb"); + else + add_string(p, toks[1]); + add_uint32(p, 0); + add_uint32(p, 0); + add_uint32(p, 0); + add_uint32(p, 0); + add_string(p, ""); + n = finish_packet(p); + iowrite(c->dio, c->datafd, p->nlength, n); + init_packet(p); + p->c = c; + add_byte(p, SSH_MSG_CHANNEL_REQUEST); + add_uint32(p, ch->otherid); + add_string(p, "shell"); + add_byte(p, 0); + if (debug) + fprint(2, "Sending shell request: rlength=%lud twindow=%lud\n", p->rlength, ch->twindow); + } + else if (strcmp(toks[0], "exec") == 0) { + ch->state = Established; + add_byte(p, SSH_MSG_CHANNEL_REQUEST); + add_uint32(p, ch->otherid); + add_string(p, "exec"); + add_byte(p, 0); + cmd = malloc(1024); + q = seprint(cmd, cmd+1024, "%s", toks[1]); + for (n = 2; n < ntok; ++n) { + q = seprint(q, cmd+1024, " %s", toks[n]); + if (q == nil) + break; + } + add_string(p, cmd); + free(cmd); + } + else { + r->aux = 0; + respond(r, "invalid request command"); + free(buf); + threadexits(nil); + } + n = finish_packet(p); + iowrite(c->dio, c->datafd, p->nlength, n); + free(p); + r->aux = 0; + respond(r, nil); + free(buf); + threadexits(nil); +} + + +void +writedata(void *a) +{ + Req *r; + Packet *p; + Conn *c; + SSHChan *ch; + int n, fnum, xconn; + + r = a; + fnum = (uintptr)r->fid->file->aux; + xconn = (fnum >> CONNSHIFT) & ConnMask; + c = connections[xconn]; + if (c == nil) { + respond(r, "Invalid connection"); + threadexits(nil); + } + ch = c->chans[fnum & ConnMask]; + p = new_packet(c); + add_byte(p, SSH_MSG_CHANNEL_DATA); + hnputl(p->payload+1, ch->otherid); + p->rlength += 4; + add_block(p, r->ifcall.data, r->ifcall.count); + n = finish_packet(p); + if (ch->sent + p->rlength <= ch->twindow) { + iowrite(c->dio, c->datafd, p->nlength, n); + r->aux = 0; + respond(r, nil); + free(p); + threadexits(nil); + } + qlock(&ch->xmtlock); + while (ch->sent + p->rlength > ch->twindow) + rsleep(&ch->xmtrendez); + qunlock(&ch->xmtlock); + iowrite(c->dio, c->datafd, p->nlength, n); + free(p); + r->aux = 0; + respond(r, nil); + threadexits(nil); +} + +/* + * Although this is named stclunk, it's attached to the destroyfid + * member of the Srv struct. It turns out there's no member + * called clunk. But if there are no other references, a 9P Tclunk + * will end up calling destroyfid. + */ +void +stclunk(Fid *f) +{ + Packet *p; + Conn *c; + SSHChan *sc; + int n, fnum, lev, cnum, chnum; + + if (f == nil || f->file == nil) + return; + fnum = (uintptr)f->file->aux; + lev = fnum >> LEVSHIFT; + cnum = (fnum >> CONNSHIFT) & ConnMask; + chnum = fnum & ConnMask; + if (debug) + fprint(2, "Got destroy fid on file: %x %d %d %d: %s\n", fnum, lev, cnum, chnum, f->file->name); + if (lev == 0 && fnum == ReqRemFile) { + if (keymbox.state != Empty) { + keymbox.state = Empty; + //nbsendul(keymbox.mchan, 1); + } + keymbox.msg = nil; + return; + } + c = connections[cnum]; + if (c == nil) + return; + if (lev == 1 && (fnum & FileMask) == CtlFile + && (c->state == Opening || c->state == Negotiating + || c->state == Authing)) { + for (n = 0; n < MAXCONN + && (!c->chans[n] || c->chans[n]->state == Empty || c->chans[n]->state == Closed || c->chans[n]->state == Closing); ++n) ; + if (n >= MAXCONN) { + if (c->rpid >= 0) { + threadint(c->rpid); + } + shutdown(c); + } + return; + } + sc = c->chans[chnum]; + if (lev == 2) { + if ((fnum & FileMask) == ListenFile && sc->state == Listening) { + qlock(&c->l); + if (sc->state != Closed) { + sc->state = Closed; + chanclose(sc->inchan); + chanclose(sc->reqchan); + } + qunlock(&c->l); + } + else if ((fnum & FileMask) == DataFile && sc->state != Empty + && sc->state != Closed && sc->state != Closing) { + if (f->file != sc->data && f->file != sc->request) { + fprint(2, "Great evil is upon us destroying a fid we didn't create\n"); + return; + } + p = new_packet(c); + add_byte(p, SSH_MSG_CHANNEL_CLOSE); + hnputl(p->payload+1, sc->otherid); + p->rlength += 4; + n = finish_packet(p); + sc->state = Closing; + iowrite(c->dio, c->datafd, p->nlength, n); + free(p); + qlock(&c->l); + rwakeup(&sc->r); + qunlock(&c->l); + nbsendul(sc->inchan, 1); + nbsendul(sc->reqchan, 1); + } + for (n = 0; n < MAXCONN + && (!c->chans[n] || c->chans[n]->state == Empty || c->chans[n]->state == Closed || c->chans[n]->state == Closing); ++n) ; + if (n >= MAXCONN) { + if (c->rpid >= 0) { + threadint(c->rpid); + } + shutdown(c); + } + } +} + +void +stflush(Req *r) +{ + int fnum; + + fnum = (uintptr)r->oldreq->fid->file->aux; + if (debug) + fprint(2, "Got flush on file %x %d %d %d: %s %p\n", fnum, fnum >> LEVSHIFT, (fnum >> CONNSHIFT) & ConnMask, fnum & ConnMask, r->oldreq->fid->file->name, r->oldreq->aux); + if (r->oldreq->aux) { + if (r->oldreq->ifcall.type == Topen && (fnum & FileMask) == ListenFile && (fnum >> LEVSHIFT) == 1) { + threadint((uintptr)r->oldreq->aux); + } + else if(r->oldreq->ifcall.type == Tread && (fnum & FileMask) == DataFile && (fnum >> LEVSHIFT) == 2) { + threadint((uintptr)r->oldreq->aux); + } + else if(r->oldreq->ifcall.type == Tread && (fnum & FileMask) == ReqRemFile) { + threadint((uintptr)r->oldreq->aux); + } + else { + threadkill((uintptr)r->oldreq->aux); + r->oldreq->aux = 0; + respond(r->oldreq, "interrupted"); + } + } + else + respond(r->oldreq, "interrupted"); + respond(r, nil); +} + +void +filedup(Req *r, File *src) +{ + r->ofcall.qid = src->qid; + closefile(r->fid->file); + r->fid->file = src; + incref(src); +} + +Conn * +alloc_conn(void) +{ + static QLock aclock; + Conn *c; + int sconn, slev, i, s, firstnil; + char buf[20]; + + qlock(&aclock); + firstnil = -1; + for (i = 0; i < MAXCONN; ++i) { + if (connections[i] == nil) { + if (firstnil == -1) + firstnil = i; + continue; + } + s = connections[i]->state; + if (s == Empty || s == Closed) + break; + } + if (i >= MAXCONN) { + if (firstnil != -1) { + connections[firstnil] = emalloc9p(sizeof (Conn)); + memset(connections[firstnil], 0, sizeof (Conn)); + i = firstnil; + } + else { + qunlock(&aclock); + return nil; + } + } + sconn = i << CONNSHIFT; + c = connections[i]; + memset(&c->r, '\0', sizeof(Rendez)); + c->r.l = &c->l; + c->dio = ioproc(); + c->rio = nil; + c->state = Allocated; + c->role = Server; + c->id = i; + c->user = nil; + c->service = nil; + c->nchan = 0; + c->ctlfd = -1; + c->datafd = -1; + c->rpid = -1; + c->inseq = 0; + c->outseq = 0; + c->cscrypt = -1; + c->sccrypt = -1; + c->csmac = -1; + c->scmac = -1; + c->ncscrypt = -1; + c->nsccrypt = -1; + c->ncsmac = -1; + c->nscmac = -1; + c->encrypt = -1; + c->decrypt = -1; + c->outmac = -1; + c->inmac = -1; + if (c->e) { + mpfree(c->e); + c->e = nil; + } + if (c->x) { + mpfree(c->x); + c->x = nil; + } + slev = 1 << LEVSHIFT; + snprint(buf, 20, "%d", i); + if (c->dir == nil) { + c->dir = createfile(rootfile, buf, uid, 0555|DMDIR, (void *)(slev | sconn)); + c->clonefile = createfile(c->dir, "clone", uid, 0666, (void *)(slev | CloneFile | sconn)); + c->ctlfile = createfile(c->dir, "ctl", uid, 0666, (void *)(slev | CtlFile | sconn)); + c->datafile = createfile(c->dir, "data", uid, 0666, (void *)(slev | DataFile | sconn)); + c->listenfile = createfile(c->dir, "listen", uid, 0666, (void *)(slev | ListenFile | sconn)); + c->localfile = createfile(c->dir, "local", uid, 0444, (void *)(slev | LocalFile | sconn)); + c->remotefile = createfile(c->dir, "remote", uid, 0444, (void *)(slev | ReqRemFile | sconn)); + c->statusfile = createfile(c->dir, "status", uid, 0444, (void *)(slev | StatusFile | sconn)); + } +// c->skexinit = nil; +// c->rkexinit = nil; + c->got_sessid = 0; + c->otherid = nil; + c->inik = nil; + c->outik = nil; + c->s2ccs = nil; + c->c2scs = nil; + c->enccs = nil; + c->deccs = nil; + qunlock(&aclock); + return c; +} + +SSHChan * +alloc_chan(Conn *c) +{ + SSHChan *sc; + Plist *p; + int cnum, slev, sconn; + char buf[10]; + + if (c->nchan >= MAXCONN) + return nil; + qlock(&c->l); + cnum = c->nchan; + if (c->chans[cnum] == nil) { + c->chans[cnum] = emalloc9p(sizeof (SSHChan)); + memset(c->chans[cnum], 0, sizeof (SSHChan)); + } + sc = c->chans[cnum]; + snprint(buf, 10, "%d", cnum); + memset(&sc->r, '\0', sizeof(Rendez)); + sc->r.l = &c->l; + sc->id = cnum; + sc->otherid = -1; + sc->state = Empty; + sc->waker = -1; + sc->conn = c->id; + sc->sent = 0; + sc->twindow = 0; + sc->rwindow = 0; + sc->inrqueue = 0; + sc->ann = nil; + sc->lreq = nil; + slev = 2 << LEVSHIFT; + sconn = c->id << CONNSHIFT; + if (sc->dir == nil) { + sc->dir = createfile(c->dir, buf, uid, 0555|DMDIR, (void *)(slev | sconn | cnum)); + sc->ctl = createfile(sc->dir, "ctl", uid, 0666, (void *)(slev | CtlFile | sconn | cnum)); + sc->data = createfile(sc->dir, "data", uid, 0666, (void *)(slev | DataFile | sconn | cnum)); + sc->listen = createfile(sc->dir, "listen", uid, 0666, (void *)(slev | ListenFile | sconn | cnum)); + sc->request = createfile(sc->dir, "request", uid, 0666, + (void *)(slev | ReqRemFile | sconn | cnum)); + sc->status = createfile(sc->dir, "status", uid, 0444, (void *)(slev | StatusFile | sconn | cnum)); + } + c->nchan++; + sc->dataq = nil; + sc->datatl = nil; + while (sc->reqq != nil) { + p = sc->reqq; + sc->reqq = p->next; + free(p->pack); + free(p); + } + sc->reqtl = nil; + if (sc->inchan) + chanfree(sc->inchan); + sc->inchan = chancreate(4, 0); + if (sc->reqchan) + chanfree(sc->reqchan); + sc->reqchan = chancreate(4, 0); + memset(&sc->xmtrendez, '\0', sizeof(Rendez)); + sc->xmtrendez.l = &sc->xmtlock; + qunlock(&c->l); + return sc; +} + +int +dohandshake(Conn *c, char *tcpchan) +{ + Ioproc *io; + char *p; + int fd, n; + char path[256], buf[32]; + + io = ioproc(); + snprint(path, 256, "%s/tcp/%s/remote", mntpt, tcpchan); + fd = ioopen(io, path, OREAD); + n = ioread(io, fd, buf, 31); + if (n > 0) { + buf[n] = 0; + p = strchr(buf, '!'); + if (p) + *p = 0; + if (c->remote) + free(c->remote); + c->remote = estrdup9p(buf); + } + ioclose(io, fd); + snprint(path, 256, "%s/tcp/%s/data", mntpt, tcpchan); + fd = ioopen(io, path, ORDWR); + if (fd < 0) { + closeioproc(io); + return -1; + } + c->datafd = fd; + + /* exchange versions--we're snobbishly only doing SSH2 */ + + snprint(path, 256, "%s\r\n", MYID); + iowrite(io, fd, path, strlen(path)); + p = path; + n = 0; + do { + if (ioread(io, fd, p, 1) < 0) { + fprint(2, "Read failure in ID exchange: %r\n"); + break; + } + ++n; + } while (*p++ != '\n'); + if (n < 5) { + close(fd); + snprint(path, 256, "%s/tcp/%s/ctl", mntpt, tcpchan); + fd = ioopen(io, path, OWRITE); + iowrite(io, fd, "hangup", 6); + ioclose(io, fd); + closeioproc(io); + return -1; + } + *p = 0; + if (debug) + fprint(2, "id string: %d:%s", n, path); + if (strncmp(path, "SSH-2", 5) != 0 && strncmp(path, "SSH-1.99", 8) != 0) { + ioclose(io, fd); + snprint(path, 256, "%s/tcp/%s/ctl", mntpt, tcpchan); + fd = ioopen(io,path, OWRITE); + iowrite(io, fd, "hangup", 6); + ioclose(io, fd); + closeioproc(io); + return -1; + } + closeioproc(io); + if (c->otherid) + free(c->otherid); + c->otherid = estrdup9p(path); + for (n = strlen(c->otherid) - 1; c->otherid[n] == '\r' || c->otherid[n] == '\n'; --n) + c->otherid[n] = '\0'; + c->state = Initting; + + /* start the reader thread */ + if (c->rpid < 0) + c->rpid = threadcreate(reader, c, 8192); + + /* + * negotiate the protocols + * We don't do the full negotiation here, because we also have + * to handle a re-negotiation request from the other end. So + * we just kick it off and let the receiver process take it from there. + */ + + send_kexinit(c); + + qlock(&c->l); + if ((c->role == Client && c->state != Negotiating) || (c->role == Server && c->state != Established)) + rsleep(&c->r); + qunlock(&c->l); + if (c->role == Server && c->state != Established) + return -1; + if (c->role == Client && c->state != Negotiating) + return -1; + return 0; +} + +void +send_kexinit(Conn *c) +{ + Packet *ptmp; + char *p, *e; + int msglen; + int i; + char *buf; + + if (debug) + fprint(2, "Initializing kexinit packet\n"); + if (c->skexinit != nil) + free(c->skexinit); + c->skexinit = new_packet(c); + buf = emalloc9p(1024); + buf[0] = (uchar)SSH_MSG_KEXINIT; + add_packet(c->skexinit, buf, 1); + for (i = 0; i < 16; ++i) + buf[i] = fastrand(); + add_packet(c->skexinit, buf, 16); /* cookie */ + e = buf+1023; + p = seprint(buf, e, "%s", kexes[0]->name); + for (i = 1; i < nelem(kexes); ++i) + p = seprint(p, e, ",%s", kexes[i]->name); + if (debug) + fprint(2, "Sent KEX algs: %s\n", buf); + add_string(c->skexinit, buf); /* Key exchange */ + if (pkas[0] == nil) + add_string(c->skexinit, ""); + else{ + p = seprint(buf, e, "%s", pkas[0]->name); + for (i = 1; i < nelem(pkas) && pkas[i] != nil; ++i) + p = seprint(p, e, ",%s", pkas[i]->name); + if (debug) + fprint(2, "Sent host key algs: %s\n", buf); + add_string(c->skexinit, buf); /* server's key algs */ + } + p = seprint(buf, e, "%s", cryptos[0]->name); + for (i = 1; i < nelem(cryptos); ++i) + p = seprint(p, e, ",%s", cryptos[i]->name); + if (debug) + fprint(2, "Sent crypto algs: %s\n", buf); + add_string(c->skexinit, buf); /* c->s crypto */ + add_string(c->skexinit, buf); /* s->c crypto */ + p = seprint(buf, e, "%s", macnames[0]); + for (i = 1; i < nelem(macnames); ++i) + p = seprint(p, e, ",%s", macnames[i]); + if (debug) + fprint(2, "Sent MAC algs: %s\n", buf); + add_string(c->skexinit, buf); /* c->s mac */ + add_string(c->skexinit, buf); /* s->c mac */ + add_string(c->skexinit, "none"); /* c->s compression */ + add_string(c->skexinit, "none"); /* s->c compression */ + add_string(c->skexinit, ""); /* c->s languages */ + add_string(c->skexinit, ""); /* s->c languages */ + memset(buf, 0, 5); + add_packet(c->skexinit, buf, 5); + ptmp = new_packet(c); + memmove(ptmp, c->skexinit, sizeof(Packet)); + msglen = finish_packet(ptmp); + if (c->dio && c->datafd >= 0) + iowrite(c->dio, c->datafd, ptmp->nlength, msglen); + free(ptmp); + free(buf); +} + +void +reader(void *a) +{ + Packet *p, *p2; + Plist *pl; + Conn *c; + SSHChan *ch; + uchar *q; + int i, n,nl, np, nm, nb, cnum; + char buf[256]; + + c = a; + c->rpid = threadid(); + if (debug) + fprint(2, "Starting reader for connection %d, pid:%d\n", c->id, c->rpid); + threadsetname("reader"); + p = new_packet(c); + p2 = new_packet(c); + c->rio = ioproc(); + while (1) { + nm = 0; + nb = 4; + if (c->decrypt != -1) + nb = cryptos[c->decrypt]->blklen; + if (debug) + fprint(2, "calling read for connection %d, state %d, nb %d, dc %d\n", + c->id, c->state, nb, c->decrypt); + if ((nl = ioreadn(c->rio, c->datafd, p->nlength, nb)) != nb) { + if (debug) + fprint(2, "Reader for connection %d exiting, nl=%d: %r\n", c->id, nl); + goto bail; + } + if (c->decrypt != -1) + cryptos[c->decrypt]->decrypt(c->deccs, p->nlength, nb); + p->rlength = nhgetl(p->nlength); + if (debug) + fprint(2, "Got message length: %ld\n", p->rlength); + if (p->rlength > 35000) { + if (debug) + fprint(2, "Absurd packet length: %ld, unrecoverable decrypt failure\n", p->rlength); + goto bail; + } + np = ioreadn(c->rio, c->datafd, p->nlength+nb, p->rlength + 4 - nb); + if (c->inmac != -1) + nm = ioreadn(c->rio, c->datafd, p->nlength + p->rlength + 4, 20); + n = nl + np + nm; + if (debug) { + fprint(2, "got message of %d bytes %d padding", n, p->pad_len); + if (p->payload[0] > SSH_MSG_CHANNEL_OPEN) { + i = nhgetl(p->payload+1); + if (c->chans[i]) + fprint(2, " for channel %d win %lud", i, c->chans[i]->rwindow); + else + fprint(2, " for invalid channel %d", i); + } + fprint(2, ": first byte: %d\n", p->payload[0]); + } + if (np != p->rlength + 4 - nb || c->inmac != -1 && nm != 20) { + if (debug) + fprint(2, "Got EOF/error on connection read: %d %d %r\n", np, nm); + goto bail; + } + p->tlength = n; + p->rlength = n - 4; + if (undo_packet(p) < 0) { + if (debug) + fprint(2, "Bad packet in connection %d: exiting\n", c->id); + goto bail; + } + if (c->state == Initting) { + if (p->payload[0] != SSH_MSG_KEXINIT) { + if (debug) + fprint(2, "Missing KEX init packet: %d\n", p->payload[0]); + goto bail; + } + if (c->rkexinit) + free(c->rkexinit); + c->rkexinit = new_packet(c); + memmove(c->rkexinit, p, sizeof(Packet)); + if (validatekex(c, p) < 0) { + if (debug) + fprint(2, "Algorithm mismatch\n"); + goto bail; + } + if (debug) + fprint(2, "Using %s Kex algorithm and %s PKA\n", + kexes[c->kexalg]->name, pkas[c->pkalg]->name); + if (c->role == Client) + kexes[c->kexalg]->clientkex1(c, p); + c->state = Negotiating; + } + else if (c->state == Negotiating) { + switch (p->payload[0]) { + case SSH_MSG_DISCONNECT: + if (debug) { + get_string(p, p->payload + 5, buf, 256, nil); + fprint(2, "Got disconnect: %s\n", buf); + } + goto bail; + break; + case SSH_MSG_NEWKEYS: + /* + * If we're just updating, go straight to established, + * otherwise wait for authentication + */ + i = c->encrypt; + memmove(c->c2siv, c->nc2siv, SHA1dlen*2); + memmove(c->s2civ, c->ns2civ, SHA1dlen*2); + memmove(c->c2sek, c->nc2sek, SHA1dlen*2); + memmove(c->s2cek, c->ns2cek, SHA1dlen*2); + memmove(c->c2sik, c->nc2sik, SHA1dlen*2); + memmove(c->s2cik, c->ns2cik, SHA1dlen*2); + c->cscrypt = c->ncscrypt; + c->sccrypt = c->nsccrypt; + c->csmac = c->ncsmac; + c->scmac = c->nscmac; + c->c2scs = cryptos[c->cscrypt]->init(c, 0); + c->s2ccs = cryptos[c->sccrypt]->init(c, 1); + if (c->role == Server) { + c->encrypt = c->sccrypt; + c->decrypt = c->cscrypt; + c->outmac = c->scmac; + c->inmac = c->csmac; + c->enccs = c->s2ccs; + c->deccs = c->c2scs; + c->outik = c->s2cik; + c->inik = c->c2sik; + } + else{ + c->encrypt = c->cscrypt; + c->decrypt = c->sccrypt; + c->outmac = c->csmac; + c->inmac = c->scmac; + c->enccs = c->c2scs; + c->deccs = c->s2ccs; + c->outik = c->c2sik; + c->inik = c->s2cik; + } + if (debug) + fprint(2, "Using %s for encryption and %s for decryption\n", + cryptos[c->encrypt]->name, cryptos[c->decrypt]->name); + qlock(&c->l); + if (i != -1) + c->state = Established; + if (c->role == Client) { + rwakeup(&c->r); + } + qunlock(&c->l); + break; + case SSH_MSG_KEXDH_INIT: + kexes[c->kexalg]->serverkex(c, p); + break; + case SSH_MSG_KEXDH_REPLY: + init_packet(p2); + p2->c = c; + if (kexes[c->kexalg]->clientkex2(c, p) >= 0) { + add_byte(p2, SSH_MSG_NEWKEYS); + n = finish_packet(p2); + iowrite(c->rio, c->datafd, p2->nlength, n); + qlock(&c->l); + rwakeup(&c->r); + qunlock(&c->l); + } + else{ + add_byte(p2, SSH_MSG_DISCONNECT); + add_byte(p2, SSH_DISCONNECT_KEY_EXCHANGE_FAILED); + add_string(p2, "Key exchange failure"); + add_string(p2, ""); + n = finish_packet(p2); + iowrite(c->rio, c->datafd, p2->nlength, n); + shutdown(c); + free(p); + free(p2); + closeioproc(c->rio); + c->rio = nil; + c->rpid = -1; + qlock(&c->l); + rwakeup(&c->r); + qunlock(&c->l); + threadexits(nil); + } + break; + case SSH_MSG_SERVICE_REQUEST: + get_string(p, p->payload + 1, buf, 256, nil); + if (debug) + fprint(2, "Got service request: %s\n", buf); + if (strcmp(buf, "ssh-userauth") == 0 || strcmp(buf, "ssh-connection") == 0) { + init_packet(p2); + p2->c = c; + if (slfd > 0) + fprint(slfd, "ssh connection from %s\n", c->remote); + else + syslog(1, "ssh", "ssh connection from %s", c->remote); + add_byte(p2, SSH_MSG_SERVICE_ACCEPT); + add_string(p2, buf); + n = finish_packet(p2); + iowrite(c->rio, c->datafd, p2->nlength, n); + c->state = Authing; + } + else{ + init_packet(p2); + p2->c = c; + add_byte(p2, SSH_MSG_DISCONNECT); + add_byte(p2, SSH_DISCONNECT_SERVICE_NOT_AVAILABLE); + add_string(p2, "Unknown service type"); + add_string(p2, ""); + n = finish_packet(p2); + iowrite(c->rio, c->datafd, p2->nlength, n); + goto bail; + } + break; + case SSH_MSG_SERVICE_ACCEPT: + get_string(p, p->payload + 1, buf, 256, nil); + if (c->service && strcmp(c->service, "ssh-userauth") == 0) { + free(c->service); + c->service = estrdup9p("ssh-connection"); + } + if (debug) + fprint(2, "Got service accept: %s: responding with %s %s\n", buf, c->user, c->service); + n = client_auth(c, c->rio); + c->state = Authing; + if (n < 0) { + qlock(&c->l); + rwakeup(&c->r); + qunlock(&c->l); + } + break; + default: + break; + } + } + else if (c->state == Authing) { + switch (p->payload[0]) { + case SSH_MSG_DISCONNECT: + if (debug) { + get_string(p, p->payload + 5, buf, 256, nil); + fprint(2, "Got disconnect: %s\n", buf); + } + goto bail; + break; + case SSH_MSG_USERAUTH_REQUEST: + switch (auth_req(p, c)) { + case 0: + qlock(&c->l); + c->state = Established; + rwakeup(&c->r); + qunlock(&c->l); + break; + case -1: + break; + case -2: + goto bail; + break; + } + break; + case SSH_MSG_USERAUTH_FAILURE: + qlock(&c->l); + rwakeup(&c->r); + qunlock(&c->l); + break; + case SSH_MSG_USERAUTH_SUCCESS: + qlock(&c->l); + c->state = Established; + rwakeup(&c->r); + qunlock(&c->l); + break; + case SSH_MSG_USERAUTH_BANNER: + break; + } + } + else if (c->state == Established) { + if (debug >1) { + fprint(2, "In Established state, got:\n"); + dump_packet(p); + } + switch (p->payload[0]) { + case SSH_MSG_DISCONNECT: + if (debug) { + get_string(p, p->payload + 5, buf, 256, nil); + fprint(2, "Got disconnect: %s\n", buf); + } + goto bail; + break; + case SSH_MSG_IGNORE: + break; + case SSH_MSG_UNIMPLEMENTED: + break; + case SSH_MSG_DEBUG: + if (debug || p->payload[1]) { + get_string(p, p->payload + 2, buf, 256, nil); + fprint(2, "Got debug message: %s\n", buf); + } + break; + case SSH_MSG_KEXINIT: + send_kexinit(c); + if (c->rkexinit) + free(c->rkexinit); + c->rkexinit = new_packet(c); + memmove(c->rkexinit, p, sizeof(Packet)); + if (validatekex(c, p) < 0) { + if (debug) + fprint(2, "Algorithm mismatch\n"); + goto bail; + } + if (debug) + fprint(2, "Using %s Kex algorithm and %s PKA\n", + kexes[c->kexalg]->name, pkas[c->pkalg]->name); + c->state = Negotiating; + break; + case SSH_MSG_GLOBAL_REQUEST: + break; + case SSH_MSG_REQUEST_SUCCESS: + break; + case SSH_MSG_REQUEST_FAILURE: + break; + case SSH_MSG_CHANNEL_OPEN: + q = get_string(p, p->payload + 1, buf, 256, nil); + if (debug) + fprint(2, "Searching for a listener for channel type %s\n", buf); + ch = alloc_chan(c); + if (ch == nil) { + init_packet(p2); + p2->c = c; + add_byte(p2, SSH_MSG_CHANNEL_OPEN_FAILURE); + add_block(p2, p->payload + 5, 4); + hnputl(p2->payload + p2->rlength - 1, 4); + p2->rlength += 4; + add_string(p2, "No available channels"); + add_string(p2, "EN"); + n = finish_packet(p2); + iowrite(c->rio, c->datafd, p2->nlength, n); + break; + } + if (debug) + fprint(2, "alloced channel %d for listener\n", ch->id); + qlock(&c->l); + ch->otherid = nhgetl(q); + ch->twindow = nhgetl(q+4); + if (debug) + fprint(2, "Got lock in channel open\n"); + for (i = 0; i < c->nchan; ++i) + if (c->chans[i] && c->chans[i]->state == Listening && c->chans[i]->ann + && strcmp(c->chans[i]->ann, buf) == 0) + break; + if (i >= c->nchan) { + if (debug) + fprint(2, "No listener: sleeping\n"); + ch->state = Opening; + if (ch->ann) + free(ch->ann); + ch->ann = estrdup9p(buf); + if (debug) + fprint(2, "Waiting for someone to announce %s\n", ch->ann); + rsleep(&ch->r); + } + else{ + if (debug) + fprint(2, "Found listener on channel %d\n", ch->id); + c->chans[i]->waker = ch->id; + rwakeup(&c->chans[i]->r); + } + qunlock(&c->l); + break; + case SSH_MSG_CHANNEL_OPEN_CONFIRMATION: + cnum = nhgetl(p->payload + 1); + ch = c->chans[cnum]; + qlock(&c->l); + ch->otherid = nhgetl(p->payload+5); + ch->twindow = nhgetl(p->payload+9); + rwakeup(&ch->r); + qunlock(&c->l); + break; + case SSH_MSG_CHANNEL_OPEN_FAILURE: + cnum = nhgetl(p->payload + 1); + ch = c->chans[cnum]; + qlock(&c->l); + rwakeup(&ch->r); + qunlock(&c->l); + goto bail; + break; + case SSH_MSG_CHANNEL_WINDOW_ADJUST: + cnum = nhgetl(p->payload + 1); + ch = c->chans[cnum]; + ch->twindow += nhgetl(p->payload + 5); + if (debug) + fprint(2, "New twindow for channel: %d: %lud\n", cnum, ch->twindow); + qlock(&ch->xmtlock); + rwakeup(&ch->xmtrendez); + qunlock(&ch->xmtlock); + break; + case SSH_MSG_CHANNEL_DATA: + case SSH_MSG_CHANNEL_EXTENDED_DATA: + cnum = nhgetl(p->payload + 1); + ch = c->chans[cnum]; + pl = emalloc9p(sizeof(Plist)); + pl->pack = emalloc9p(sizeof(Packet)); + memmove(pl->pack, p, sizeof(Packet)); + if (p->payload[0] == SSH_MSG_CHANNEL_DATA) { + pl->rem = nhgetl(p->payload+5); + pl->st = pl->pack->payload+9; + } + else { + pl->rem = nhgetl(p->payload+9); + pl->st = pl->pack->payload+13; + } + pl->next = nil; + if (ch->dataq == nil) + ch->dataq = pl; + else + ch->datatl->next = pl; + ch->datatl = pl; + ch->inrqueue += pl->rem; + nbsendul(ch->inchan, 1); + break; + case SSH_MSG_CHANNEL_EOF: + cnum = nhgetl(p->payload + 1); + ch = c->chans[cnum]; + if (ch->state != Closed && ch->state != Closing) { + ch->state = Eof; + nbsendul(ch->inchan, 1); + nbsendul(ch->reqchan, 1); + } + break; + case SSH_MSG_CHANNEL_CLOSE: + cnum = nhgetl(p->payload + 1); + ch = c->chans[cnum]; + if (ch->state != Closed && ch->state != Closing) { + init_packet(p2); + p2->c = c; + add_byte(p2, SSH_MSG_CHANNEL_CLOSE); + hnputl(p2->payload + 1, ch->otherid); + p2->rlength += 4; + n = finish_packet(p2); + iowrite(c->rio, c->datafd, p2->nlength, n); + } + qlock(&c->l); + if (ch->state != Closed) { + ch->state = Closed; + rwakeup(&ch->r); + nbsendul(ch->inchan, 1); + nbsendul(ch->reqchan, 1); + chanclose(ch->inchan); + chanclose(ch->reqchan); + } + qunlock(&c->l); + for (i = 0; i < MAXCONN && (!c->chans[i] || c->chans[i]->state == Empty || c->chans[i]->state == Closed); ++i) ; + if (i >= MAXCONN) { + goto bail; + } + break; + case SSH_MSG_CHANNEL_REQUEST: + cnum = nhgetl(p->payload + 1); + ch = c->chans[cnum]; + if (debug) + fprint(2, "Queueing channel request for channel: %d\n", cnum); + q = get_string(p, p->payload+5, buf, 256, nil); + pl = emalloc9p(sizeof(Plist)); + pl->pack = emalloc9p(sizeof(Packet)); + n = snprint((char *)pl->pack->payload, 32768, "%s %c", buf, *q ? 't': 'f'); + if (debug) + fprint(2, "request message begins: %s\n", (char *)pl->pack->payload); + memmove(pl->pack->payload + n, q+1, p->rlength - (11 + (n-2))); + pl->rem = p->rlength - 11 + 2; + pl->st = pl->pack->payload; + pl->next = nil; + if (ch->reqq == nil) + ch->reqq = pl; + else + ch->reqtl->next = pl; + ch->reqtl = pl; + nbsendul(ch->reqchan, 1); + break; + case SSH_MSG_CHANNEL_SUCCESS: + break; + case SSH_MSG_CHANNEL_FAILURE: + break; + default: + break; + } + } + else { + if (debug) + fprint(2, "Connection %d in invalid state, reader exiting\n", c->id); +bail: + shutdown(c); + free(p); + free(p2); + if (c->rio) { + closeioproc(c->rio); + c->rio = nil; + } + c->rpid = -1; + threadexits(nil); + } + } +} + +int +validatekex(Conn *c, Packet *p) +{ + if (c->role == Server) + return validatekexs(p); + else + return validatekexc(p); +} + +int +validatekexs(Packet *p) +{ + uchar *q; + char *toks[128]; + int i, j, n; + char *buf; + + buf = emalloc9p(1024); + q = p->payload + 17; + q = get_string(p, q, buf, 1024, nil); + if (debug) + fprint(2, "Received KEX algs: %s\n", buf); + n = gettokens(buf, toks, 128, ","); + for (i = 0; i < n; ++i) + for (j = 0; j < nelem(kexes); ++j) + if (strcmp(toks[i], kexes[j]->name) == 0) + goto foundk; + free(buf); + return -1; +foundk: + p->c->kexalg = j; + q = get_string(p, q, buf, 1024, nil); + if (debug) + fprint(2, "Received host key algs: %s\n", buf); + n = gettokens(buf, toks, 128, ","); + for (i = 0; i < n; ++i) + for (j = 0; j < nelem(pkas) && pkas[j] != nil; ++j) + if (strcmp(toks[i], pkas[j]->name) == 0) + goto foundpka; + free(buf); + return -1; +foundpka: + p->c->pkalg = j; + q = get_string(p, q, buf, 1024, nil); + if (debug) + fprint(2, "Received C2S crypto algs: %s\n", buf); + n = gettokens(buf, toks, 128, ","); + for (i = 0; i < n; ++i) + for (j = 0; j < nelem(cryptos); ++j) + if (strcmp(toks[i], cryptos[j]->name) == 0) + goto foundc1; + free(buf); + return -1; +foundc1: + p->c->ncscrypt = j; + q = get_string(p, q, buf, 1024, nil); + if (debug) + fprint(2, "Received S2C crypto algs: %s\n", buf); + n = gettokens(buf, toks, 128, ","); + for (i = 0; i < n; ++i) + for (j = 0; j < nelem(cryptos); ++j) + if (strcmp(toks[i], cryptos[j]->name) == 0) + goto foundc2; + free(buf); + return -1; +foundc2: + p->c->nsccrypt = j; + q = get_string(p, q, buf, 1024, nil); + if (debug) + fprint(2, "Received C2S MAC algs: %s\n", buf); + n = gettokens(buf, toks, 128, ","); + for (i = 0; i < n; ++i) + for (j = 0; j < nelem(macnames); ++j) + if (strcmp(toks[i], macnames[j]) == 0) + goto foundm1; + free(buf); + return -1; +foundm1: + p->c->ncsmac = j; + q = get_string(p, q, buf, 1024, nil); + if (debug) + fprint(2, "Received S2C MAC algs: %s\n", buf); + n = gettokens(buf, toks, 128, ","); + for (i = 0; i < n; ++i) + for (j = 0; j < nelem(macnames); ++j) + if (strcmp(toks[i], macnames[j]) == 0) + goto foundm2; + free(buf); + return -1; +foundm2: + p->c->nscmac = j; + q = get_string(p, q, buf, 1024, nil); + q = get_string(p, q, buf, 1024, nil); + q = get_string(p, q, buf, 1024, nil); + q = get_string(p, q, buf, 1024, nil); + free(buf); + if (*q) + return 1; + return 0; +} + +int +validatekexc(Packet *p) +{ + uchar *q; + char *toks[128]; + int i, j, n; + char *buf; + + buf = emalloc9p(1024); + q = p->payload + 17; + q = get_string(p, q, buf, 1024, nil); + n = gettokens(buf, toks, 128, ","); + for (j = 0; j < nelem(kexes); ++j) + for (i = 0; i < n; ++i) + if (strcmp(toks[i], kexes[j]->name) == 0) + goto foundk; + free(buf); + return -1; +foundk: + p->c->kexalg = j; + q = get_string(p, q, buf, 1024, nil); + n = gettokens(buf, toks, 128, ","); + for (j = 0; j < nelem(pkas) && pkas[j] != nil; ++j) + for (i = 0; i < n; ++i) + if (strcmp(toks[i], pkas[j]->name) == 0) + goto foundpka; + free(buf); + return -1; +foundpka: + p->c->pkalg = j; + q = get_string(p, q, buf, 1024, nil); + n = gettokens(buf, toks, 128, ","); + for (j = 0; j < nelem(cryptos); ++j) + for (i = 0; i < n; ++i) + if (strcmp(toks[i], cryptos[j]->name) == 0) + goto foundc1; + free(buf); + return -1; +foundc1: + p->c->ncscrypt = j; + q = get_string(p, q, buf, 1024, nil); + n = gettokens(buf, toks, 128, ","); + for (j = 0; j < nelem(cryptos); ++j) + for (i = 0; i < n; ++i) + if (strcmp(toks[i], cryptos[j]->name) == 0) + goto foundc2; + free(buf); + return -1; +foundc2: + p->c->nsccrypt = j; + q = get_string(p, q, buf, 1024, nil); + n = gettokens(buf, toks, 128, ","); + for (j = 0; j < nelem(macnames); ++j) + for (i = 0; i < n; ++i) + if (strcmp(toks[i], macnames[j]) == 0) + goto foundm1; + free(buf); + return -1; +foundm1: + p->c->ncsmac = j; + q = get_string(p, q, buf, 1024, nil); + n = gettokens(buf, toks, 128, ","); + for (j = 0; j < nelem(macnames); ++j) + for (i = 0; i < n; ++i) + if (strcmp(toks[i], macnames[j]) == 0) + goto foundm2; + free(buf); + return -1; +foundm2: + p->c->nscmac = j; + q = get_string(p, q, buf, 1024, nil); + q = get_string(p, q, buf, 1024, nil); + q = get_string(p, q, buf, 1024, nil); + q = get_string(p, q, buf, 1024, nil); + free(buf); + if (*q) + return 1; + return 0; +} + +int +memrandom(void *p, int n) +{ + uchar *cp; + + for (cp = (uchar*)p; n > 0; n--) + *cp++ = fastrand(); + return 0; +} + +/* + * create a change uid capability + */ +char* +mkcap(char *from, char *to) +{ + uchar rand[20]; + char *cap; + char *key; + int fd, nfrom, nto; + uchar hash[SHA1dlen]; + + fd = open("#ยค/caphash", OWRITE); + + /* create the capability */ + nto = strlen(to); + nfrom = strlen(from); + cap = emalloc9p(nfrom+1+nto+1+sizeof(rand)*3+1); + snprint(cap, nfrom+1+nto+1+sizeof(rand)*3+1, "%s@%s", from, to); + memrandom(rand, sizeof(rand)); + key = cap+nfrom+1+nto+1; + enc64(key, sizeof(rand)*3, rand, sizeof(rand)); + + /* hash the capability */ + hmac_sha1((uchar*)cap, strlen(cap), (uchar*)key, strlen(key), hash, nil); + + /* give the kernel the hash */ + key[-1] = '@'; + if (write(fd, hash, SHA1dlen) < 0) { + close(fd); + free(cap); + return nil; + } + close(fd); + return cap; +} + +int +auth_req(Packet *p, Conn *c) +{ + Packet *p2; + AuthInfo *ai; + uchar *q; + char *service, *me, *user, *pw, *path; + char *alg, *blob, *sig; + int n, fd, retval, nblob, nsig; + char method[32]; + char key1[DESKEYLEN], key2[DESKEYLEN]; + + service = emalloc9p(128); + me = emalloc9p(128); + user = emalloc9p(128); + pw = emalloc9p(128); + alg = emalloc9p(128); + blob = emalloc9p(512); + sig = emalloc9p(512); + path = emalloc9p(128); + retval = 0; + q = get_string(p, p->payload + 1, user, 128, nil); + if (c->user) + free(c->user); + c->user = estrdup9p(user); + q = get_string(p, q, service, 128, nil); + q = get_string(p, q, method, 32, nil); + if (debug) { + fprint(2, "Got userauth request: "); + fprint(2, " %s ", user); + fprint(2, " %s ", service); + fprint(2, " %s\n", method); + } + fd = open("/dev/user", OREAD); + n = read(fd, me, 127); + me[n] = '\0'; + close(fd); + p2 = new_packet(c); + if (strcmp(method, "publickey") == 0) { + if (*q == 0) { + /* Should really check to see if this user can be authed this way */ + q = get_string(p, q+1, alg, 128, nil); + get_string(p, q, blob, 512, &nblob); + for (n = 0; n < nelem(pkas) && pkas[n] != nil && strcmp(pkas[n]->name, alg) != 0; ++n) ; + if (n >= nelem(pkas) || pkas[n] == nil) { + add_byte(p2, SSH_MSG_USERAUTH_FAILURE); + add_string(p2, "password,publickey"); + add_byte(p2, 0); + retval = -1; + } + else { + add_byte(p2, SSH_MSG_USERAUTH_PK_OK); + add_string(p2, alg); + add_block(p2, blob, nblob); + retval = -1; + } + } + else { + q = get_string(p, q+1, alg, 128, nil); + q = get_string(p, q, blob, 512, &nblob); + get_string(p, q, sig, 512, &nsig); + + for (n = 0; n < nelem(pkas) && pkas[n] != nil && strcmp(pkas[n]->name, alg) != 0; ++n) ; + if (n >= nelem(pkas) || pkas[n] == nil) { + add_byte(p2, SSH_MSG_USERAUTH_FAILURE); + add_string(p2, "password,publickey"); + add_byte(p2, 0); + retval = -1; + } + else { + add_block(p2, c->sessid, SHA1dlen); + add_byte(p2, SSH_MSG_USERAUTH_REQUEST); + add_string(p2, user); + add_string(p2, service); + add_string(p2, method); + add_byte(p2, 1); + add_string(p2, alg); + add_block(p2, blob, nblob); + if (pkas[n]->verify(c, p2->payload, p2->rlength - 1, user, sig, nsig)) { + if (c->cap != nil) + free(c->cap); + c->cap = mkcap(me, user); + init_packet(p2); + p2->c = c; + if (slfd > 0) + fprint(slfd, "ssh logged in as %s\n", user); + else + syslog(1, "ssh", "ssh logged in as %s", user); + add_byte(p2, SSH_MSG_USERAUTH_SUCCESS); + } + else { + init_packet(p2); + p2->c = c; + if (slfd > 0) + fprint(slfd, "ssh public key login failure for %s\n", user); + else + syslog(1, "ssh", "ssh public key login failure for %s", user); + add_byte(p2, SSH_MSG_USERAUTH_FAILURE); + add_string(p2, "password,publickey"); + add_byte(p2, 0); + retval = -1; + } + } + } + } + else if (strcmp(method, "password") == 0) { + ++q; + get_string(p, q, pw, 128, nil); + if (debug > 1) + fprint(2, "%s\n", pw); + ai = nil; + if (kflag) { + if (passtokey(key1, pw) == 0) + goto answer; + snprint(path, 128, "/mnt/keys/%s/key", user); + if ((fd = open(path, OREAD)) < 0) { + werrstr("Invalid user"); + goto answer; + } + if (read(fd, key2, DESKEYLEN) != DESKEYLEN) { + werrstr("Password mismatch"); + goto answer; + } + close(fd); + if (memcmp(key1, key2, DESKEYLEN) != 0) { + werrstr("Password mismatch"); + goto answer; + } + ai = emalloc9p(sizeof(AuthInfo)); + ai->cuid = estrdup9p(user); + ai->suid = estrdup9p(me); + ai->cap = mkcap(me, user); + ai->nsecret = 0; + ai->secret = (uchar *)estrdup9p(""); + } + else + ai = auth_userpasswd(user, pw); +answer: + if (ai == nil) { + if (debug) + fprint(2, "Auth error: %r\n"); + if (slfd > 0) + fprint(slfd, "ssh login failure for %s: %r\n", user); + else + syslog(1, "ssh", "ssh login failure for %s: %r", user); + add_byte(p2, SSH_MSG_USERAUTH_FAILURE); + add_string(p2, "password,publickey"); + add_byte(p2, 0); + retval = -1; + } + else{ + if (debug) + fprint(2, "Auth successful: cuid is %s suid is %s cap is %s\n", ai->cuid, ai->suid, ai->cap); + if (c->cap != nil) + free(c->cap); + if (strcmp(user, me) == 0) + c->cap = estrdup9p("n/a"); + else + c->cap = estrdup9p(ai->cap); + if (slfd > 0) + fprint(slfd, "ssh logged in as %s\n", user); + else + syslog(1, "ssh", "ssh logged in as %s", user); + add_byte(p2, SSH_MSG_USERAUTH_SUCCESS); + auth_freeAI(ai); + } + } + else { + add_byte(p2, SSH_MSG_USERAUTH_FAILURE); + add_string(p2, "password,publickey"); + add_byte(p2, 0); + retval = -1; + } + n = finish_packet(p2); + iowrite(c->dio, c->datafd, p2->nlength, n); + + free(service); + free(me); + free(user); + free(pw); + free(alg); + free(blob); + free(sig); + free(path); + free(p2); + return retval; +} + +int +client_auth(Conn *c, Ioproc *io) +{ + Packet *p2, *p3, *p4; + char *r, *s; + mpint *ek, *nk; + int i, n; + + if (!c->password && !c->authkey) + return -1; + p2 = new_packet(c); + add_byte(p2, SSH_MSG_USERAUTH_REQUEST); + add_string(p2, c->user); + add_string(p2, c->service); + if (c->password) { + add_string(p2, "password"); + add_byte(p2, 0); + add_string(p2, c->password); + } + else { + add_string(p2, "publickey"); + add_byte(p2, 1); + add_string(p2, "ssh-rsa"); + + r = strstr(c->authkey, " ek="); + s = strstr(c->authkey, " n="); + if (!r || !s) { + shutdown(c); + free(p2); + return -1; + } + ek = strtomp(r+4, nil, 16, nil); + nk = strtomp(s+3, nil, 16, nil); + + p3 = new_packet(c); + add_string(p3, "ssh-rsa"); + add_mp(p3, ek); + add_mp(p3, nk); + add_block(p2, p3->payload, p3->rlength-1); + + p4 = new_packet(c); + add_block(p4, c->sessid, SHA1dlen); + add_byte(p4, SSH_MSG_USERAUTH_REQUEST); + add_string(p4, c->user); + add_string(p4, c->service); + add_string(p4, "publickey"); + add_byte(p4, 1); + add_string(p4, "ssh-rsa"); + add_block(p4, p3->payload, p3->rlength-1); + mpfree(ek); + mpfree(nk); + free(p3); + + for (i = 0; pkas[i] && strcmp("ssh-rsa", pkas[i]->name); ++i) ; + if ((p3 = pkas[i]->sign(c, p4->payload, p4->rlength-1)) == nil) { + free(p4); + free(p2); + return -1; + } + add_block(p2, p3->payload, p3->rlength-1); + free(p3); + free(p4); + } + n = finish_packet(p2); + if (io) + iowrite(io, c->datafd, p2->nlength, n); + else + write(c->datafd, p2->nlength, n); + free(p2); + return 0; +} + +char * +factlookup(int nattr, int nreq, char *attrs[]) +{ + Biobuf *bp; + char *buf, *toks[32], *res, *q; + int ntok, nmatch, maxmatch; + int i, j; + + res = nil; + bp = Bopen("/mnt/factotum/ctl", OREAD); + if (bp == nil) + return nil; + maxmatch = 0; + while (buf = Brdstr(bp, '\n', 1)) { + q = estrdup9p(buf); + ntok = gettokens(buf, toks, 32, " "); + nmatch = 0; + for (i = 0; i < nattr; ++i) { + for (j = 0; j < ntok; ++j) { + if (strcmp(attrs[i], toks[j]) == 0) { + ++nmatch; + break; + } + } + if (i < nreq && j >= ntok) + break; + } + if (i >= nattr && nmatch > maxmatch) { + free(res); + res = q; + maxmatch = nmatch; + } + else + free(q); + free(buf); + } + Bterm(bp); + return res; +} + +void +shutdown(Conn *c) +{ + Plist *p; + SSHChan *sc; + int i, ostate; + + if (debug) + fprint(2, "Shutting down connection %d\n", c->id); + ostate = c->state; + if (c->clonefile->ref <= 2 && c->ctlfile->ref <= 2 && c->datafile->ref <= 2 + && c->listenfile->ref <= 2 && c->localfile->ref <= 2 + && c->remotefile->ref <= 2 && c->statusfile->ref <= 2) + c->state = Closed; + else { + if (c->state != Closed) + c->state = Closing; + if (debug) + fprint(2, "clone %ld ctl %ld data %ld listen %ld local %ld remote %ld status %ld\n", + c->clonefile->ref, c->ctlfile->ref, c->datafile->ref, c->listenfile->ref, c->localfile->ref, + c->remotefile->ref, c->statusfile->ref); + } + if (ostate == Closed || ostate == Closing) { + c->state = Closed; + return; + } + if (c->role == Server && c->remote) { + if (slfd > 0) + fprint(slfd, "closing ssh connection from %s\n", c->remote); + else + syslog(1, "ssh", "closing ssh connection from %s", c->remote); + } + fprint(c->ctlfd, "hangup"); + close(c->ctlfd); + close(c->datafd); + if (c->dio) { + closeioproc(c->dio); + c->dio = nil; + } + c->decrypt = -1; + c->inmac = -1; + c->ctlfd = -1; + c->datafd = -1; + c->nchan = 0; + free(c->otherid); + if (c->s2ccs) { + free(c->s2ccs); + c->s2ccs = nil; + } + if (c->c2scs) { + free(c->c2scs); + c->c2scs = nil; + } + if (c->remote) { + free(c->remote); + c->remote = nil; + } + if (c->x) { + mpfree(c->x); + c->x = nil; + } + if (c->e) { + mpfree(c->e); + c->e = nil; + } + if (c->user) { + free(c->user); + c->user = nil; + } + if (c->service) { + free(c->service); + c->service = nil; + } + c->otherid = nil; + qlock(&c->l); + rwakeupall(&c->r); + qunlock(&c->l); + for (i = 0; i < MAXCONN; ++i) { + sc = c->chans[i]; + if (sc == nil) + continue; + if (sc->ann) { + free(sc->ann); + sc->ann = nil; + } + if (sc->state != Empty && sc->state != Closed) { + sc->state = Closed; + sc->lreq = nil; + while (sc->dataq != nil) { + p = sc->dataq; + sc->dataq = p->next; + free(p->pack); + free(p); + } + while (sc->reqq != nil) { + p = sc->reqq; + sc->reqq = p->next; + free(p->pack); + free(p); + } + qlock(&c->l); + rwakeupall(&sc->r); + nbsendul(sc->inchan, 1); + nbsendul(sc->reqchan, 1); + chanclose(sc->inchan); + chanclose(sc->reqchan); + qunlock(&c->l); + } + } + qlock(&availlck); + rwakeup(&availrend); + qunlock(&availlck); + if (debug) + fprint(2, "Done processing shutdown of connection %d\n", c->id); +} diff -Nru /sys/src/cmd/ssh2/sshtun.h /sys/src/cmd/ssh2/sshtun.h --- /sys/src/cmd/ssh2/sshtun.h Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/sshtun.h Fri Mar 30 00:00:00 2012 @@ -0,0 +1,262 @@ +#include +#define MYID "SSH-2.0-Plan9" + +#pragma varargck type "M" mpint* + +enum { + CONNSHIFT = 7, + MAXCONN = 1 << CONNSHIFT, + LEVSHIFT = 2 * CONNSHIFT + 3, + + RootFile = 0, + CloneFile = 1 << (2 * CONNSHIFT), + CtlFile = 2 << (2 * CONNSHIFT), + DataFile = 3 << (2 * CONNSHIFT), + ListenFile = 4 << (2 * CONNSHIFT), + LocalFile = 5 << (2 * CONNSHIFT), + ReqRemFile = 6 << (2 * CONNSHIFT), + StatusFile = 7 << (2* CONNSHIFT), + FileMask = 7 << (2 * CONNSHIFT), + ConnMask = (1 << CONNSHIFT) - 1, + + Server = 0, + Client, +}; + +/* + * The stylistic anomaly with these names of unbounded length + * is a result of following the RFCs in using the same names for + * these constants. I did that to make it easier to search and + * cross-reference between the code and the RFCs. + */ +enum /* SSH2 Protocol Packet Types */ +{ + SSH_MSG_DISCONNECT = 1, + SSH_MSG_IGNORE = 2, + SSH_MSG_UNIMPLEMENTED, + SSH_MSG_DEBUG, + SSH_MSG_SERVICE_REQUEST, + SSH_MSG_SERVICE_ACCEPT, + + SSH_MSG_KEXINIT = 20, + SSH_MSG_NEWKEYS, + + SSH_MSG_KEXDH_INIT = 30, + SSH_MSG_KEXDH_REPLY, + + SSH_MSG_USERAUTH_REQUEST = 50, + SSH_MSG_USERAUTH_FAILURE, + SSH_MSG_USERAUTH_SUCCESS, + SSH_MSG_USERAUTH_BANNER, + + SSH_MSG_USERAUTH_PK_OK = 60, + SSH_MSG_USERAUTH_PASSWD_CHANGEREQ = 60, + + SSH_MSG_GLOBAL_REQUEST = 80, + SSH_MSG_REQUEST_SUCCESS, + SSH_MSG_REQUEST_FAILURE, + + SSH_MSG_CHANNEL_OPEN = 90, + SSH_MSG_CHANNEL_OPEN_CONFIRMATION, + SSH_MSG_CHANNEL_OPEN_FAILURE, + SSH_MSG_CHANNEL_WINDOW_ADJUST, + SSH_MSG_CHANNEL_DATA, + SSH_MSG_CHANNEL_EXTENDED_DATA, + SSH_MSG_CHANNEL_EOF, + SSH_MSG_CHANNEL_CLOSE, + SSH_MSG_CHANNEL_REQUEST, + SSH_MSG_CHANNEL_SUCCESS, + SSH_MSG_CHANNEL_FAILURE, +}; + +enum /* SSH2 reason codes */ +{ + SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1, + SSH_DISCONNECT_PROTOCOL_ERROR, + SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + SSH_DISCONNECT_RESERVED, + SSH_DISCONNECT_MAC_ERROR, + SSH_DISCONNECT_COMPRESSION_ERROR, + SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, + SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, + SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, + SSH_DISCONNECT_CONNECTION_LOST, + SSH_DISCONNECT_BY_APPLICATION, + SSH_DISCONNECT_TOO_MANY_CONNECTIONS, + SSH_DISCONNECT_AUTH_CANCELLED_BY_USER, + SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + SSH_DISCONNECT_ILLEGAL_USR_NAME, + + SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1, + SSH_OPEN_CONNECT_FAILED, + SSH_OPEN_UNKNOWN_CHANNEL_TYPE, + SSH_OPEN_RESOURCE_SHORTAGE, +}; + +enum /* SSH2 type code */ +{ + SSH_EXTENDED_DATA_STDERR = 1, +}; + +enum /* connection and channel states */ +{ + Empty = 0, + Allocated, + Initting, + Listening, + Opening, + Negotiating, + Authing, + Established, + Eof, + Closing, + Closed, +}; + +enum { + NoKeyFile, + NoKey, + KeyWrong, + KeyOk, +}; + +typedef struct Plist Plist; +typedef struct SSHChan SSHChan; +typedef struct Conn Conn; +typedef struct Packet Packet; +typedef struct Cipher Cipher; +typedef struct CipherState CipherState; +typedef struct Kex Kex; +typedef struct PKA PKA; +typedef struct MBox MBox; + +#pragma incomplete CipherState + +struct Plist { + Packet *pack; + uchar *st; + int rem; + Plist *next; +}; + +struct SSHChan { + Rendez r; + int id, otherid, state; + int waker; + int conn; + ulong rwindow, twindow; + ulong sent, inrqueue; + char *ann; + Req *lreq; + File *dir, *ctl, *data, *listen, *request, *status; + Plist *dataq, *datatl, *reqq, *reqtl; + Channel *inchan, *reqchan; + QLock xmtlock; + Rendez xmtrendez; +}; + +struct Conn { + QLock l; + Rendez r; + Ioproc *dio, *cio, *rio; + int state; + int role; + int id; + char *remote; + char *user, *password, *service; + char *cap; + char *authkey; + int nchan; + int datafd, ctlfd; + int rpid; + int inseq, outseq; + int kexalg, pkalg; + int cscrypt, ncscrypt, sccrypt, nsccrypt, csmac, ncsmac, scmac, nscmac; + int encrypt, decrypt, outmac, inmac; + File *dir, *clonefile, *ctlfile, *datafile, *listenfile, *localfile, *remotefile, *statusfile; + Packet *skexinit, *rkexinit; + mpint *x, *e; + int got_sessid; + uchar sessid[SHA1dlen]; + uchar c2siv[SHA1dlen*2], nc2siv[SHA1dlen*2], s2civ[SHA1dlen*2], ns2civ[SHA1dlen*2]; + uchar c2sek[SHA1dlen*2], nc2sek[SHA1dlen*2], s2cek[SHA1dlen*2], ns2cek[SHA1dlen*2]; + uchar c2sik[SHA1dlen*2], nc2sik[SHA1dlen*2], s2cik[SHA1dlen*2], ns2cik[SHA1dlen*2]; + char *otherid; + uchar *inik, *outik; + CipherState *s2ccs, *c2scs; + CipherState *enccs, *deccs; + SSHChan *chans[MAXCONN]; +}; + +struct Packet { + Conn *c; + ulong rlength, tlength; + uchar nlength[4]; + uchar pad_len; + uchar payload[35000]; +}; + +struct Cipher +{ + char *name; + int blklen; + CipherState *(*init)(Conn*, int); + void (*encrypt)(CipherState*, uchar*, int); + void (*decrypt)(CipherState*, uchar*, int); +}; + +struct Kex +{ + char *name; + int (*serverkex)(Conn *, Packet *); + int (*clientkex1)(Conn *, Packet *); + int (*clientkex2)(Conn *, Packet *); +}; + +struct PKA +{ + char *name; + Packet *(*ks)(Conn *); + Packet *(*sign)(Conn *, uchar *, int); + int (*verify)(Conn *, uchar *, int, char *, char *, int); +}; + +struct MBox +{ + Channel *mchan; + char *msg; + int state; +}; + +extern int debug; +extern Cipher cipherblowfish, cipher3des, cipherrc4; +extern Cipher cipheraes128, cipheraes192, cipheraes256; +extern Kex dh1sha1, dh14sha1; +extern PKA rsa_pka, dss_pka, *pkas[]; +extern sshkeychan[]; +extern MBox keymbox; + +/* pubkey.c */ +RSApub *readpublickey(Biobuf *, char **); +int findkey(char *, char *, RSApub *); +int replacekey(char *, char *, RSApub *); +int appendkey(char *, char *, RSApub *); + +/* dh.c */ +void dh_init(PKA *[]); + +/* transport.c */ +Packet *new_packet(Conn *); +void init_packet(Packet *); +void add_byte(Packet *, char); +void add_uint32(Packet *, ulong); +ulong get_uint32(Packet *, uchar **); +int add_packet(Packet *, void *, int); +void add_block(Packet *, void *, int); +void add_string(Packet *, char *); +uchar *get_string(Packet *, uchar *, char *, int, int *); +void add_mp(Packet *, mpint *); +mpint *get_mp(uchar *q); +int finish_packet(Packet *); +int undo_packet(Packet *); +void dump_packet(Packet *); diff -Nru /sys/src/cmd/ssh2/transport.c /sys/src/cmd/ssh2/transport.c --- /sys/src/cmd/ssh2/transport.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/ssh2/transport.c Fri Mar 30 00:00:00 2012 @@ -0,0 +1,236 @@ +#include +#include +#include +#include +#include +#include <9p.h> +#include +#include +#include "sshtun.h" + +extern Cipher *cryptos[]; + +Packet * +new_packet(Conn *c) +{ + Packet *p; + + p = emalloc9p(sizeof(Packet)); + init_packet(p); + p->c = c; + return p; +} + +void +init_packet(Packet *p) +{ + memset(p, 0, sizeof(Packet)); + p->rlength = 1; +} + +void +add_byte(Packet *p, char c) +{ + p->payload[p->rlength-1] = c; + p->rlength++; +} + +void +add_uint32(Packet *p, ulong l) +{ + hnputl(p->payload+p->rlength-1, l); + p->rlength += 4; +} + +ulong +get_uint32(Packet *, uchar **data) +{ + ulong x; + x = nhgetl(*data); + *data += 4; + return x; +} + +int +add_packet(Packet *p, void *data, int len) +{ + if(p->rlength + len > 32768) + return -1; + memmove(p->payload + p->rlength - 1, data, len); + p->rlength += len; + return 0; +} + +void +add_block(Packet *p, void *data, int len) +{ + hnputl(p->payload + p->rlength - 1, len); + p->rlength += 4; + add_packet(p, data, len); +} + +void +add_string(Packet *p, char *s) +{ + uchar *q; + int n; + uchar nn[4]; + + n = strlen(s); + hnputl(nn, n); + q = p->payload + p->rlength - 1; + memmove(q, nn, 4); + memmove(q+4, s, n); + p->rlength += n + 4; +} + +uchar * +get_string(Packet *p, uchar *q, char *s, int lim, int *len) +{ + int n, m; + + if (p && q > p->payload + p->rlength) + s[0] = '\0'; + m = nhgetl(q); + q += 4; + if(m < lim) + n = m; + else + n = lim - 1; + memmove(s, q, n); + s[n] = '\0'; + q += m; + if(len) + *len = n; + return q; +} + +void +add_mp(Packet *p, mpint *x) +{ + uchar *q; + int n; + + q = p->payload + p->rlength - 1; + n = mptobe(x, q + 4, 35000 - p->rlength + 1 - 4, nil); + if(q[4] & 0x80){ + memmove(q + 5, q + 4, n); + q[4] = 0; + n++; + } + hnputl(q, n); + p->rlength += n + 4; +} + +mpint * +get_mp(uchar *q) +{ + int n; + + n = nhgetl(q); + q += 4; + return betomp(q, n, nil); +} + +int +finish_packet(Packet *p) +{ + Conn *c; + uchar *q, *buf; + int blklen, i, n2, n1, maclen; + + c = p->c; + blklen = 8; + if(debug > 1) + fprint(2, "In finish packet: enc:%d outmac:%d len:%ld\n", c->encrypt, c->outmac, p->rlength); + if(c && c->encrypt != -1){ + blklen = cryptos[c->encrypt]->blklen; + if(blklen < 8) + blklen = 8; + } + n1 = p->rlength - 1; + n2 = blklen - ((n1 + 5) % blklen); + if(n2 < 4) + n2 += blklen; + p->pad_len = n2; + for(i = 0, q = p->payload + n1; i < n2; ++i, ++q) + *q = fastrand(); + p->rlength = n1 + n2 + 1; + hnputl(p->nlength, p->rlength); + maclen = 0; + if(c && c->outmac != -1){ + maclen = SHA1dlen; + buf = emalloc9p(35000); + hnputl(buf, c->outseq); + memmove(buf + 4, p->nlength, p->rlength + 4); + hmac_sha1(buf, p->rlength + 8, c->outik, maclen, q, nil); + free(buf); + } + if(c && c->encrypt != -1) + cryptos[c->encrypt]->encrypt(c->enccs, p->nlength, p->rlength + 4); + c->outseq++; + if(debug > 1) + fprint(2, "leaving finish packet: len:%ld n1:%d n2:%d maclen:%d\n", p->rlength, n1, n2, maclen); + return p->rlength + 4 + maclen; +} + +/* + * The first blklen bytes are already decrypted so we could find the + * length. + */ +int +undo_packet(Packet *p) +{ + Conn *c; + long nlength; + int nb; + uchar rmac[SHA1dlen], *buf; + + c = p->c; + nb = 4; + if(c->decrypt != -1) + nb = cryptos[c->decrypt]->blklen; + if(c->inmac != -1) + p->rlength -= 20; + nlength = nhgetl(p->nlength); + if(c->decrypt != -1) + cryptos[c->decrypt]->decrypt(c->deccs, p->nlength + nb, p->rlength + 4 - nb); + if(c->inmac != -1){ + buf = emalloc9p(35000); + hnputl(buf, c->inseq); + memmove(buf + 4, p->nlength, nlength + 4); + hmac_sha1(buf, nlength + 8, c->inik, SHA1dlen, rmac, nil); + free(buf); + if(memcmp(rmac, p->payload + nlength - 1, SHA1dlen) != 0){ + fprint(2, "Received MAC verification failed: seq=%d\n", c->inseq); + return -1; + } + } + c->inseq++; + p->rlength -= p->pad_len; + p->pad_len = 0; + return p->rlength - 1; +} + +void +dump_packet(Packet *p) +{ + int i; + char *buf, *q, *e; + + fprint(2, "Length: %ld, Padding length: %d\n", p->rlength, p->pad_len); + buf = emalloc9p(4096); + e = buf + 4096; + q = buf; + for(i = 0; i < p->rlength - 1; ++i){ + q = seprint(q, e, " %02x", p->payload[i]); + if(i % 16 == 15) + q = seprint(q, e, "\n"); + if((q - buf) > 4092){ + fprint(2, "%s", buf); + q = buf; + } + } + fprint(2, "%s\n", buf); + free(buf); +}