From e78a59f5a91fcf70eb5f225b43568ac2ebd167c3 Mon Sep 17 00:00:00 2001 From: damien Date: Wed, 12 Apr 2000 08:45:05 +0000 Subject: [PATCH] - More large OpenBSD CVS updates: - [auth.c auth.h servconf.c servconf.h serverloop.c session.c] [session.h ssh.h sshd.c README.openssh2] ssh2 server side, see README.openssh2; enable with 'sshd -2' - [channels.c] no adjust after close - [sshd.c compat.c ] interop w/ latest ssh.com windows client. --- ChangeLog | 10 +- README.openssh2 | 36 ++++++ auth.c | 188 ++++++++++++++++++++++++++- auth.h | 4 + channels.c | 5 +- compat.c | 4 +- servconf.c | 10 +- servconf.h | 1 + serverloop.c | 192 ++++++++++++++++++++++++++-- session.c | 328 ++++++++++++++++++++++++++++++++++++++++++++++-- session.h | 7 ++ ssh.h | 4 + sshd.c | 240 +++++++++++++++++++++++++++++++++-- 13 files changed, 997 insertions(+), 32 deletions(-) create mode 100644 README.openssh2 diff --git a/ChangeLog b/ChangeLog index 85734134..976c3834 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,7 +2,15 @@ - Avoid some compiler warnings in fake-get*.c - Add IPTOS macros for systems which lack them - Only set define entropy collection macros if they are found - + - More large OpenBSD CVS updates: + - [auth.c auth.h servconf.c servconf.h serverloop.c session.c] + [session.h ssh.h sshd.c README.openssh2] + ssh2 server side, see README.openssh2; enable with 'sshd -2' + - [channels.c] + no adjust after close + - [sshd.c compat.c ] + interop w/ latest ssh.com windows client. + 20000406 - OpenBSD CVS update: - [channels.c] diff --git a/README.openssh2 b/README.openssh2 new file mode 100644 index 00000000..8bdf8bf0 --- /dev/null +++ b/README.openssh2 @@ -0,0 +1,36 @@ +$Id$ + +works: + secsh-transport: works w/o rekey + proposal exchange, i.e. different enc/mac/comp per direction + encryption: blowfish-cbc, 3des-cbc, arcfour, cast128-cbc + mac: hmac-md5, hmac-sha1, (hmac-ripemd160) + compression: zlib, none + secsh-userauth: passwd only + secsh-connection: pty+shell or command, flow control works (window adjust) + tcp-forwarding: -L works + dss: verification works, + key database in ~/.ssh/known_hosts with bits == 0 hack + dss: signature works, keygen w/ openssl: + $ umask 077 + $ openssl dsaparam 1024 -out dsa1024.pem + $ openssl gendsa -out /etc/ssh_dsa_key dsa1024.pem -rand /dev/arandom + start sshd with '-2' flag + client interops w/ sshd2, lshd + server interops w/ ssh2, lsh, ssh.com's Windows client, SecureCRT + server supports multiple concurrent sessions (e.g. with SSH.com Windows client) +todo: + re-keying + secsh-connection features: + tcp-forwarding, agent-fwd, x11-fwd + auth other than passwd: + pubkey, keyboard-interactive + config + server-auth w/ old host-keys + cleanup + advanced key storage? + keynote + sftp + +-markus +$Date$ diff --git a/auth.c b/auth.c index 11b53817..e94a86e9 100644 --- a/auth.c +++ b/auth.c @@ -1,10 +1,11 @@ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved + * Copyright (c) 2000 Markus Friedl. All rights reserved. */ #include "includes.h" -RCSID("$OpenBSD: auth.c,v 1.1 2000/03/28 21:15:45 markus Exp $"); +RCSID("$OpenBSD: auth.c,v 1.2 2000/04/06 08:55:22 markus Exp $"); #include "xmalloc.h" #include "rsa.h" @@ -15,12 +16,17 @@ RCSID("$OpenBSD: auth.c,v 1.1 2000/03/28 21:15:45 markus Exp $"); #include "cipher.h" #include "mpaux.h" #include "servconf.h" +#include "compat.h" #include "channels.h" #include "match.h" +#include "bufaux.h" +#include "ssh2.h" +#include "auth.h" #include "session.h" #include "dispatch.h" + /* import */ extern ServerOptions options; extern char *forced_command; @@ -604,3 +610,183 @@ do_authentication() /* Perform session preparation. */ do_authenticated(pw); } + + +void input_service_request(int type, int plen); +void input_userauth_request(int type, int plen); +void ssh2_pty_cleanup(void); + +typedef struct Authctxt Authctxt; +struct Authctxt { + char *user; + char *service; + struct passwd pw; + int valid; +}; +static Authctxt *authctxt = NULL; +static int userauth_success = 0; + +struct passwd* +auth_get_user(void) +{ + return (authctxt != NULL && authctxt->valid) ? &authctxt->pw : NULL; +} +struct passwd* +auth_set_user(char *u, char *s) +{ + struct passwd *pw, *copy; + + if (authctxt == NULL) { + authctxt = xmalloc(sizeof(*authctxt)); + authctxt->valid = 0; + authctxt->user = xstrdup(u); + authctxt->service = xstrdup(s); + setproctitle("%s", u); + pw = getpwnam(u); + if (!pw || !allowed_user(pw)) { + log("auth_set_user: bad user %s", u); + return NULL; + } +#ifdef USE_PAM + start_pam(pw); +#endif + copy = &authctxt->pw; + memset(copy, 0, sizeof(*copy)); + copy->pw_name = xstrdup(pw->pw_name); + copy->pw_passwd = xstrdup(pw->pw_passwd); + copy->pw_uid = pw->pw_uid; + copy->pw_gid = pw->pw_gid; + copy->pw_dir = xstrdup(pw->pw_dir); + copy->pw_shell = xstrdup(pw->pw_shell); + authctxt->valid = 1; + } else { + if (strcmp(u, authctxt->user) != 0 || + strcmp(s, authctxt->service) != 0) { + log("auth_set_user: missmatch: (%s,%s)!=(%s,%s)", + u, s, authctxt->user, authctxt->service); + return NULL; + } + } + return auth_get_user(); +} + +static void +protocol_error(int type, int plen) +{ + log("auth: protocol error: type %d plen %d", type, plen); + packet_start(SSH2_MSG_UNIMPLEMENTED); + packet_put_int(0); + packet_send(); + packet_write_wait(); +} +void +input_service_request(int type, int plen) +{ + unsigned int len; + int accept = 0; + char *service = packet_get_string(&len); + + if (strcmp(service, "ssh-userauth") == 0) { + if (!userauth_success) { + accept = 1; + /* now we can handle user-auth requests */ + dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &input_userauth_request); + } + } + /* XXX all other service requests are denied */ + + if (accept) { + packet_start(SSH2_MSG_SERVICE_ACCEPT); + packet_put_cstring(service); + packet_send(); + packet_write_wait(); + } else { + debug("bad service request %s", service); + packet_disconnect("bad service request %s", service); + } + xfree(service); +} +void +input_userauth_request(int type, int plen) +{ + static int try = 0; + unsigned int len; + int c, authenticated = 0; + char *user, *service, *method; + struct passwd *pw; + + if (++try == AUTH_FAIL_MAX) + packet_disconnect("too many failed userauth_requests"); + + user = packet_get_string(&len); + service = packet_get_string(&len); + method = packet_get_string(&len); + debug("userauth-request for user %s service %s method %s", user, service, method); + + /* XXX we only allow the ssh-connection service */ + pw = auth_set_user(user, service); + if (pw && strcmp(service, "ssh-connection")==0) { + if (strcmp(method, "none") == 0 && try == 1) { +#ifdef USE_PAM + /* Do PAM auth with password */ + authenticated = auth_pam_password(pw, ""); +#else /* USE_PAM */ + /* Try authentication with the password. */ + authenticated = auth_password(pw, ""); +#endif /* USE_PAM */ + } else if (strcmp(method, "password") == 0) { + char *password; + c = packet_get_char(); + if (c) + debug("password change not supported"); + password = packet_get_string(&len); +#ifdef USE_PAM + /* Do PAM auth with password */ + authenticated = auth_pam_password(pw, password); +#else /* USE_PAM */ + /* Try authentication with the password. */ + authenticated = auth_password(pw, password); +#endif /* USE_PAM */ + memset(password, 0, len); + xfree(password); + } else if (strcmp(method, "publickey") == 0) { + /* XXX TODO */ + char *pkalg; + char *pkblob; + c = packet_get_char(); + pkalg = packet_get_string(&len); + pkblob = packet_get_string(&len); + xfree(pkalg); + xfree(pkblob); + } + } + /* XXX check if other auth methods are needed */ + if (authenticated) { + /* turn off userauth */ + dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &protocol_error); + /* success! */ + packet_start(SSH2_MSG_USERAUTH_SUCCESS); + packet_send(); + packet_write_wait(); + log("userauth success for %s", user); + /* now we can break out */ + userauth_success = 1; + } else { + packet_start(SSH2_MSG_USERAUTH_FAILURE); + packet_put_cstring("password"); + packet_put_char(0); /* partial success */ + packet_send(); + packet_write_wait(); + } + xfree(service); + xfree(user); + xfree(method); +} +void +do_authentication2() +{ + dispatch_init(&protocol_error); + dispatch_set(SSH2_MSG_SERVICE_REQUEST, &input_service_request); + dispatch_run(DISPATCH_BLOCK, &userauth_success); + do_authenticated2(); +} diff --git a/auth.h b/auth.h index 80517772..3771e826 100644 --- a/auth.h +++ b/auth.h @@ -2,5 +2,9 @@ #define AUTH_H void do_authentication(void); +void do_authentication2(void); + +struct passwd * +auth_get_user(void); #endif diff --git a/channels.c b/channels.c index 77818e0c..ccc7e5c0 100644 --- a/channels.c +++ b/channels.c @@ -674,7 +674,7 @@ channel_handle_efd(Channel *c, fd_set * readset, fd_set * writeset) int channel_check_window(Channel *c, fd_set * readset, fd_set * writeset) { - if (!(c->flags & CHAN_CLOSE_SENT) && + if (!(c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD)) && c->local_window < c->local_window_max/2 && c->local_consumed > 0) { packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST); @@ -837,7 +837,8 @@ channel_output_poll() c->istate != CHAN_INPUT_WAIT_DRAIN) continue; } - if (compat20 && (c->flags & CHAN_CLOSE_SENT)) { + if (compat20 && + (c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD))) { debug("channel: %d: no data after CLOSE", c->self); continue; } diff --git a/compat.c b/compat.c index 0e36c218..5a633ff2 100644 --- a/compat.c +++ b/compat.c @@ -58,9 +58,7 @@ compat_datafellows(const char *version) size_t len; static const char *check[] = { "2.0.1", - "2.1.0.beta.9", - "2.1.0.pre.3", - "2.1.0.public.beta.1", + "2.1.0", NULL }; for (i = 0; check[i]; i++) { diff --git a/servconf.c b/servconf.c index 31b53248..b3bf54cf 100644 --- a/servconf.c +++ b/servconf.c @@ -31,6 +31,7 @@ initialize_server_options(ServerOptions *options) options->ports_from_cmdline = 0; options->listen_addrs = NULL; options->host_key_file = NULL; + options->dsa_key_file = NULL; options->server_key_bits = -1; options->login_grace_time = -1; options->key_regeneration_time = -1; @@ -78,6 +79,8 @@ fill_default_server_options(ServerOptions *options) add_listen_addr(options, NULL); if (options->host_key_file == NULL) options->host_key_file = HOST_KEY_FILE; + if (options->dsa_key_file == NULL) + options->dsa_key_file = DSA_KEY_FILE; if (options->server_key_bits == -1) options->server_key_bits = 768; if (options->login_grace_time == -1) @@ -159,7 +162,7 @@ typedef enum { sPrintMotd, sIgnoreRhosts, sX11Forwarding, sX11DisplayOffset, sStrictModes, sEmptyPasswd, sRandomSeedFile, sKeepAlives, sCheckMail, sUseLogin, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups, - sIgnoreUserKnownHosts + sIgnoreUserKnownHosts, sDSAKeyFile } ServerOpCodes; /* Textual representation of the tokens. */ @@ -169,6 +172,7 @@ static struct { } keywords[] = { { "port", sPort }, { "hostkey", sHostKeyFile }, + { "dsakey", sDSAKeyFile }, { "serverkeybits", sServerKeyBits }, { "logingracetime", sLoginGraceTime }, { "keyregenerationinterval", sKeyRegenerationTime }, @@ -338,7 +342,9 @@ parse_int: break; case sHostKeyFile: - charptr = &options->host_key_file; + case sDSAKeyFile: + charptr = (opcode == sHostKeyFile ) ? + &options->host_key_file : &options->dsa_key_file; cp = strtok(NULL, WHITESPACE); if (!cp) { fprintf(stderr, "%s line %d: missing file name.\n", diff --git a/servconf.h b/servconf.h index 26e6ea3f..de5383a8 100644 --- a/servconf.h +++ b/servconf.h @@ -32,6 +32,7 @@ typedef struct { char *listen_addr; /* Address on which the server listens. */ struct addrinfo *listen_addrs; /* Addresses on which the server listens. */ char *host_key_file; /* File containing host key. */ + char *dsa_key_file; /* File containing dsa host key. */ int server_key_bits;/* Size of the server key. */ int login_grace_time; /* Disconnect if no auth in this time * (sec). */ diff --git a/serverloop.c b/serverloop.c index 8bf448ce..0ea57faa 100644 --- a/serverloop.c +++ b/serverloop.c @@ -5,6 +5,10 @@ * Created: Sun Sep 10 00:30:37 1995 ylo * Server main loop for handling the interactive session. */ +/* + * SSH2 support by Markus Friedl. + * Copyright (c) 2000 Markus Friedl. All rights reserved. + */ #include "includes.h" #include "xmalloc.h" @@ -16,6 +20,8 @@ #include "channels.h" #include "compat.h" +#include "ssh2.h" +#include "session.h" #include "dispatch.h" static Buffer stdin_buffer; /* Buffer for stdin data. */ @@ -72,6 +78,15 @@ sigchld_handler(int sig) signal(SIGCHLD, sigchld_handler); errno = save_errno; } +void +sigchld_handler2(int sig) +{ + int save_errno = errno; + debug("Received SIGCHLD."); + child_terminated = 1; + signal(SIGCHLD, sigchld_handler2); + errno = save_errno; +} /* * Make packets from buffered stderr data, and buffer it for sending @@ -154,15 +169,21 @@ retry_select: * Read packets from the client unless we have too much buffered * stdin or channel data. */ - if (buffer_len(&stdin_buffer) < 4096 && - channel_not_very_much_buffered_data()) - FD_SET(connection_in, readset); + if (compat20) { + // wrong: bad conditionXXX + if (channel_not_very_much_buffered_data()) + FD_SET(connection_in, readset); + } else { + if (buffer_len(&stdin_buffer) < 4096 && + channel_not_very_much_buffered_data()) + FD_SET(connection_in, readset); + } /* * If there is not too much data already buffered going to the * client, try to get some more data from the program. */ - if (packet_not_very_much_data_to_write()) { + if (!compat20 && packet_not_very_much_data_to_write()) { if (!fdout_eof) FD_SET(fdout, readset); if (!fderr_eof) @@ -182,7 +203,7 @@ retry_select: /* If we have buffered data, try to write some of that data to the program. */ - if (fdin != -1 && buffer_len(&stdin_buffer) > 0) + if (!compat20 && fdin != -1 && buffer_len(&stdin_buffer) > 0) FD_SET(fdin, writeset); /* Update the maximum descriptor number if appropriate. */ @@ -204,6 +225,8 @@ retry_select: tv.tv_usec = 1000 * (max_time_milliseconds % 1000); tvp = &tv; } + if (tvp!=NULL) + debug("tvp!=NULL kid %d mili %d", child_terminated, max_time_milliseconds); /* Wait for something to happen, or the timeout to expire. */ ret = select(max_fd + 1, readset, writeset, NULL, tvp); @@ -250,6 +273,9 @@ process_input(fd_set * readset) /* Buffer any received data. */ packet_process_incoming(buf, len); } + if (compat20) + return; + /* Read and buffer any available stdout data from the program. */ if (!fdout_eof && FD_ISSET(fdout, readset)) { len = read(fdout, buf, sizeof(buf)); @@ -279,7 +305,7 @@ process_output(fd_set * writeset) int len; /* Write buffered data to program stdin. */ - if (fdin != -1 && FD_ISSET(fdin, writeset)) { + if (!compat20 && fdin != -1 && FD_ISSET(fdin, writeset)) { len = write(fdin, buffer_ptr(&stdin_buffer), buffer_len(&stdin_buffer)); if (len <= 0) { @@ -578,6 +604,51 @@ server_loop(int pid, int fdin_arg, int fdout_arg, int fderr_arg) /* NOTREACHED */ } +void +server_loop2(void) +{ + fd_set readset, writeset; + int had_channel = 0; + int status; + pid_t pid; + + debug("Entering interactive session for SSH2."); + + signal(SIGCHLD, sigchld_handler2); + child_terminated = 0; + connection_in = packet_get_connection_in(); + connection_out = packet_get_connection_out(); + max_fd = connection_in; + if (connection_out > max_fd) + max_fd = connection_out; + server_init_dispatch(); + + for (;;) { + process_buffered_input_packets(); + if (!had_channel && channel_still_open()) + had_channel = 1; + if (had_channel && !channel_still_open()) { + debug("!channel_still_open."); + break; + } + if (packet_not_very_much_data_to_write()) + channel_output_poll(); + wait_until_can_do_something(&readset, &writeset, 0); + if (child_terminated) { + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) + session_close_by_pid(pid, status); + child_terminated = 0; + } + channel_after_select(&readset, &writeset); + process_input(&readset); + process_output(&writeset); + } + signal(SIGCHLD, SIG_DFL); + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) + session_close_by_pid(pid, status); + channel_stop_listening(); +} + void server_input_stdin_data(int type, int plen) { @@ -622,6 +693,111 @@ server_input_window_size(int type, int plen) pty_change_window_size(fdin, row, col, xpixel, ypixel); } +int +input_direct_tcpip(void) +{ + int sock; + char *host, *originator; + int host_port, originator_port; + + host = packet_get_string(NULL); + host_port = packet_get_int(); + originator = packet_get_string(NULL); + originator_port = packet_get_int(); + /* XXX check permission */ + sock = channel_connect_to(host, host_port); + xfree(host); + xfree(originator); + if (sock < 0) + return -1; + return channel_new("direct-tcpip", SSH_CHANNEL_OPEN, + sock, sock, -1, 4*1024, 32*1024, 0, xstrdup("direct-tcpip")); +} + +void +server_input_channel_open(int type, int plen) +{ + Channel *c = NULL; + char *ctype; + int id; + unsigned int len; + int rchan; + int rmaxpack; + int rwindow; + + ctype = packet_get_string(&len); + rchan = packet_get_int(); + rwindow = packet_get_int(); + rmaxpack = packet_get_int(); + + log("channel_input_open: ctype %s rchan %d win %d max %d", + ctype, rchan, rwindow, rmaxpack); + + if (strcmp(ctype, "session") == 0) { + debug("open session"); + /* + * A server session has no fd to read or write + * until a CHANNEL_REQUEST for a shell is made, + * so we set the type to SSH_CHANNEL_LARVAL. + * Additionally, a callback for handling all + * CHANNEL_REQUEST messages is registered. + */ + id = channel_new(ctype, SSH_CHANNEL_LARVAL, + -1, -1, -1, 0, 32*1024, 0, xstrdup("server-session")); + if (session_open(id) == 1) { + channel_register_callback(id, SSH2_MSG_CHANNEL_REQUEST, + session_input_channel_req, (void *)0); + channel_register_cleanup(id, session_close_by_channel); + c = channel_lookup(id); + } else { + debug("session open failed, free channel %d", id); + channel_free(id); + } + } else if (strcmp(ctype, "direct-tcpip") == 0) { + debug("open direct-tcpip"); + id = input_direct_tcpip(); + if (id >= 0) + c = channel_lookup(id); + } + if (c != NULL) { + debug("confirm %s", ctype); + c->remote_id = rchan; + c->remote_window = rwindow; + c->remote_maxpacket = rmaxpack; + + packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + packet_put_int(c->remote_id); + packet_put_int(c->self); + packet_put_int(c->local_window); + packet_put_int(c->local_maxpacket); + packet_send(); + } else { + debug("failure %s", ctype); + packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(rchan); + packet_put_int(SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED); + packet_put_cstring("bla bla"); + packet_put_cstring(""); + packet_send(); + } + xfree(ctype); +} + +void +server_init_dispatch_20() +{ + debug("server_init_dispatch_20"); + dispatch_init(&dispatch_protocol_error); + dispatch_set(SSH2_MSG_CHANNEL_CLOSE, &channel_input_oclose); + dispatch_set(SSH2_MSG_CHANNEL_DATA, &channel_input_data); + dispatch_set(SSH2_MSG_CHANNEL_EOF, &channel_input_ieof); + dispatch_set(SSH2_MSG_CHANNEL_EXTENDED_DATA, &channel_input_extended_data); + dispatch_set(SSH2_MSG_CHANNEL_OPEN, &server_input_channel_open); + dispatch_set(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); + dispatch_set(SSH2_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); + dispatch_set(SSH2_MSG_CHANNEL_REQUEST, &channel_input_channel_request); + dispatch_set(SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust); +} void server_init_dispatch_13() { @@ -648,7 +824,9 @@ server_init_dispatch_15() void server_init_dispatch() { - if (compat13) + if (compat20) + server_init_dispatch_20(); + else if (compat13) server_init_dispatch_13(); else server_init_dispatch_15(); diff --git a/session.c b/session.c index 2128fe39..835a4694 100644 --- a/session.c +++ b/session.c @@ -2,9 +2,13 @@ * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved */ +/* + * SSH2 support by Markus Friedl. + * Copyright (c) 2000 Markus Friedl. All rights reserved. + */ #include "includes.h" -RCSID("$OpenBSD: session.c,v 1.1 2000/03/28 21:15:45 markus Exp $"); +RCSID("$OpenBSD: session.c,v 1.2 2000/04/06 08:55:22 markus Exp $"); #include "xmalloc.h" #include "ssh.h" @@ -19,6 +23,10 @@ RCSID("$OpenBSD: session.c,v 1.1 2000/03/28 21:15:45 markus Exp $"); #include "channels.h" #include "nchan.h" +#include "bufaux.h" +#include "ssh2.h" +#include "auth.h" + /* types */ #define TTYSZ 64 @@ -448,9 +456,13 @@ do_exec_no_pty(Session *s, const char *command, struct passwd * pw) close(pout[1]); close(perr[1]); - /* Enter the interactive session. */ - server_loop(pid, pin[1], pout[0], perr[0]); - /* server_loop has closed pin[1], pout[1], and perr[1]. */ + if (compat20) { + session_set_fds(s, pin[1], pout[0], perr[0]); + } else { + /* Enter the interactive session. */ + server_loop(pid, pin[1], pout[0], perr[0]); + /* server_loop has closed pin[1], pout[1], and perr[1]. */ + } #else /* USE_PIPES */ /* We are the parent. Close the child sides of the socket pairs. */ close(inout[0]); @@ -460,8 +472,12 @@ do_exec_no_pty(Session *s, const char *command, struct passwd * pw) * Enter the interactive session. Note: server_loop must be able to * handle the case that fdin and fdout are the same. */ - server_loop(pid, inout[1], inout[1], err[1]); - /* server_loop has closed inout[1] and err[1]. */ + if (compat20) { + session_set_fds(s, inout[1], inout[1], err[1]); + } else { + server_loop(pid, inout[1], inout[1], err[1]); + /* server_loop has closed inout[1] and err[1]. */ + } #endif /* USE_PIPES */ } @@ -631,9 +647,13 @@ do_exec_pty(Session *s, const char *command, struct passwd * pw) s->ptymaster = ptymaster; /* Enter interactive session. */ - server_loop(pid, ptyfd, fdout, -1); - /* server_loop _has_ closed ptyfd and fdout. */ - session_pty_cleanup(s); + if (compat20) { + session_set_fds(s, ptyfd, fdout, -1); + } else { + server_loop(pid, ptyfd, fdout, -1); + /* server_loop _has_ closed ptyfd and fdout. */ + session_pty_cleanup(s); + } } /* @@ -1126,6 +1146,181 @@ session_dump(void) } } +int +session_open(int chanid) +{ + Session *s = session_new(); + debug("session_open: channel %d", chanid); + if (s == NULL) { + error("no more sessions"); + return 0; + } + debug("session_open: session %d: link with channel %d", s->self, chanid); + s->chanid = chanid; + s->pw = auth_get_user(); + if (s->pw == NULL) + fatal("no user for session %i channel %d", + s->self, s->chanid); + return 1; +} + +Session * +session_by_channel(int id) +{ + int i; + for(i = 0; i < MAX_SESSIONS; i++) { + Session *s = &sessions[i]; + if (s->used && s->chanid == id) { + debug("session_by_channel: session %d channel %d", i, id); + return s; + } + } + debug("session_by_channel: unknown channel %d", id); + session_dump(); + return NULL; +} + +Session * +session_by_pid(pid_t pid) +{ + int i; + debug("session_by_pid: pid %d", pid); + for(i = 0; i < MAX_SESSIONS; i++) { + Session *s = &sessions[i]; + if (s->used && s->pid == pid) + return s; + } + error("session_by_pid: unknown pid %d", pid); + session_dump(); + return NULL; +} + +int +session_window_change_req(Session *s) +{ + s->col = packet_get_int(); + s->row = packet_get_int(); + s->xpixel = packet_get_int(); + s->ypixel = packet_get_int(); + pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel); + return 1; +} + +int +session_pty_req(Session *s) +{ + unsigned int len; + + if (s->ttyfd != -1) + return -1; + s->term = packet_get_string(&len); + s->col = packet_get_int(); + s->row = packet_get_int(); + s->xpixel = packet_get_int(); + s->ypixel = packet_get_int(); + + if (strcmp(s->term, "") == 0) { + xfree(s->term); + s->term = NULL; + } + /* Allocate a pty and open it. */ + if (!pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, sizeof(s->tty))) { + xfree(s->term); + s->term = NULL; + s->ptyfd = -1; + s->ttyfd = -1; + error("session_pty_req: session %d alloc failed", s->self); + return -1; + } + debug("session_pty_req: session %d alloc %s", s->self, s->tty); + /* + * Add a cleanup function to clear the utmp entry and record logout + * time in case we call fatal() (e.g., the connection gets closed). + */ + fatal_add_cleanup(pty_cleanup_proc, (void *)s); + pty_setowner(s->pw, s->tty); + /* Get window size from the packet. */ + pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel); + + return 1; +} + +void +session_input_channel_req(int id, void *arg) +{ + unsigned int len; + int reply; + int success = 0; + char *rtype; + Session *s; + Channel *c; + + rtype = packet_get_string(&len); + reply = packet_get_char(); + + s = session_by_channel(id); + if (s == NULL) + fatal("session_input_channel_req: channel %d: no session", id); + c = channel_lookup(id); + if (c == NULL) + fatal("session_input_channel_req: channel %d: bad channel", id); + + debug("session_input_channel_req: session %d channel %d request %s reply %d", + s->self, id, rtype, reply); + + /* + * a session is in LARVAL state until a shell + * or programm is executed + */ + if (c->type == SSH_CHANNEL_LARVAL) { + if (strcmp(rtype, "shell") == 0) { + if (s->ttyfd == -1) + do_exec_no_pty(s, NULL, s->pw); + else + do_exec_pty(s, NULL, s->pw); + success = 1; + } else if (strcmp(rtype, "exec") == 0) { + char *command = packet_get_string(&len); + if (s->ttyfd == -1) + do_exec_no_pty(s, command, s->pw); + else + do_exec_pty(s, command, s->pw); + xfree(command); + success = 1; + } else if (strcmp(rtype, "pty-req") == 0) { + if (session_pty_req(s) > 0) + success = 1; + } + } + if (strcmp(rtype, "window-change") == 0) { + success = session_window_change_req(s); + } + + if (reply) { + packet_start(success ? + SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE); + packet_put_int(c->remote_id); + packet_send(); + } + xfree(rtype); +} + +void +session_set_fds(Session *s, int fdin, int fdout, int fderr) +{ + if (!compat20) + fatal("session_set_fds: called for proto != 2.0"); + /* + * now that have a child and a pipe to the child, + * we can activate our channel and register the fd's + */ + if (s->chanid == -1) + fatal("no channel for session %d", s->self); + channel_set_fds(s->chanid, + fdout, fdin, fderr, + fderr == -1 ? CHAN_EXTENDED_IGNORE : CHAN_EXTENDED_READ); +} + void session_pty_cleanup(Session *s) { @@ -1151,3 +1346,118 @@ session_pty_cleanup(Session *s) if (close(s->ptymaster) < 0) error("close(s->ptymaster): %s", strerror(errno)); } + +void +session_exit_message(Session *s, int status) +{ + Channel *c; + if (s == NULL) + fatal("session_close: no session"); + c = channel_lookup(s->chanid); + if (c == NULL) + fatal("session_close: session %d: no channel %d", + s->self, s->chanid); + debug("session_exit_message: session %d channel %d pid %d", + s->self, s->chanid, s->pid); + + if (WIFEXITED(status)) { + channel_request_start(s->chanid, + "exit-status", 0); + packet_put_int(WEXITSTATUS(status)); + packet_send(); + } else if (WIFSIGNALED(status)) { + channel_request_start(s->chanid, + "exit-signal", 0); + packet_put_int(WTERMSIG(status)); + packet_put_char(WCOREDUMP(status)); + packet_put_cstring(""); + packet_put_cstring(""); + packet_send(); + } else { + /* Some weird exit cause. Just exit. */ + packet_disconnect("wait returned status %04x.", status); + } + + /* disconnect channel */ + debug("session_exit_message: release channel %d", s->chanid); + channel_cancel_cleanup(s->chanid); + if (c->istate == CHAN_INPUT_OPEN) + chan_read_failed(c); + chan_write_failed(c); + s->chanid = -1; +} + +void +session_free(Session *s) +{ + debug("session_free: session %d pid %d", s->self, s->pid); + if (s->term) + xfree(s->term); + if (s->display) + xfree(s->display); + if (s->auth_data) + xfree(s->auth_data); + if (s->auth_proto) + xfree(s->auth_proto); + s->used = 0; +} + +void +session_close(Session *s) +{ + session_pty_cleanup(s); + session_free(s); +} + +void +session_close_by_pid(pid_t pid, int status) +{ + Session *s = session_by_pid(pid); + if (s == NULL) { + debug("session_close_by_pid: no session for pid %d", s->pid); + return; + } + if (s->chanid != -1) + session_exit_message(s, status); + session_close(s); +} + +/* + * this is called when a channel dies before + * the session 'child' itself dies + */ +void +session_close_by_channel(int id, void *arg) +{ + Session *s = session_by_channel(id); + if (s == NULL) { + debug("session_close_by_channel: no session for channel %d", id); + return; + } + /* disconnect channel */ + channel_cancel_cleanup(s->chanid); + s->chanid = -1; + + debug("session_close_by_channel: channel %d kill %d", id, s->pid); + if (s->pid == 0) { + /* close session immediately */ + session_close(s); + } else { + /* notify child, delay session cleanup */ + if (kill(s->pid, (s->ttyfd == -1) ? SIGTERM : SIGHUP) < 0) + error("session_close_by_channel: kill %d: %s", + s->pid, strerror(errno)); + } +} + +void +do_authenticated2(void) +{ + /* + * Cancel the alarm we set to limit the time taken for + * authentication. + */ + alarm(0); + log("do_authenticated2"); + server_loop2(); +} diff --git a/session.h b/session.h index 2051b737..a3427bcb 100644 --- a/session.h +++ b/session.h @@ -4,4 +4,11 @@ /* SSH1 */ void do_authenticated(struct passwd * pw); +/* SSH2 */ +void do_authenticated2(void); +int session_open(int id); +void session_input_channel_req(int id, void *arg); +void session_close_by_pid(pid_t pid, int status); +void session_close_by_channel(int id, void *arg); + #endif diff --git a/ssh.h b/ssh.h index 407dc1c3..e1d07dee 100644 --- a/ssh.h +++ b/ssh.h @@ -90,6 +90,7 @@ #define HOST_KEY_FILE ETCDIR "/ssh_host_key" #define SERVER_CONFIG_FILE ETCDIR "/sshd_config" #define HOST_CONFIG_FILE ETCDIR "/ssh_config" +#define DSA_KEY_FILE ETCDIR "/ssh_dsa_key" #ifndef SSH_PROGRAM #define SSH_PROGRAM "/usr/bin/ssh" @@ -486,6 +487,8 @@ void fatal_add_cleanup(void (*proc) (void *context), void *context); /* Removes a cleanup function to be called at fatal(). */ void fatal_remove_cleanup(void (*proc) (void *context), void *context); +/* ---- misc */ + /* * Expands tildes in the file name. Returns data allocated by xmalloc. * Warning: this calls getpw*. @@ -500,6 +503,7 @@ char *tilde_expand_filename(const char *filename, uid_t my_uid); * program). */ void server_loop(int pid, int fdin, int fdout, int fderr); +void server_loop2(void); /* Client side main loop for the interactive session. */ int client_loop(int have_pty, int escape_char); diff --git a/sshd.c b/sshd.c index bb5685dc..44782e39 100644 --- a/sshd.c +++ b/sshd.c @@ -8,10 +8,13 @@ * information to/from the application to the user client over an encrypted * connection. This can also handle forwarding of X11, TCP/IP, and authentication * agent connections. + * + * SSH2 implementation, + * Copyright (c) 2000 Markus Friedl. All rights reserved. */ #include "includes.h" -RCSID("$OpenBSD: sshd.c,v 1.97 2000/04/04 21:37:27 markus Exp $"); +RCSID("$OpenBSD: sshd.c,v 1.99 2000/04/07 09:17:39 markus Exp $"); #include "xmalloc.h" #include "rsa.h" @@ -25,6 +28,7 @@ RCSID("$OpenBSD: sshd.c,v 1.97 2000/04/04 21:37:27 markus Exp $"); #include "compat.h" #include "buffer.h" +#include "ssh2.h" #ifdef HAVE_OPENSSL # include # include @@ -39,9 +43,12 @@ RCSID("$OpenBSD: sshd.c,v 1.97 2000/04/04 21:37:27 markus Exp $"); # include # include #endif +#include "kex.h" #include "key.h" +#include "dsa.h" #include "auth.h" +#include "myproposal.h" #ifdef LIBWRAP #include @@ -70,6 +77,9 @@ int IPv4or6 = AF_INET; int IPv4or6 = AF_UNSPEC; #endif +/* Flag indicating whether SSH2 is enabled */ +int allow_ssh2 = 0; + /* * Debug mode flag. This can be set on the command line. If debug * mode is enabled, extra debugging output will be sent to the system @@ -136,6 +146,7 @@ unsigned char session_id[16]; /* Prototypes for various functions defined later in this file. */ void do_ssh1_kex(); +void do_ssh2_kex(); /* * Close all listening sockets @@ -255,6 +266,21 @@ key_regeneration_alarm(int sig) errno = save_errno; } +char * +chop(char *s) +{ + char *t = s; + while (*t) { + if(*t == '\n' || *t == '\r') { + *t = '\0'; + return s; + } + t++; + } + return s; + +} + void sshd_exchange_identification(int sock_in, int sock_out) { @@ -265,7 +291,9 @@ sshd_exchange_identification(int sock_in, int sock_out) char remote_version[256]; /* Must be at least as big as buf. */ snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n", - PROTOCOL_MAJOR, PROTOCOL_MINOR, SSH_VERSION); + allow_ssh2 ? 1 : PROTOCOL_MAJOR, + allow_ssh2 ? 99 : PROTOCOL_MINOR, + SSH_VERSION); server_version_string = xstrdup(buf); if (client_version_string == NULL) { @@ -286,7 +314,7 @@ sshd_exchange_identification(int sock_in, int sock_out) buf[i] = '\n'; buf[i + 1] = 0; continue; - /*break; XXX eat \r */ + //break; } if (buf[i] == '\n') { /* buf[i] == '\n' */ @@ -315,6 +343,8 @@ sshd_exchange_identification(int sock_in, int sock_out) debug("Client protocol version %d.%d; client software version %.100s", remote_major, remote_minor, remote_version); + compat_datafellows(remote_version); + switch(remote_major) { case 1: if (remote_minor < 3) { @@ -324,7 +354,15 @@ sshd_exchange_identification(int sock_in, int sock_out) /* note that this disables agent-forwarding */ enable_compat13(); } - break; + if (remote_minor != 99) + break; + /* FALLTHROUGH */ + case 2: + if (allow_ssh2) { + enable_compat20(); + break; + } + /* FALLTHROUGH */ default: s = "Protocol major versions differ.\n"; (void) atomicio(write, sock_out, s, strlen(s)); @@ -335,6 +373,8 @@ sshd_exchange_identification(int sock_in, int sock_out) fatal_cleanup(); break; } + chop(server_version_string); + chop(client_version_string); } /* @@ -370,8 +410,11 @@ main(int ac, char **av) initialize_server_options(&options); /* Parse command-line arguments. */ - while ((opt = getopt(ac, av, "f:p:b:k:h:g:V:diqQ46")) != EOF) { + while ((opt = getopt(ac, av, "f:p:b:k:h:g:V:diqQ246")) != EOF) { switch (opt) { + case '2': + allow_ssh2 = 1; + break; case '4': IPv4or6 = AF_INET; break; @@ -837,9 +880,14 @@ main(int ac, char **av) packet_set_nonblocking(); /* perform the key exchange */ - do_ssh1_kex(); /* authenticate user and start session */ - do_authentication(); + if (compat20) { + do_ssh2_kex(); + do_authentication2(); + } else { + do_ssh1_kex(); + do_authentication(); + } #ifdef KRB4 /* Cleanup user's ticket cache file. */ @@ -1049,3 +1097,181 @@ do_ssh1_kex() packet_send(); packet_write_wait(); } + +/* + * SSH2 key exchange: diffie-hellman-group1-sha1 + */ +void +do_ssh2_kex() +{ + Buffer *server_kexinit; + Buffer *client_kexinit; + int payload_len, dlen; + int slen; + unsigned int klen, kout; + char *ptr; + unsigned char *signature = NULL; + unsigned char *server_host_key_blob = NULL; + unsigned int sbloblen; + DH *dh; + BIGNUM *dh_client_pub = 0; + BIGNUM *shared_secret = 0; + int i; + unsigned char *kbuf; + unsigned char *hash; + Kex *kex; + Key *server_host_key; + char *cprop[PROPOSAL_MAX]; + char *sprop[PROPOSAL_MAX]; + +/* KEXINIT */ + + debug("Sending KEX init."); + + for (i = 0; i < PROPOSAL_MAX; i++) + sprop[i] = xstrdup(myproposal[i]); + server_kexinit = kex_init(sprop); + packet_start(SSH2_MSG_KEXINIT); + packet_put_raw(buffer_ptr(server_kexinit), buffer_len(server_kexinit)); + packet_send(); + packet_write_wait(); + + debug("done"); + + packet_read_expect(&payload_len, SSH2_MSG_KEXINIT); + + /* + * save raw KEXINIT payload in buffer. this is used during + * computation of the session_id and the session keys. + */ + client_kexinit = xmalloc(sizeof(*client_kexinit)); + buffer_init(client_kexinit); + ptr = packet_get_raw(&payload_len); + buffer_append(client_kexinit, ptr, payload_len); + + /* skip cookie */ + for (i = 0; i < 16; i++) + (void) packet_get_char(); + /* save kex init proposal strings */ + for (i = 0; i < PROPOSAL_MAX; i++) { + cprop[i] = packet_get_string(NULL); + debug("got kexinit string: %s", cprop[i]); + } + + i = (int) packet_get_char(); + debug("first kex follow == %d", i); + i = packet_get_int(); + debug("reserved == %d", i); + + debug("done read kexinit"); + kex = kex_choose_conf(cprop, sprop, 1); + +/* KEXDH */ + + debug("Wait SSH2_MSG_KEXDH_INIT."); + packet_read_expect(&payload_len, SSH2_MSG_KEXDH_INIT); + + /* key, cert */ + dh_client_pub = BN_new(); + if (dh_client_pub == NULL) + fatal("dh_client_pub == NULL"); + packet_get_bignum2(dh_client_pub, &dlen); + +#ifdef DEBUG_KEXDH + fprintf(stderr, "\ndh_client_pub= "); + bignum_print(dh_client_pub); + fprintf(stderr, "\n"); + debug("bits %d", BN_num_bits(dh_client_pub)); +#endif + + /* generate DH key */ + dh = new_dh_group1(); /* XXX depends on 'kex' */ + +#ifdef DEBUG_KEXDH + fprintf(stderr, "\np= "); + bignum_print(dh->p); + fprintf(stderr, "\ng= "); + bignum_print(dh->g); + fprintf(stderr, "\npub= "); + bignum_print(dh->pub_key); + fprintf(stderr, "\n"); +#endif + + klen = DH_size(dh); + kbuf = xmalloc(klen); + kout = DH_compute_key(kbuf, dh_client_pub, dh); + +#ifdef DEBUG_KEXDH + debug("shared secret: len %d/%d", klen, kout); + fprintf(stderr, "shared secret == "); + for (i = 0; i< kout; i++) + fprintf(stderr, "%02x", (kbuf[i])&0xff); + fprintf(stderr, "\n"); +#endif + shared_secret = BN_new(); + + BN_bin2bn(kbuf, kout, shared_secret); + memset(kbuf, 0, klen); + xfree(kbuf); + + server_host_key = dsa_get_serverkey(options.dsa_key_file); + dsa_make_serverkey_blob(server_host_key, &server_host_key_blob, &sbloblen); + + /* calc H */ /* XXX depends on 'kex' */ + hash = kex_hash( + client_version_string, + server_version_string, + buffer_ptr(client_kexinit), buffer_len(client_kexinit), + buffer_ptr(server_kexinit), buffer_len(server_kexinit), + (char *)server_host_key_blob, sbloblen, + dh_client_pub, + dh->pub_key, + shared_secret + ); + buffer_free(client_kexinit); + buffer_free(server_kexinit); + xfree(client_kexinit); + xfree(server_kexinit); +#ifdef DEBUG_KEXDH + fprintf(stderr, "hash == "); + for (i = 0; i< 20; i++) + fprintf(stderr, "%02x", (hash[i])&0xff); + fprintf(stderr, "\n"); +#endif + /* sign H */ + dsa_sign(server_host_key, &signature, &slen, hash, 20); + /* hashlen depends on KEX */ + key_free(server_host_key); + + /* send server hostkey, DH pubkey 'f' and singed H */ + packet_start(SSH2_MSG_KEXDH_REPLY); + packet_put_string((char *)server_host_key_blob, sbloblen); + packet_put_bignum2(dh->pub_key); // f + packet_put_string((char *)signature, slen); + packet_send(); + packet_write_wait(); + + kex_derive_keys(kex, hash, shared_secret); + packet_set_kex(kex); + + /* have keys, free DH */ + DH_free(dh); + + debug("send SSH2_MSG_NEWKEYS."); + packet_start(SSH2_MSG_NEWKEYS); + packet_send(); + packet_write_wait(); + debug("done: send SSH2_MSG_NEWKEYS."); + + debug("Wait SSH2_MSG_NEWKEYS."); + packet_read_expect(&payload_len, SSH2_MSG_NEWKEYS); + debug("GOT SSH2_MSG_NEWKEYS."); + + /* send 1st encrypted/maced/compressed message */ + packet_start(SSH2_MSG_IGNORE); + packet_put_cstring("markus"); + packet_send(); + packet_write_wait(); + + debug("done: KEX2."); +} -- 2.45.2