diff options
Diffstat (limited to 'crypto')
| -rw-r--r-- | crypto/openssh/auth.c | 130 | ||||
| -rw-r--r-- | crypto/openssh/aux.c | 36 | ||||
| -rw-r--r-- | crypto/openssh/kex.c | 468 | ||||
| -rw-r--r-- | crypto/openssh/kex.h | 117 | ||||
| -rw-r--r-- | crypto/openssh/key.c | 332 | ||||
| -rw-r--r-- | crypto/openssh/myproposal.h | 20 | ||||
| -rw-r--r-- | crypto/openssh/session.c | 1772 | ||||
| -rw-r--r-- | crypto/openssh/ssh2.h | 112 | ||||
| -rw-r--r-- | crypto/openssh/sshconnect2.c | 451 |
9 files changed, 3438 insertions, 0 deletions
diff --git a/crypto/openssh/auth.c b/crypto/openssh/auth.c new file mode 100644 index 000000000000..5e64cf05256b --- /dev/null +++ b/crypto/openssh/auth.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * $FreeBSD$ + */ + +#include "includes.h" +RCSID("$OpenBSD: auth.c,v 1.7 2000/05/17 21:37:24 deraadt Exp $"); + +#include "xmalloc.h" +#include "rsa.h" +#include "ssh.h" +#include "pty.h" +#include "packet.h" +#include "buffer.h" +#include "cipher.h" +#include "mpaux.h" +#include "servconf.h" +#include "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; + +/* + * Check if the user is allowed to log in via ssh. If user is listed in + * DenyUsers or user's primary group is listed in DenyGroups, false will + * be returned. If AllowUsers isn't empty and user isn't listed there, or + * if AllowGroups isn't empty and user isn't listed there, false will be + * returned. + * If the user's shell is not executable, false will be returned. + * Otherwise true is returned. + */ +int +allowed_user(struct passwd * pw) +{ + struct stat st; + struct group *grp; + char *shell; + int i; + + /* Shouldn't be called if pw is NULL, but better safe than sorry... */ + if (!pw) + return 0; + + /* + * Get the shell from the password data. An empty shell field is + * legal, and means /bin/sh. + */ + shell = (pw->pw_shell[0] == '\0') ? _PATH_BSHELL : pw->pw_shell; + + /* deny if shell does not exists or is not executable */ + if (stat(shell, &st) != 0) + return 0; + if (!((st.st_mode & S_IFREG) && (st.st_mode & (S_IXOTH|S_IXUSR|S_IXGRP)))) + return 0; + + /* Return false if user is listed in DenyUsers */ + if (options.num_deny_users > 0) { + if (!pw->pw_name) + return 0; + for (i = 0; i < options.num_deny_users; i++) + if (match_pattern(pw->pw_name, options.deny_users[i])) + return 0; + } + /* Return false if AllowUsers isn't empty and user isn't listed there */ + if (options.num_allow_users > 0) { + if (!pw->pw_name) + return 0; + for (i = 0; i < options.num_allow_users; i++) + if (match_pattern(pw->pw_name, options.allow_users[i])) + break; + /* i < options.num_allow_users iff we break for loop */ + if (i >= options.num_allow_users) + return 0; + } + /* Get the primary group name if we need it. Return false if it fails */ + if (options.num_deny_groups > 0 || options.num_allow_groups > 0) { + grp = getgrgid(pw->pw_gid); + if (!grp) + return 0; + + /* Return false if user's group is listed in DenyGroups */ + if (options.num_deny_groups > 0) { + if (!grp->gr_name) + return 0; + for (i = 0; i < options.num_deny_groups; i++) + if (match_pattern(grp->gr_name, options.deny_groups[i])) + return 0; + } + /* + * Return false if AllowGroups isn't empty and user's group + * isn't listed there + */ + if (options.num_allow_groups > 0) { + if (!grp->gr_name) + return 0; + for (i = 0; i < options.num_allow_groups; i++) + if (match_pattern(grp->gr_name, options.allow_groups[i])) + break; + /* i < options.num_allow_groups iff we break for + loop */ + if (i >= options.num_allow_groups) + return 0; + } + } +#ifndef __FreeBSD__ /* FreeBSD handle it later */ + /* Fail if the account's expiration time has passed. */ + if (pw->pw_expire != 0) { + struct timeval tv; + + (void)gettimeofday(&tv, NULL); + if (tv.tv_sec >= pw->pw_expire) + return 0; + } +#endif /* !__FreeBSD__ */ + /* We found no reason not to let this user try to log on... */ + return 1; +} diff --git a/crypto/openssh/aux.c b/crypto/openssh/aux.c new file mode 100644 index 000000000000..899142da7167 --- /dev/null +++ b/crypto/openssh/aux.c @@ -0,0 +1,36 @@ +#include "includes.h" +RCSID("$OpenBSD: aux.c,v 1.2 2000/05/17 09:47:59 markus Exp $"); + +#include "ssh.h" + +char * +chop(char *s) +{ + char *t = s; + while (*t) { + if(*t == '\n' || *t == '\r') { + *t = '\0'; + return s; + } + t++; + } + return s; + +} + +void +set_nonblock(int fd) +{ + int val; + val = fcntl(fd, F_GETFL, 0); + if (val < 0) { + error("fcntl(%d, F_GETFL, 0): %s", fd, strerror(errno)); + return; + } + if (val & O_NONBLOCK) + return; + debug("fd %d setting O_NONBLOCK", fd); + val |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, val) == -1) + error("fcntl(%d, F_SETFL, O_NONBLOCK): %s", fd, strerror(errno)); +} diff --git a/crypto/openssh/kex.c b/crypto/openssh/kex.c new file mode 100644 index 000000000000..a6c8174a4251 --- /dev/null +++ b/crypto/openssh/kex.c @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Markus Friedl. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "includes.h" +RCSID("$Id: kex.c,v 1.7 2000/05/25 20:45:20 markus Exp $"); + +#include "ssh.h" +#include "ssh2.h" +#include "xmalloc.h" +#include "buffer.h" +#include "bufaux.h" +#include "packet.h" +#include "cipher.h" +#include "compat.h" + +#include <openssl/bn.h> +#include <openssl/dh.h> + +#include <openssl/crypto.h> +#include <openssl/bio.h> +#include <openssl/bn.h> +#include <openssl/dh.h> +#include <openssl/pem.h> + +#include "kex.h" + +#define KEX_COOKIE_LEN 16 + +Buffer * +kex_init(char *myproposal[PROPOSAL_MAX]) +{ + int first_kex_packet_follows = 0; + unsigned char cookie[KEX_COOKIE_LEN]; + u_int32_t rand = 0; + int i; + Buffer *ki = xmalloc(sizeof(*ki)); + for (i = 0; i < KEX_COOKIE_LEN; i++) { + if (i % 4 == 0) + rand = arc4random(); + cookie[i] = rand & 0xff; + rand >>= 8; + } + buffer_init(ki); + buffer_append(ki, (char *)cookie, sizeof cookie); + for (i = 0; i < PROPOSAL_MAX; i++) + buffer_put_cstring(ki, myproposal[i]); + buffer_put_char(ki, first_kex_packet_follows); + buffer_put_int(ki, 0); /* uint32 reserved */ + return ki; +} + +/* send kexinit, parse and save reply */ +void +kex_exchange_kexinit( + Buffer *my_kexinit, Buffer *peer_kexint, + char *peer_proposal[PROPOSAL_MAX]) +{ + int i; + char *ptr; + int plen; + + debug("send KEXINIT"); + packet_start(SSH2_MSG_KEXINIT); + packet_put_raw(buffer_ptr(my_kexinit), buffer_len(my_kexinit)); + packet_send(); + packet_write_wait(); + debug("done"); + + /* + * read and save raw KEXINIT payload in buffer. this is used during + * computation of the session_id and the session keys. + */ + debug("wait KEXINIT"); + packet_read_expect(&plen, SSH2_MSG_KEXINIT); + ptr = packet_get_raw(&plen); + buffer_append(peer_kexint, ptr, plen); + + /* parse packet and save algorithm proposal */ + /* skip cookie */ + for (i = 0; i < KEX_COOKIE_LEN; i++) + packet_get_char(); + /* extract kex init proposal strings */ + for (i = 0; i < PROPOSAL_MAX; i++) { + peer_proposal[i] = packet_get_string(NULL); + debug("got kexinit: %s", peer_proposal[i]); + } + /* first kex follow / reserved */ + i = packet_get_char(); + debug("first kex follow: %d ", i); + i = packet_get_int(); + debug("reserved: %d ", i); + packet_done(); + debug("done"); +} + +/* diffie-hellman-group1-sha1 */ + +int +dh_pub_is_valid(DH *dh, BIGNUM *dh_pub) +{ + int i; + int n = BN_num_bits(dh_pub); + int bits_set = 0; + + /* we only accept g==2 */ + if (!BN_is_word(dh->g, 2)) { + log("invalid DH base != 2"); + return 0; + } + if (dh_pub->neg) { + log("invalid public DH value: negativ"); + return 0; + } + for (i = 0; i <= n; i++) + if (BN_is_bit_set(dh_pub, i)) + bits_set++; + debug("bits set: %d/%d", bits_set, BN_num_bits(dh->p)); + + /* if g==2 and bits_set==1 then computing log_g(dh_pub) is trivial */ + if (bits_set > 1 && (BN_cmp(dh_pub, dh->p) == -1)) + return 1; + log("invalid public DH value (%d/%d)", bits_set, BN_num_bits(dh->p)); + return 0; +} + +DH * +dh_new_group1() +{ + static char *group1 = + "FFFFFFFF" "FFFFFFFF" "C90FDAA2" "2168C234" "C4C6628B" "80DC1CD1" + "29024E08" "8A67CC74" "020BBEA6" "3B139B22" "514A0879" "8E3404DD" + "EF9519B3" "CD3A431B" "302B0A6D" "F25F1437" "4FE1356D" "6D51C245" + "E485B576" "625E7EC6" "F44C42E9" "A637ED6B" "0BFF5CB6" "F406B7ED" + "EE386BFB" "5A899FA5" "AE9F2411" "7C4B1FE6" "49286651" "ECE65381" + "FFFFFFFF" "FFFFFFFF"; + DH *dh; + int ret, tries = 0; + dh = DH_new(); + if(dh == NULL) + fatal("DH_new"); + ret = BN_hex2bn(&dh->p, group1); + if(ret<0) + fatal("BN_hex2bn"); + dh->g = BN_new(); + if(dh->g == NULL) + fatal("DH_new g"); + BN_set_word(dh->g, 2); + do { + if (DH_generate_key(dh) == 0) + fatal("DH_generate_key"); + if (tries++ > 10) + fatal("dh_new_group1: too many bad keys: giving up"); + } while (!dh_pub_is_valid(dh, dh->pub_key)); + return dh; +} + +void +dump_digest(unsigned char *digest, int len) +{ + int i; + for (i = 0; i< len; i++){ + fprintf(stderr, "%02x", digest[i]); + if(i%2!=0) + fprintf(stderr, " "); + } + fprintf(stderr, "\n"); +} + +unsigned char * +kex_hash( + char *client_version_string, + char *server_version_string, + char *ckexinit, int ckexinitlen, + char *skexinit, int skexinitlen, + char *serverhostkeyblob, int sbloblen, + BIGNUM *client_dh_pub, + BIGNUM *server_dh_pub, + BIGNUM *shared_secret) +{ + Buffer b; + static unsigned char digest[EVP_MAX_MD_SIZE]; + EVP_MD *evp_md = EVP_sha1(); + EVP_MD_CTX md; + + buffer_init(&b); + buffer_put_string(&b, client_version_string, strlen(client_version_string)); + buffer_put_string(&b, server_version_string, strlen(server_version_string)); + + /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ + buffer_put_int(&b, ckexinitlen+1); + buffer_put_char(&b, SSH2_MSG_KEXINIT); + buffer_append(&b, ckexinit, ckexinitlen); + buffer_put_int(&b, skexinitlen+1); + buffer_put_char(&b, SSH2_MSG_KEXINIT); + buffer_append(&b, skexinit, skexinitlen); + + buffer_put_string(&b, serverhostkeyblob, sbloblen); + buffer_put_bignum2(&b, client_dh_pub); + buffer_put_bignum2(&b, server_dh_pub); + buffer_put_bignum2(&b, shared_secret); + +#ifdef DEBUG_KEX + buffer_dump(&b); +#endif + + EVP_DigestInit(&md, evp_md); + EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b)); + EVP_DigestFinal(&md, digest, NULL); + + buffer_free(&b); + +#ifdef DEBUG_KEX + dump_digest(digest, evp_md->md_size); +#endif + return digest; +} + +unsigned char * +derive_key(int id, int need, char unsigned *hash, BIGNUM *shared_secret) +{ + Buffer b; + EVP_MD *evp_md = EVP_sha1(); + EVP_MD_CTX md; + char c = id; + int have; + int mdsz = evp_md->md_size; + unsigned char *digest = xmalloc(((need+mdsz-1)/mdsz)*mdsz); + + buffer_init(&b); + buffer_put_bignum2(&b, shared_secret); + + EVP_DigestInit(&md, evp_md); + EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b)); /* shared_secret K */ + EVP_DigestUpdate(&md, hash, mdsz); /* transport-06 */ + EVP_DigestUpdate(&md, &c, 1); /* key id */ + EVP_DigestUpdate(&md, hash, mdsz); /* session id */ + EVP_DigestFinal(&md, digest, NULL); + + /* expand */ + for (have = mdsz; need > have; have += mdsz) { + EVP_DigestInit(&md, evp_md); + EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b)); + EVP_DigestUpdate(&md, hash, mdsz); + EVP_DigestUpdate(&md, digest, have); + EVP_DigestFinal(&md, digest + have, NULL); + } + buffer_free(&b); +#ifdef DEBUG_KEX + fprintf(stderr, "Digest '%c'== ", c); + dump_digest(digest, need); +#endif + return digest; +} + +#define NKEYS 6 + +#define MAX_PROP 20 +#define SEP "," + +char * +get_match(char *client, char *server) +{ + char *sproposals[MAX_PROP]; + char *c, *s, *p, *ret; + int i, j, nproposals; + + c = xstrdup(client); + s = xstrdup(server); + + for ((p = strtok(s, SEP)), i=0; p; (p = strtok(NULL, SEP)), i++) { + if (i < MAX_PROP) + sproposals[i] = p; + else + break; + } + nproposals = i; + + for ((p = strtok(c, SEP)), i=0; p; (p = strtok(NULL, SEP)), i++) { + for (j = 0; j < nproposals; j++) { + if (strcmp(p, sproposals[j]) == 0) { + ret = xstrdup(p); + xfree(c); + xfree(s); + return ret; + } + } + } + xfree(c); + xfree(s); + return NULL; +} +void +choose_enc(Enc *enc, char *client, char *server) +{ + char *name = get_match(client, server); + if (name == NULL) + fatal("no matching cipher found: client %s server %s", client, server); + enc->type = cipher_number(name); + + switch (enc->type) { + case SSH_CIPHER_3DES_CBC: + enc->key_len = 24; + enc->iv_len = 8; + enc->block_size = 8; + break; + case SSH_CIPHER_BLOWFISH_CBC: + case SSH_CIPHER_CAST128_CBC: + enc->key_len = 16; + enc->iv_len = 8; + enc->block_size = 8; + break; + case SSH_CIPHER_ARCFOUR: + enc->key_len = 16; + enc->iv_len = 0; + enc->block_size = 8; + break; + default: + fatal("unsupported cipher %s", name); + } + enc->name = name; + enc->enabled = 0; + enc->iv = NULL; + enc->key = NULL; +} +void +choose_mac(Mac *mac, char *client, char *server) +{ + char *name = get_match(client, server); + if (name == NULL) + fatal("no matching mac found: client %s server %s", client, server); + if (strcmp(name, "hmac-md5") == 0) { + mac->md = EVP_md5(); + } else if (strcmp(name, "hmac-sha1") == 0) { + mac->md = EVP_sha1(); + } else if (strcmp(name, "hmac-ripemd160@openssh.com") == 0) { + mac->md = EVP_ripemd160(); + } else { + fatal("unsupported mac %s", name); + } + mac->name = name; + mac->mac_len = mac->md->md_size; + mac->key_len = (datafellows & SSH_BUG_HMAC) ? 16 : mac->mac_len; + mac->key = NULL; + mac->enabled = 0; +} +void +choose_comp(Comp *comp, char *client, char *server) +{ + char *name = get_match(client, server); + if (name == NULL) + fatal("no matching comp found: client %s server %s", client, server); + if (strcmp(name, "zlib") == 0) { + comp->type = 1; + } else if (strcmp(name, "none") == 0) { + comp->type = 0; + } else { + fatal("unsupported comp %s", name); + } + comp->name = name; +} +void +choose_kex(Kex *k, char *client, char *server) +{ + k->name = get_match(client, server); + if (k->name == NULL) + fatal("no kex alg"); + if (strcmp(k->name, KEX_DH1) != 0) + fatal("bad kex alg %s", k->name); +} +void +choose_hostkeyalg(Kex *k, char *client, char *server) +{ + k->hostkeyalg = get_match(client, server); + if (k->hostkeyalg == NULL) + fatal("no hostkey alg"); + if (strcmp(k->hostkeyalg, KEX_DSS) != 0) + fatal("bad hostkey alg %s", k->hostkeyalg); +} + +Kex * +kex_choose_conf(char *cprop[PROPOSAL_MAX], char *sprop[PROPOSAL_MAX], int server) +{ + int mode; + int ctos; /* direction: if true client-to-server */ + int need; + Kex *k; + + k = xmalloc(sizeof(*k)); + memset(k, 0, sizeof(*k)); + k->server = server; + + for (mode = 0; mode < MODE_MAX; mode++) { + int nenc, nmac, ncomp; + ctos = (!k->server && mode == MODE_OUT) || (k->server && mode == MODE_IN); + nenc = ctos ? PROPOSAL_ENC_ALGS_CTOS : PROPOSAL_ENC_ALGS_STOC; + nmac = ctos ? PROPOSAL_MAC_ALGS_CTOS : PROPOSAL_MAC_ALGS_STOC; + ncomp = ctos ? PROPOSAL_COMP_ALGS_CTOS : PROPOSAL_COMP_ALGS_STOC; + choose_enc (&k->enc [mode], cprop[nenc], sprop[nenc]); + choose_mac (&k->mac [mode], cprop[nmac], sprop[nmac]); + choose_comp(&k->comp[mode], cprop[ncomp], sprop[ncomp]); + debug("kex: %s %s %s %s", + ctos ? "client->server" : "server->client", + k->enc[mode].name, + k->mac[mode].name, + k->comp[mode].name); + } + choose_kex(k, cprop[PROPOSAL_KEX_ALGS], sprop[PROPOSAL_KEX_ALGS]); + choose_hostkeyalg(k, cprop[PROPOSAL_SERVER_HOST_KEY_ALGS], + sprop[PROPOSAL_SERVER_HOST_KEY_ALGS]); + need = 0; + for (mode = 0; mode < MODE_MAX; mode++) { + if (need < k->enc[mode].key_len) + need = k->enc[mode].key_len; + if (need < k->enc[mode].iv_len) + need = k->enc[mode].iv_len; + if (need < k->mac[mode].key_len) + need = k->mac[mode].key_len; + } + /* XXX need runden? */ + k->we_need = need; + return k; +} + +int +kex_derive_keys(Kex *k, unsigned char *hash, BIGNUM *shared_secret) +{ + int i; + int mode; + int ctos; + unsigned char *keys[NKEYS]; + + for (i = 0; i < NKEYS; i++) + keys[i] = derive_key('A'+i, k->we_need, hash, shared_secret); + + for (mode = 0; mode < MODE_MAX; mode++) { + ctos = (!k->server && mode == MODE_OUT) || (k->server && mode == MODE_IN); + k->enc[mode].iv = keys[ctos ? 0 : 1]; + k->enc[mode].key = keys[ctos ? 2 : 3]; + k->mac[mode].key = keys[ctos ? 4 : 5]; + } + return 0; +} diff --git a/crypto/openssh/kex.h b/crypto/openssh/kex.h new file mode 100644 index 000000000000..7e5c67024bff --- /dev/null +++ b/crypto/openssh/kex.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Markus Friedl. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef KEX_H +#define KEX_H + +#define KEX_DH1 "diffie-hellman-group1-sha1" +#define KEX_DSS "ssh-dss" + +enum kex_init_proposals { + PROPOSAL_KEX_ALGS, + PROPOSAL_SERVER_HOST_KEY_ALGS, + PROPOSAL_ENC_ALGS_CTOS, + PROPOSAL_ENC_ALGS_STOC, + PROPOSAL_MAC_ALGS_CTOS, + PROPOSAL_MAC_ALGS_STOC, + PROPOSAL_COMP_ALGS_CTOS, + PROPOSAL_COMP_ALGS_STOC, + PROPOSAL_LANG_CTOS, + PROPOSAL_LANG_STOC, + PROPOSAL_MAX +}; + +enum kex_modes { + MODE_IN, + MODE_OUT, + MODE_MAX +}; + +typedef struct Kex Kex; +typedef struct Mac Mac; +typedef struct Comp Comp; +typedef struct Enc Enc; + +struct Enc { + int type; + int enabled; + int block_size; + unsigned char *key; + unsigned char *iv; + int key_len; + int iv_len; + char *name; +}; +struct Mac { + EVP_MD *md; + int enabled; + int mac_len; + unsigned char *key; + int key_len; + char *name; +}; +struct Comp { + int type; + int enabled; + char *name; +}; +struct Kex { + Enc enc [MODE_MAX]; + Mac mac [MODE_MAX]; + Comp comp[MODE_MAX]; + int we_need; + int server; + char *name; + char *hostkeyalg; +}; + +Buffer *kex_init(char *myproposal[PROPOSAL_MAX]); +void +kex_exchange_kexinit( + Buffer *my_kexinit, Buffer *peer_kexint, + char *peer_proposal[PROPOSAL_MAX]); +Kex * +kex_choose_conf(char *cprop[PROPOSAL_MAX], + char *sprop[PROPOSAL_MAX], int server); +int kex_derive_keys(Kex *k, unsigned char *hash, BIGNUM *shared_secret); +void packet_set_kex(Kex *k); +int dh_pub_is_valid(DH *dh, BIGNUM *dh_pub); +DH *dh_new_group1(); + +unsigned char * +kex_hash( + char *client_version_string, + char *server_version_string, + char *ckexinit, int ckexinitlen, + char *skexinit, int skexinitlen, + char *serverhostkeyblob, int sbloblen, + BIGNUM *client_dh_pub, + BIGNUM *server_dh_pub, + BIGNUM *shared_secret); + +#endif diff --git a/crypto/openssh/key.c b/crypto/openssh/key.c new file mode 100644 index 000000000000..b91bd2190edf --- /dev/null +++ b/crypto/openssh/key.c @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Markus Friedl. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ +/* + * read_bignum(): + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + */ + +#include "includes.h" +#include "ssh.h" +#include <openssl/rsa.h> +#include <openssl/dsa.h> +#include <openssl/evp.h> +#include "xmalloc.h" +#include "key.h" +#include "dsa.h" +#include "uuencode.h" + +#define SSH_DSS "ssh-dss" + +Key * +key_new(int type) +{ + Key *k; + RSA *rsa; + DSA *dsa; + k = xmalloc(sizeof(*k)); + k->type = type; + k->dsa = NULL; + k->rsa = NULL; + switch (k->type) { + case KEY_RSA: + rsa = RSA_new(); + rsa->n = BN_new(); + rsa->e = BN_new(); + k->rsa = rsa; + break; + case KEY_DSA: + dsa = DSA_new(); + dsa->p = BN_new(); + dsa->q = BN_new(); + dsa->g = BN_new(); + dsa->pub_key = BN_new(); + k->dsa = dsa; + break; + case KEY_EMPTY: + break; + default: + fatal("key_new: bad key type %d", k->type); + break; + } + return k; +} +void +key_free(Key *k) +{ + switch (k->type) { + case KEY_RSA: + if (k->rsa != NULL) + RSA_free(k->rsa); + k->rsa = NULL; + break; + case KEY_DSA: + if (k->dsa != NULL) + DSA_free(k->dsa); + k->dsa = NULL; + break; + default: + fatal("key_free: bad key type %d", k->type); + break; + } + xfree(k); +} +int +key_equal(Key *a, Key *b) +{ + if (a == NULL || b == NULL || a->type != b->type) + return 0; + switch (a->type) { + case KEY_RSA: + return a->rsa != NULL && b->rsa != NULL && + BN_cmp(a->rsa->e, b->rsa->e) == 0 && + BN_cmp(a->rsa->n, b->rsa->n) == 0; + break; + case KEY_DSA: + return a->dsa != NULL && b->dsa != NULL && + BN_cmp(a->dsa->p, b->dsa->p) == 0 && + BN_cmp(a->dsa->q, b->dsa->q) == 0 && + BN_cmp(a->dsa->g, b->dsa->g) == 0 && + BN_cmp(a->dsa->pub_key, b->dsa->pub_key) == 0; + break; + default: + fatal("key_equal: bad key type %d", a->type); + break; + } + return 0; +} + +#define FPRINT "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x" + +/* + * Generate key fingerprint in ascii format. + * Based on ideas and code from Bjoern Groenvall <bg@sics.se> + */ +char * +key_fingerprint(Key *k) +{ + static char retval[80]; + unsigned char *blob = NULL; + int len = 0; + int nlen, elen; + + switch (k->type) { + case KEY_RSA: + nlen = BN_num_bytes(k->rsa->n); + elen = BN_num_bytes(k->rsa->e); + len = nlen + elen; + blob = xmalloc(len); + BN_bn2bin(k->rsa->n, blob); + BN_bn2bin(k->rsa->e, blob + nlen); + break; + case KEY_DSA: + dsa_make_key_blob(k, &blob, &len); + break; + default: + fatal("key_fingerprint: bad key type %d", k->type); + break; + } + if (blob != NULL) { + unsigned char d[16]; + EVP_MD_CTX md; + EVP_DigestInit(&md, EVP_md5()); + EVP_DigestUpdate(&md, blob, len); + EVP_DigestFinal(&md, d, NULL); + snprintf(retval, sizeof(retval), FPRINT, + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], + d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15]); + memset(blob, 0, len); + xfree(blob); + } + return retval; +} + +/* + * Reads a multiple-precision integer in decimal from the buffer, and advances + * the pointer. The integer must already be initialized. This function is + * permitted to modify the buffer. This leaves *cpp to point just beyond the + * last processed (and maybe modified) character. Note that this may modify + * the buffer containing the number. + */ +int +read_bignum(char **cpp, BIGNUM * value) +{ + char *cp = *cpp; + int old; + + /* Skip any leading whitespace. */ + for (; *cp == ' ' || *cp == '\t'; cp++) + ; + + /* Check that it begins with a decimal digit. */ + if (*cp < '0' || *cp > '9') + return 0; + + /* Save starting position. */ + *cpp = cp; + + /* Move forward until all decimal digits skipped. */ + for (; *cp >= '0' && *cp <= '9'; cp++) + ; + + /* Save the old terminating character, and replace it by \0. */ + old = *cp; + *cp = 0; + + /* Parse the number. */ + if (BN_dec2bn(&value, *cpp) == 0) + return 0; + + /* Restore old terminating character. */ + *cp = old; + + /* Move beyond the number and return success. */ + *cpp = cp; + return 1; +} +int +write_bignum(FILE *f, BIGNUM *num) +{ + char *buf = BN_bn2dec(num); + if (buf == NULL) { + error("write_bignum: BN_bn2dec() failed"); + return 0; + } + fprintf(f, " %s", buf); + free(buf); + return 1; +} +unsigned int +key_read(Key *ret, char **cpp) +{ + Key *k; + unsigned int bits = 0; + char *cp; + int len, n; + unsigned char *blob; + + cp = *cpp; + + switch(ret->type) { + case KEY_RSA: + /* Get number of bits. */ + if (*cp < '0' || *cp > '9') + return 0; /* Bad bit count... */ + for (bits = 0; *cp >= '0' && *cp <= '9'; cp++) + bits = 10 * bits + *cp - '0'; + if (bits == 0) + return 0; + *cpp = cp; + /* Get public exponent, public modulus. */ + if (!read_bignum(cpp, ret->rsa->e)) + return 0; + if (!read_bignum(cpp, ret->rsa->n)) + return 0; + break; + case KEY_DSA: + if (strncmp(cp, SSH_DSS " ", 7) != 0) + return 0; + cp += 7; + len = 2*strlen(cp); + blob = xmalloc(len); + n = uudecode(cp, blob, len); + if (n < 0) { + error("key_read: uudecode %s failed", cp); + return 0; + } + k = dsa_key_from_blob(blob, n); + if (k == NULL) { + error("key_read: dsa_key_from_blob %s failed", cp); + return 0; + } + xfree(blob); + if (ret->dsa != NULL) + DSA_free(ret->dsa); + ret->dsa = k->dsa; + k->dsa = NULL; + key_free(k); + bits = BN_num_bits(ret->dsa->p); + /* advance cp: skip whitespace and data */ + while (*cp == ' ' || *cp == '\t') + cp++; + while (*cp != '\0' && *cp != ' ' && *cp != '\t') + cp++; + *cpp = cp; + break; + default: + fatal("key_read: bad key type: %d", ret->type); + break; + } + return bits; +} +int +key_write(Key *key, FILE *f) +{ + int success = 0; + unsigned int bits = 0; + + if (key->type == KEY_RSA && key->rsa != NULL) { + /* size of modulus 'n' */ + bits = BN_num_bits(key->rsa->n); + fprintf(f, "%u", bits); + if (write_bignum(f, key->rsa->e) && + write_bignum(f, key->rsa->n)) { + success = 1; + } else { + error("key_write: failed for RSA key"); + } + } else if (key->type == KEY_DSA && key->dsa != NULL) { + int len, n; + unsigned char *blob, *uu; + dsa_make_key_blob(key, &blob, &len); + uu = xmalloc(2*len); + n = uuencode(blob, len, uu, 2*len); + if (n > 0) { + fprintf(f, "%s %s", SSH_DSS, uu); + success = 1; + } + xfree(blob); + xfree(uu); + } + return success; +} +char * +key_type(Key *k) +{ + switch (k->type) { + case KEY_RSA: + return "RSA"; + break; + case KEY_DSA: + return "DSA"; + break; + } + return "unknown"; +} diff --git a/crypto/openssh/myproposal.h b/crypto/openssh/myproposal.h new file mode 100644 index 000000000000..9611d89517d8 --- /dev/null +++ b/crypto/openssh/myproposal.h @@ -0,0 +1,20 @@ +#define KEX_DEFAULT_KEX "diffie-hellman-group1-sha1" +#define KEX_DEFAULT_PK_ALG "ssh-dss" +#define KEX_DEFAULT_ENCRYPT "3des-cbc,blowfish-cbc,arcfour,cast128-cbc" +#define KEX_DEFAULT_MAC "hmac-sha1,hmac-md5,hmac-ripemd160@openssh.com" +#define KEX_DEFAULT_COMP "zlib,none" +#define KEX_DEFAULT_LANG "" + + +static char *myproposal[PROPOSAL_MAX] = { + KEX_DEFAULT_KEX, + KEX_DEFAULT_PK_ALG, + KEX_DEFAULT_ENCRYPT, + KEX_DEFAULT_ENCRYPT, + KEX_DEFAULT_MAC, + KEX_DEFAULT_MAC, + KEX_DEFAULT_COMP, + KEX_DEFAULT_COMP, + KEX_DEFAULT_LANG, + KEX_DEFAULT_LANG +}; diff --git a/crypto/openssh/session.c b/crypto/openssh/session.c new file mode 100644 index 000000000000..f4c615d23725 --- /dev/null +++ b/crypto/openssh/session.c @@ -0,0 +1,1772 @@ +/* + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + */ +/* + * SSH2 support by Markus Friedl. + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * $FreeBSD$ + */ + +#include "includes.h" +RCSID("$OpenBSD: session.c,v 1.15 2000/05/30 17:23:37 markus Exp $"); + +#include "xmalloc.h" +#include "ssh.h" +#include "pty.h" +#include "packet.h" +#include "buffer.h" +#include "cipher.h" +#include "mpaux.h" +#include "servconf.h" +#include "uidswap.h" +#include "compat.h" +#include "channels.h" +#include "nchan.h" + +#include "bufaux.h" +#include "ssh2.h" +#include "auth.h" + +#ifdef __FreeBSD__ +#define LOGIN_CAP +#define _PATH_CHPASS "/usr/bin/passwd" +#endif /* __FreeBSD__ */ + +#ifdef LOGIN_CAP +#include <login_cap.h> +#endif /* LOGIN_CAP */ + +#ifdef KRB5 +extern krb5_context ssh_context; +#endif + +/* types */ + +#define TTYSZ 64 +typedef struct Session Session; +struct Session { + int used; + int self; + int extended; + struct passwd *pw; + pid_t pid; + /* tty */ + char *term; + int ptyfd, ttyfd, ptymaster; + int row, col, xpixel, ypixel; + char tty[TTYSZ]; + /* X11 */ + char *display; + int screen; + char *auth_proto; + char *auth_data; + int single_connection; + /* proto 2 */ + int chanid; +}; + +/* func */ + +Session *session_new(void); +void session_set_fds(Session *s, int fdin, int fdout, int fderr); +void session_pty_cleanup(Session *s); +void session_proctitle(Session *s); +void do_exec_pty(Session *s, const char *command, struct passwd * pw); +void do_exec_no_pty(Session *s, const char *command, struct passwd * pw); + +void +do_child(const char *command, struct passwd * pw, const char *term, + const char *display, const char *auth_proto, + const char *auth_data, const char *ttyname); + +/* import */ +extern ServerOptions options; +extern char *__progname; +extern int log_stderr; +extern int debug_flag; + +/* Local Xauthority file. */ +static char *xauthfile; + +/* data */ +#define MAX_SESSIONS 10 +Session sessions[MAX_SESSIONS]; + +/* Flags set in auth-rsa from authorized_keys flags. These are set in auth-rsa.c. */ +int no_port_forwarding_flag = 0; +int no_agent_forwarding_flag = 0; +int no_x11_forwarding_flag = 0; +int no_pty_flag = 0; + +/* RSA authentication "command=" option. */ +char *forced_command = NULL; + +/* RSA authentication "environment=" options. */ +struct envstring *custom_environment = NULL; + +/* + * Remove local Xauthority file. + */ +void +xauthfile_cleanup_proc(void *ignore) +{ + debug("xauthfile_cleanup_proc called"); + + if (xauthfile != NULL) { + char *p; + unlink(xauthfile); + p = strrchr(xauthfile, '/'); + if (p != NULL) { + *p = '\0'; + rmdir(xauthfile); + } + xfree(xauthfile); + xauthfile = NULL; + } +} + +/* + * Function to perform cleanup if we get aborted abnormally (e.g., due to a + * dropped connection). + */ +void +pty_cleanup_proc(void *session) +{ + Session *s=session; + if (s == NULL) + fatal("pty_cleanup_proc: no session"); + debug("pty_cleanup_proc: %s", s->tty); + + if (s->pid != 0) { + /* Record that the user has logged out. */ + record_logout(s->pid, s->tty); + } + + /* Release the pseudo-tty. */ + pty_release(s->tty); +} + +/* + * Prepares for an interactive session. This is called after the user has + * been successfully authenticated. During this message exchange, pseudo + * terminals are allocated, X11, TCP/IP, and authentication agent forwardings + * are requested, etc. + */ +void +do_authenticated(struct passwd * pw) +{ + Session *s; + int type; + int compression_level = 0, enable_compression_after_reply = 0; + int have_pty = 0; + char *command; + int n_bytes; + int plen; + unsigned int proto_len, data_len, dlen; + + /* + * Cancel the alarm we set to limit the time taken for + * authentication. + */ + alarm(0); + + /* + * Inform the channel mechanism that we are the server side and that + * the client may request to connect to any port at all. (The user + * could do it anyway, and we wouldn\'t know what is permitted except + * by the client telling us, so we can equally well trust the client + * not to request anything bogus.) + */ + if (!no_port_forwarding_flag) + channel_permit_all_opens(); + + s = session_new(); + s->pw = pw; + + /* + * We stay in this loop until the client requests to execute a shell + * or a command. + */ + for (;;) { + int success = 0; + + /* Get a packet from the client. */ + type = packet_read(&plen); + + /* Process the packet. */ + switch (type) { + case SSH_CMSG_REQUEST_COMPRESSION: + packet_integrity_check(plen, 4, type); + compression_level = packet_get_int(); + if (compression_level < 1 || compression_level > 9) { + packet_send_debug("Received illegal compression level %d.", + compression_level); + break; + } + /* Enable compression after we have responded with SUCCESS. */ + enable_compression_after_reply = 1; + success = 1; + break; + + case SSH_CMSG_REQUEST_PTY: + if (no_pty_flag) { + debug("Allocating a pty not permitted for this authentication."); + break; + } + if (have_pty) + packet_disconnect("Protocol error: you already have a pty."); + + debug("Allocating pty."); + + /* Allocate a pty and open it. */ + if (!pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, + sizeof(s->tty))) { + error("Failed to allocate pty."); + break; + } + fatal_add_cleanup(pty_cleanup_proc, (void *)s); + pty_setowner(pw, s->tty); + + /* Get TERM from the packet. Note that the value may be of arbitrary length. */ + s->term = packet_get_string(&dlen); + packet_integrity_check(dlen, strlen(s->term), type); + /* packet_integrity_check(plen, 4 + dlen + 4*4 + n_bytes, type); */ + /* Remaining bytes */ + n_bytes = plen - (4 + dlen + 4 * 4); + + if (strcmp(s->term, "") == 0) { + xfree(s->term); + s->term = NULL; + } + /* Get window size from the packet. */ + s->row = packet_get_int(); + s->col = 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); + + /* Get tty modes from the packet. */ + tty_parse_modes(s->ttyfd, &n_bytes); + packet_integrity_check(plen, 4 + dlen + 4 * 4 + n_bytes, type); + + session_proctitle(s); + + /* Indicate that we now have a pty. */ + success = 1; + have_pty = 1; + break; + + case SSH_CMSG_X11_REQUEST_FORWARDING: + if (!options.x11_forwarding) { + packet_send_debug("X11 forwarding disabled in server configuration file."); + break; + } +#ifdef XAUTH_PATH + if (no_x11_forwarding_flag) { + packet_send_debug("X11 forwarding not permitted for this authentication."); + break; + } + debug("Received request for X11 forwarding with auth spoofing."); + if (s->display != NULL) + packet_disconnect("Protocol error: X11 display already set."); + + s->auth_proto = packet_get_string(&proto_len); + s->auth_data = packet_get_string(&data_len); + packet_integrity_check(plen, 4 + proto_len + 4 + data_len + 4, type); + + if (packet_get_protocol_flags() & SSH_PROTOFLAG_SCREEN_NUMBER) + s->screen = packet_get_int(); + else + s->screen = 0; + s->display = x11_create_display_inet(s->screen, options.x11_display_offset); + + if (s->display == NULL) + break; + + /* Setup to always have a local .Xauthority. */ + xauthfile = xmalloc(MAXPATHLEN); + strlcpy(xauthfile, "/tmp/ssh-XXXXXXXX", MAXPATHLEN); + temporarily_use_uid(pw->pw_uid); + if (mkdtemp(xauthfile) == NULL) { + restore_uid(); + error("private X11 dir: mkdtemp %s failed: %s", + xauthfile, strerror(errno)); + xfree(xauthfile); + xauthfile = NULL; + /* XXXX remove listening channels */ + break; + } + strlcat(xauthfile, "/cookies", MAXPATHLEN); + open(xauthfile, O_RDWR|O_CREAT|O_EXCL, 0600); + restore_uid(); + fatal_add_cleanup(xauthfile_cleanup_proc, NULL); + success = 1; + break; +#else /* XAUTH_PATH */ + packet_send_debug("No xauth program; cannot forward with spoofing."); + break; +#endif /* XAUTH_PATH */ + + case SSH_CMSG_AGENT_REQUEST_FORWARDING: + if (no_agent_forwarding_flag || compat13) { + debug("Authentication agent forwarding not permitted for this authentication."); + break; + } + debug("Received authentication agent forwarding request."); + success = auth_input_request_forwarding(pw); + break; + + case SSH_CMSG_PORT_FORWARD_REQUEST: + if (no_port_forwarding_flag) { + debug("Port forwarding not permitted for this authentication."); + break; + } + debug("Received TCP/IP port forwarding request."); + channel_input_port_forward_request(pw->pw_uid == 0, options.gateway_ports); + success = 1; + break; + + case SSH_CMSG_MAX_PACKET_SIZE: + if (packet_set_maxsize(packet_get_int()) > 0) + success = 1; + break; + + case SSH_CMSG_EXEC_SHELL: + case SSH_CMSG_EXEC_CMD: + /* Set interactive/non-interactive mode. */ + packet_set_interactive(have_pty || s->display != NULL, + options.keepalives); + + if (type == SSH_CMSG_EXEC_CMD) { + command = packet_get_string(&dlen); + debug("Exec command '%.500s'", command); + packet_integrity_check(plen, 4 + dlen, type); + } else { + command = NULL; + packet_integrity_check(plen, 0, type); + } + if (forced_command != NULL) { + command = forced_command; + debug("Forced command '%.500s'", forced_command); + } + if (have_pty) + do_exec_pty(s, command, pw); + else + do_exec_no_pty(s, command, pw); + + if (command != NULL) + xfree(command); + /* Cleanup user's local Xauthority file. */ + if (xauthfile) + xauthfile_cleanup_proc(NULL); + return; + + default: + /* + * Any unknown messages in this phase are ignored, + * and a failure message is returned. + */ + log("Unknown packet type received after authentication: %d", type); + } + packet_start(success ? SSH_SMSG_SUCCESS : SSH_SMSG_FAILURE); + packet_send(); + packet_write_wait(); + + /* Enable compression now that we have replied if appropriate. */ + if (enable_compression_after_reply) { + enable_compression_after_reply = 0; + packet_start_compression(compression_level); + } + } +} + +/* + * This is called to fork and execute a command when we have no tty. This + * will call do_child from the child, and server_loop from the parent after + * setting up file descriptors and such. + */ +void +do_exec_no_pty(Session *s, const char *command, struct passwd * pw) +{ + int pid; + +#ifdef USE_PIPES + int pin[2], pout[2], perr[2]; + /* Allocate pipes for communicating with the program. */ + if (pipe(pin) < 0 || pipe(pout) < 0 || pipe(perr) < 0) + packet_disconnect("Could not create pipes: %.100s", + strerror(errno)); +#else /* USE_PIPES */ + int inout[2], err[2]; + /* Uses socket pairs to communicate with the program. */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) < 0 || + socketpair(AF_UNIX, SOCK_STREAM, 0, err) < 0) + packet_disconnect("Could not create socket pairs: %.100s", + strerror(errno)); +#endif /* USE_PIPES */ + if (s == NULL) + fatal("do_exec_no_pty: no session"); + + session_proctitle(s); + + /* Fork the child. */ + if ((pid = fork()) == 0) { + /* Child. Reinitialize the log since the pid has changed. */ + log_init(__progname, options.log_level, options.log_facility, log_stderr); + + /* + * Create a new session and process group since the 4.4BSD + * setlogin() affects the entire process group. + */ + if (setsid() < 0) + error("setsid failed: %.100s", strerror(errno)); + +#ifdef USE_PIPES + /* + * Redirect stdin. We close the parent side of the socket + * pair, and make the child side the standard input. + */ + close(pin[1]); + if (dup2(pin[0], 0) < 0) + perror("dup2 stdin"); + close(pin[0]); + + /* Redirect stdout. */ + close(pout[0]); + if (dup2(pout[1], 1) < 0) + perror("dup2 stdout"); + close(pout[1]); + + /* Redirect stderr. */ + close(perr[0]); + if (dup2(perr[1], 2) < 0) + perror("dup2 stderr"); + close(perr[1]); +#else /* USE_PIPES */ + /* + * Redirect stdin, stdout, and stderr. Stdin and stdout will + * use the same socket, as some programs (particularly rdist) + * seem to depend on it. + */ + close(inout[1]); + close(err[1]); + if (dup2(inout[0], 0) < 0) /* stdin */ + perror("dup2 stdin"); + if (dup2(inout[0], 1) < 0) /* stdout. Note: same socket as stdin. */ + perror("dup2 stdout"); + if (dup2(err[0], 2) < 0) /* stderr */ + perror("dup2 stderr"); +#endif /* USE_PIPES */ + + /* Do processing for the child (exec command etc). */ + do_child(command, pw, NULL, s->display, s->auth_proto, s->auth_data, NULL); + /* NOTREACHED */ + } + if (pid < 0) + packet_disconnect("fork failed: %.100s", strerror(errno)); + s->pid = pid; +#ifdef USE_PIPES + /* We are the parent. Close the child sides of the pipes. */ + close(pin[0]); + close(pout[1]); + close(perr[1]); + + if (compat20) { + session_set_fds(s, pin[1], pout[0], s->extended ? perr[0] : -1); + } 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]); + close(err[0]); + + /* + * Enter the interactive session. Note: server_loop must be able to + * handle the case that fdin and fdout are the same. + */ + if (compat20) { + session_set_fds(s, inout[1], inout[1], s->extended ? err[1] : -1); + } else { + server_loop(pid, inout[1], inout[1], err[1]); + /* server_loop has closed inout[1] and err[1]. */ + } +#endif /* USE_PIPES */ +} + +/* + * This is called to fork and execute a command when we have a tty. This + * will call do_child from the child, and server_loop from the parent after + * setting up file descriptors, controlling tty, updating wtmp, utmp, + * lastlog, and other such operations. + */ +void +do_exec_pty(Session *s, const char *command, struct passwd * pw) +{ + FILE *f; + char buf[100], *time_string; + char line[256]; + const char *hostname; + int fdout, ptyfd, ttyfd, ptymaster; + int quiet_login; + pid_t pid; + socklen_t fromlen; + struct sockaddr_storage from; + struct stat st; + time_t last_login_time; +#ifdef LOGIN_CAP + login_cap_t *lc; + char *fname; +#endif /* LOGIN_CAP */ +#ifdef __FreeBSD__ +#define DEFAULT_WARN (2L * 7L * 86400L) /* Two weeks */ + struct timeval tv; + time_t warntime = DEFAULT_WARN; +#endif /* __FreeBSD__ */ + + if (s == NULL) + fatal("do_exec_pty: no session"); + ptyfd = s->ptyfd; + ttyfd = s->ttyfd; + + /* Get remote host name. */ + hostname = get_canonical_hostname(); + + /* + * Get the time when the user last logged in. Buf will be set to + * contain the hostname the last login was from. + */ + if (!options.use_login) { + last_login_time = get_last_login_time(pw->pw_uid, pw->pw_name, + buf, sizeof(buf)); + } + + /* Fork the child. */ + if ((pid = fork()) == 0) { + pid = getpid(); + + /* Child. Reinitialize the log because the pid has + changed. */ + log_init(__progname, options.log_level, options.log_facility, log_stderr); + + /* Close the master side of the pseudo tty. */ + close(ptyfd); + + /* Make the pseudo tty our controlling tty. */ + pty_make_controlling_tty(&ttyfd, s->tty); + + /* Redirect stdin from the pseudo tty. */ + if (dup2(ttyfd, fileno(stdin)) < 0) + error("dup2 stdin failed: %.100s", strerror(errno)); + + /* Redirect stdout to the pseudo tty. */ + if (dup2(ttyfd, fileno(stdout)) < 0) + error("dup2 stdin failed: %.100s", strerror(errno)); + + /* Redirect stderr to the pseudo tty. */ + if (dup2(ttyfd, fileno(stderr)) < 0) + error("dup2 stdin failed: %.100s", strerror(errno)); + + /* Close the extra descriptor for the pseudo tty. */ + close(ttyfd); + +/* XXXX ? move to do_child() ??*/ + /* + * Get IP address of client. This is needed because we want + * to record where the user logged in from. If the + * connection is not a socket, let the ip address be 0.0.0.0. + */ + memset(&from, 0, sizeof(from)); + if (packet_connection_is_on_socket()) { + fromlen = sizeof(from); + if (getpeername(packet_get_connection_in(), + (struct sockaddr *) & from, &fromlen) < 0) { + debug("getpeername: %.100s", strerror(errno)); + fatal_cleanup(); + } + } + /* Record that there was a login on that terminal. */ + record_login(pid, s->tty, pw->pw_name, pw->pw_uid, hostname, + (struct sockaddr *)&from); + + /* Check if .hushlogin exists. */ + snprintf(line, sizeof line, "%.200s/.hushlogin", pw->pw_dir); + quiet_login = stat(line, &st) >= 0; + +#ifdef LOGIN_CAP + lc = login_getpwclass(pw); + if (lc == NULL) + lc = login_getclassbyname(NULL, pw); + quiet_login = login_getcapbool(lc, "hushlogin", quiet_login); +#endif /* LOGIN_CAP */ + +#ifdef __FreeBSD__ + if (pw->pw_change || pw->pw_expire) + (void)gettimeofday(&tv, NULL); +#ifdef LOGIN_CAP + warntime = login_getcaptime(lc, "warnpassword", + DEFAULT_WARN, DEFAULT_WARN); +#endif /* LOGIN_CAP */ + /* + * If the password change time is set and has passed, give the + * user a password expiry notice and chance to change it. + */ + if (pw->pw_change != 0) { + if (tv.tv_sec >= pw->pw_change) { + (void)printf( + "Sorry -- your password has expired.\n"); + log("%s Password expired - forcing change", + pw->pw_name); + command = _PATH_CHPASS; + } else if (pw->pw_change - tv.tv_sec < warntime && + !quiet_login) + (void)printf( + "Warning: your password expires on %s", + ctime(&pw->pw_change)); + } +#ifdef LOGIN_CAP + warntime = login_getcaptime(lc, "warnexpire", + DEFAULT_WARN, DEFAULT_WARN); +#endif /* LOGIN_CAP */ + if (pw->pw_expire) { + if (tv.tv_sec >= pw->pw_expire) { + (void)printf( + "Sorry -- your account has expired.\n"); + log( + "LOGIN %.200s REFUSED (EXPIRED) FROM %.200s ON TTY %.200s", + pw->pw_name, hostname, ttyname); + exit(254); + } else if (pw->pw_expire - tv.tv_sec < warntime && + !quiet_login) + (void)printf( + "Warning: your account expires on %s", + ctime(&pw->pw_expire)); + } +#endif /* __FreeBSD__ */ +#ifdef LOGIN_CAP + if (!auth_ttyok(lc, ttyname)) { + (void)printf("Permission denied.\n"); + log( + "LOGIN %.200s REFUSED (TTY) FROM %.200s ON TTY %.200s", + pw->pw_name, hostname, ttyname); + exit(254); + } +#endif /* LOGIN_CAP */ + + /* + * If the user has logged in before, display the time of last + * login. However, don't display anything extra if a command + * has been specified (so that ssh can be used to execute + * commands on a remote machine without users knowing they + * are going to another machine). Login(1) will do this for + * us as well, so check if login(1) is used + */ + if (command == NULL && last_login_time != 0 && !quiet_login && + !options.use_login) { + /* Convert the date to a string. */ + time_string = ctime(&last_login_time); + /* Remove the trailing newline. */ + if (strchr(time_string, '\n')) + *strchr(time_string, '\n') = 0; + /* Display the last login time. Host if displayed + if known. */ + if (strcmp(buf, "") == 0) + printf("Last login: %s\r\n", time_string); + else + printf("Last login: %s from %s\r\n", time_string, buf); + } + +#ifdef LOGIN_CAP + if (command == NULL && !quiet_login && !options.use_login) { + fname = login_getcapstr(lc, "copyright", NULL, NULL); + if (fname != NULL && (f = fopen(fname, "r")) != NULL) { + while (fgets(line, sizeof(line), f) != NULL) + fputs(line, stdout); + fclose(f); + } else + (void)printf("%s\n\t%s %s\n", + "Copyright (c) 1980, 1983, 1986, 1988, 1990, 1991, 1993, 1994", + "The Regents of the University of California. ", + "All rights reserved."); + } +#endif /* LOGIN_CAP */ + + /* + * Print /etc/motd unless a command was specified or printing + * it was disabled in server options or login(1) will be + * used. Note that some machines appear to print it in + * /etc/profile or similar. + */ + if (command == NULL && options.print_motd && !quiet_login && + !options.use_login) { +#ifdef LOGIN_CAP + fname = login_getcapstr(lc, "welcome", NULL, NULL); + if (fname == NULL || (f = fopen(fname, "r")) == NULL) + f = fopen("/etc/motd", "r"); +#else /* !LOGIN_CAP */ + f = fopen("/etc/motd", "r"); +#endif /* LOGIN_CAP */ + /* Print /etc/motd if it exists. */ + if (f) { + while (fgets(line, sizeof(line), f)) + fputs(line, stdout); + fclose(f); + } + } +#ifdef LOGIN_CAP + login_close(lc); +#endif /* LOGIN_CAP */ + + /* Do common processing for the child, such as execing the command. */ + do_child(command, pw, s->term, s->display, s->auth_proto, + s->auth_data, s->tty); + /* NOTREACHED */ + } + if (pid < 0) + packet_disconnect("fork failed: %.100s", strerror(errno)); + s->pid = pid; + + /* Parent. Close the slave side of the pseudo tty. */ + close(ttyfd); + + /* + * Create another descriptor of the pty master side for use as the + * standard input. We could use the original descriptor, but this + * simplifies code in server_loop. The descriptor is bidirectional. + */ + fdout = dup(ptyfd); + if (fdout < 0) + packet_disconnect("dup #1 failed: %.100s", strerror(errno)); + + /* we keep a reference to the pty master */ + ptymaster = dup(ptyfd); + if (ptymaster < 0) + packet_disconnect("dup #2 failed: %.100s", strerror(errno)); + s->ptymaster = ptymaster; + + /* Enter interactive session. */ + 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); + } +} + +/* + * Sets the value of the given variable in the environment. If the variable + * already exists, its value is overriden. + */ +void +child_set_env(char ***envp, unsigned int *envsizep, const char *name, + const char *value) +{ + unsigned int i, namelen; + char **env; + + /* + * Find the slot where the value should be stored. If the variable + * already exists, we reuse the slot; otherwise we append a new slot + * at the end of the array, expanding if necessary. + */ + env = *envp; + namelen = strlen(name); + for (i = 0; env[i]; i++) + if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=') + break; + if (env[i]) { + /* Reuse the slot. */ + xfree(env[i]); + } else { + /* New variable. Expand if necessary. */ + if (i >= (*envsizep) - 1) { + (*envsizep) += 50; + env = (*envp) = xrealloc(env, (*envsizep) * sizeof(char *)); + } + /* Need to set the NULL pointer at end of array beyond the new slot. */ + env[i + 1] = NULL; + } + + /* Allocate space and format the variable in the appropriate slot. */ + env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1); + snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); +} + +/* + * Reads environment variables from the given file and adds/overrides them + * into the environment. If the file does not exist, this does nothing. + * Otherwise, it must consist of empty lines, comments (line starts with '#') + * and assignments of the form name=value. No other forms are allowed. + */ +void +read_environment_file(char ***env, unsigned int *envsize, + const char *filename) +{ + FILE *f; + char buf[4096]; + char *cp, *value; + + f = fopen(filename, "r"); + if (!f) + return; + + while (fgets(buf, sizeof(buf), f)) { + for (cp = buf; *cp == ' ' || *cp == '\t'; cp++) + ; + if (!*cp || *cp == '#' || *cp == '\n') + continue; + if (strchr(cp, '\n')) + *strchr(cp, '\n') = '\0'; + value = strchr(cp, '='); + if (value == NULL) { + fprintf(stderr, "Bad line in %.100s: %.200s\n", filename, buf); + continue; + } + /* + * Replace the equals sign by nul, and advance value to + * the value string. + */ + *value = '\0'; + value++; + child_set_env(env, envsize, cp, value); + } + fclose(f); +} + +/* + * Performs common processing for the child, such as setting up the + * environment, closing extra file descriptors, setting the user and group + * ids, and executing the command or shell. + */ +void +do_child(const char *command, struct passwd * pw, const char *term, + const char *display, const char *auth_proto, + const char *auth_data, const char *ttyname) +{ + char *shell; + const char *cp = NULL; + char buf[256]; + FILE *f; + unsigned int envsize, i; + char **env = NULL; + extern char **environ; + struct stat st; + char *argv[10]; + +#ifdef LOGIN_CAP + login_cap_t *lc; + + lc = login_getpwclass(pw); + if (lc == NULL) + lc = login_getclassbyname(NULL, pw); + if (pw->pw_uid != 0) + auth_checknologin(lc); +#else /* !LOGIN_CAP */ + f = fopen("/etc/nologin", "r"); + if (f) { + /* /etc/nologin exists. Print its contents and exit. */ + while (fgets(buf, sizeof(buf), f)) + fputs(buf, stderr); + fclose(f); + if (pw->pw_uid != 0) + exit(254); + } +#endif /* LOGIN_CAP */ + +#ifdef LOGIN_CAP + if (options.use_login) +#endif /* LOGIN_CAP */ + /* Set login name in the kernel. */ + if (setlogin(pw->pw_name) < 0) + error("setlogin failed: %s", strerror(errno)); + + /* Set uid, gid, and groups. */ + /* Login(1) does this as well, and it needs uid 0 for the "-h" + switch, so we let login(1) to this for us. */ + if (!options.use_login) { +#ifdef LOGIN_CAP + char **tmpenv; + + /* Initialize temp environment */ + envsize = 64; + env = xmalloc(envsize * sizeof(char *)); + env[0] = NULL; + + child_set_env(&env, &envsize, "PATH", + (pw->pw_uid == 0) ? + _PATH_STDPATH : _PATH_DEFPATH); + + snprintf(buf, sizeof buf, "%.200s/%.50s", + _PATH_MAILDIR, pw->pw_name); + child_set_env(&env, &envsize, "MAIL", buf); + + if (getenv("TZ")) + child_set_env(&env, &envsize, "TZ", getenv("TZ")); + + /* Save parent environment */ + tmpenv = environ; + environ = env; + + if (setusercontext(lc, pw, pw->pw_uid, LOGIN_SETALL) < 0) + fatal("setusercontext failed: %s", strerror(errno)); + + /* Restore parent environment */ + env = environ; + environ = tmpenv; + + for (envsize = 0; env[envsize] != NULL; ++envsize) + ; + envsize = (envsize < 100) ? 100 : envsize + 16; + env = xrealloc(env, envsize * sizeof(char *)); + +#else /* !LOGIN_CAP */ + if (getuid() == 0 || geteuid() == 0) { + if (setgid(pw->pw_gid) < 0) { + perror("setgid"); + exit(1); + } + /* Initialize the group list. */ + if (initgroups(pw->pw_name, pw->pw_gid) < 0) { + perror("initgroups"); + exit(1); + } + endgrent(); + + /* Permanently switch to the desired uid. */ + permanently_set_uid(pw->pw_uid); + } + if (getuid() != pw->pw_uid || geteuid() != pw->pw_uid) + fatal("Failed to set uids to %d.", (int) pw->pw_uid); +#endif /* LOGIN_CAP */ + } + /* + * Get the shell from the password data. An empty shell field is + * legal, and means /bin/sh. + */ + shell = (pw->pw_shell[0] == '\0') ? _PATH_BSHELL : pw->pw_shell; +#ifdef LOGIN_CAP + shell = login_getcapstr(lc, "shell", shell, shell); +#endif /* LOGIN_CAP */ + +#ifdef AFS + /* Try to get AFS tokens for the local cell. */ + if (k_hasafs()) { + char cell[64]; + + if (k_afs_cell_of_file(pw->pw_dir, cell, sizeof(cell)) == 0) + krb_afslog(cell, 0); + + krb_afslog(0, 0); + } +#endif /* AFS */ + + /* Initialize the environment. */ + if (env == NULL) { + envsize = 100; + env = xmalloc(envsize * sizeof(char *)); + env[0] = NULL; + } + + if (!options.use_login) { + /* Set basic environment. */ + child_set_env(&env, &envsize, "USER", pw->pw_name); + child_set_env(&env, &envsize, "LOGNAME", pw->pw_name); + child_set_env(&env, &envsize, "HOME", pw->pw_dir); +#ifndef LOGIN_CAP + child_set_env(&env, &envsize, "PATH", _PATH_STDPATH); + + snprintf(buf, sizeof buf, "%.200s/%.50s", + _PATH_MAILDIR, pw->pw_name); + child_set_env(&env, &envsize, "MAIL", buf); +#endif /* !LOGIN_CAP */ + + /* Normal systems set SHELL by default. */ + child_set_env(&env, &envsize, "SHELL", shell); + } +#ifdef LOGIN_CAP + if (options.use_login) +#endif /* LOGIN_CAP */ + if (getenv("TZ")) + child_set_env(&env, &envsize, "TZ", getenv("TZ")); + + /* Set custom environment options from RSA authentication. */ + while (custom_environment) { + struct envstring *ce = custom_environment; + char *s = ce->s; + int i; + for (i = 0; s[i] != '=' && s[i]; i++); + if (s[i] == '=') { + s[i] = 0; + child_set_env(&env, &envsize, s, s + i + 1); + } + custom_environment = ce->next; + xfree(ce->s); + xfree(ce); + } + + snprintf(buf, sizeof buf, "%.50s %d %d", + get_remote_ipaddr(), get_remote_port(), get_local_port()); + child_set_env(&env, &envsize, "SSH_CLIENT", buf); + + if (ttyname) + child_set_env(&env, &envsize, "SSH_TTY", ttyname); + if (term) + child_set_env(&env, &envsize, "TERM", term); + if (display) + child_set_env(&env, &envsize, "DISPLAY", display); + +#ifdef KRB4 + { + extern char *ticket; + + if (ticket) + child_set_env(&env, &envsize, "KRBTKFILE", ticket); + } +#endif /* KRB4 */ +#ifdef KRB5 +{ + extern krb5_ccache mem_ccache; + + if (mem_ccache) { + krb5_error_code problem; + krb5_ccache ccache; +#ifdef AFS + if (k_hasafs()) + krb5_afslog(ssh_context, mem_ccache, NULL, NULL); +#endif /* AFS */ + + problem = krb5_cc_default(ssh_context, &ccache); + if (problem) {} + else { + problem = krb5_cc_copy_cache(ssh_context, mem_ccache, ccache); + if (problem) {} + } + + krb5_cc_close(ssh_context, ccache); + } + + krb5_cleanup_proc(NULL); + } +#endif /* KRB5 */ + + if (xauthfile) + child_set_env(&env, &envsize, "XAUTHORITY", xauthfile); + if (auth_get_socket_name() != NULL) + child_set_env(&env, &envsize, SSH_AUTHSOCKET_ENV_NAME, + auth_get_socket_name()); + + /* read $HOME/.ssh/environment. */ + if (!options.use_login) { + snprintf(buf, sizeof buf, "%.200s/.ssh/environment", + pw->pw_dir); + read_environment_file(&env, &envsize, buf); + } + if (debug_flag) { + /* dump the environment */ + fprintf(stderr, "Environment:\n"); + for (i = 0; env[i]; i++) + fprintf(stderr, " %.200s\n", env[i]); + } + /* + * Close the connection descriptors; note that this is the child, and + * the server will still have the socket open, and it is important + * that we do not shutdown it. Note that the descriptors cannot be + * closed before building the environment, as we call + * get_remote_ipaddr there. + */ + if (packet_get_connection_in() == packet_get_connection_out()) + close(packet_get_connection_in()); + else { + close(packet_get_connection_in()); + close(packet_get_connection_out()); + } + /* + * Close all descriptors related to channels. They will still remain + * open in the parent. + */ + /* XXX better use close-on-exec? -markus */ + channel_close_all(); + + /* + * Close any extra file descriptors. Note that there may still be + * descriptors left by system functions. They will be closed later. + */ + endpwent(); + + /* + * Close any extra open file descriptors so that we don\'t have them + * hanging around in clients. Note that we want to do this after + * initgroups, because at least on Solaris 2.3 it leaves file + * descriptors open. + */ + for (i = 3; i < getdtablesize(); i++) + close(i); + + /* Change current directory to the user\'s home directory. */ + if ( +#ifdef __FreeBSD__ + !*pw->pw_dir || +#endif /* __FreeBSD__ */ + chdir(pw->pw_dir) < 0 + ) { +#ifdef __FreeBSD__ + int quiet_login = 0; +#endif /* __FreeBSD__ */ +#ifdef LOGIN_CAP + if (login_getcapbool(lc, "requirehome", 0)) { + (void)printf("Home directory not available\n"); + log("LOGIN %.200s REFUSED (HOMEDIR) ON TTY %.200s", + pw->pw_name, ttyname); + exit(254); + } +#endif /* LOGIN_CAP */ +#ifdef __FreeBSD__ + if (chdir("/") < 0) { + (void)printf("Cannot find root directory\n"); + log("LOGIN %.200s REFUSED (ROOTDIR) ON TTY %.200s", + pw->pw_name, ttyname); + exit(254); + } +#ifdef LOGIN_CAP + quiet_login = login_getcapbool(lc, "hushlogin", 0); +#endif /* LOGIN_CAP */ + if (!quiet_login || *pw->pw_dir) + (void)printf( + "No home directory.\nLogging in with home = \"/\".\n"); + +#else /* !__FreeBSD__ */ + + fprintf(stderr, "Could not chdir to home directory %s: %s\n", + pw->pw_dir, strerror(errno)); +#endif /* __FreeBSD__ */ + } +#ifdef LOGIN_CAP + login_close(lc); +#endif /* LOGIN_CAP */ + + /* + * Must take new environment into use so that .ssh/rc, /etc/sshrc and + * xauth are run in the proper environment. + */ + environ = env; + + /* + * Run $HOME/.ssh/rc, /etc/sshrc, or xauth (whichever is found first + * in this order). + */ + if (!options.use_login) { + if (stat(SSH_USER_RC, &st) >= 0) { + if (debug_flag) + fprintf(stderr, "Running /bin/sh %s\n", SSH_USER_RC); + + f = popen("/bin/sh " SSH_USER_RC, "w"); + if (f) { + if (auth_proto != NULL && auth_data != NULL) + fprintf(f, "%s %s\n", auth_proto, auth_data); + pclose(f); + } else + fprintf(stderr, "Could not run %s\n", SSH_USER_RC); + } else if (stat(SSH_SYSTEM_RC, &st) >= 0) { + if (debug_flag) + fprintf(stderr, "Running /bin/sh %s\n", SSH_SYSTEM_RC); + + f = popen("/bin/sh " SSH_SYSTEM_RC, "w"); + if (f) { + if (auth_proto != NULL && auth_data != NULL) + fprintf(f, "%s %s\n", auth_proto, auth_data); + pclose(f); + } else + fprintf(stderr, "Could not run %s\n", SSH_SYSTEM_RC); + } +#ifdef XAUTH_PATH + else { + /* Add authority data to .Xauthority if appropriate. */ + if (auth_proto != NULL && auth_data != NULL) { + char *screen = strchr(display, ':'); + if (debug_flag) { + fprintf(stderr, + "Running %.100s add %.100s %.100s %.100s\n", + XAUTH_PATH, display, auth_proto, auth_data); + if (screen != NULL) + fprintf(stderr, + "Adding %.*s/unix%s %s %s\n", + screen-display, display, + screen, auth_proto, auth_data); + } + f = popen(XAUTH_PATH " -q -", "w"); + if (f) { + fprintf(f, "add %s %s %s\n", display, + auth_proto, auth_data); + if (screen != NULL) + fprintf(f, "add %.*s/unix%s %s %s\n", + screen-display, display, + screen, auth_proto, auth_data); + pclose(f); + } else + fprintf(stderr, "Could not run %s -q -\n", + XAUTH_PATH); + } + } +#endif /* XAUTH_PATH */ + + /* Get the last component of the shell name. */ + cp = strrchr(shell, '/'); + if (cp) + cp++; + else + cp = shell; + } + /* + * If we have no command, execute the shell. In this case, the shell + * name to be passed in argv[0] is preceded by '-' to indicate that + * this is a login shell. + */ + if (!command) { + if (!options.use_login) { + char buf[256]; + + /* + * Check for mail if we have a tty and it was enabled + * in server options. + */ + if (ttyname && options.check_mail) { + char *mailbox; + struct stat mailstat; + mailbox = getenv("MAIL"); + if (mailbox != NULL) { + if (stat(mailbox, &mailstat) != 0 || + mailstat.st_size == 0) +#ifdef __FreeBSD__ + ; +#else /* !__FreeBSD__ */ + printf("No mail.\n"); +#endif /* __FreeBSD__ */ + else if (mailstat.st_mtime < mailstat.st_atime) + printf("You have mail.\n"); + else + printf("You have new mail.\n"); + } + } + /* Start the shell. Set initial character to '-'. */ + buf[0] = '-'; + strncpy(buf + 1, cp, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = 0; + + /* Execute the shell. */ + argv[0] = buf; + argv[1] = NULL; + execve(shell, argv, env); + + /* Executing the shell failed. */ + perror(shell); + exit(1); + + } else { + /* Launch login(1). */ + + execl("/usr/bin/login", "login", "-h", get_remote_ipaddr(), + "-p", "-f", "--", pw->pw_name, NULL); + + /* Login couldn't be executed, die. */ + + perror("login"); + exit(1); + } + } + /* + * Execute the command using the user's shell. This uses the -c + * option to execute the command. + */ + argv[0] = (char *) cp; + argv[1] = "-c"; + argv[2] = (char *) command; + argv[3] = NULL; + execve(shell, argv, env); + perror(shell); + exit(1); +} + +Session * +session_new(void) +{ + int i; + static int did_init = 0; + if (!did_init) { + debug("session_new: init"); + for(i = 0; i < MAX_SESSIONS; i++) { + sessions[i].used = 0; + sessions[i].self = i; + } + did_init = 1; + } + for(i = 0; i < MAX_SESSIONS; i++) { + Session *s = &sessions[i]; + if (! s->used) { + s->pid = 0; + s->extended = 0; + s->chanid = -1; + s->ptyfd = -1; + s->ttyfd = -1; + s->term = NULL; + s->pw = NULL; + s->display = NULL; + s->screen = 0; + s->auth_data = NULL; + s->auth_proto = NULL; + s->used = 1; + s->pw = NULL; + debug("session_new: session %d", i); + return s; + } + } + return NULL; +} + +void +session_dump(void) +{ + int i; + for(i = 0; i < MAX_SESSIONS; i++) { + Session *s = &sessions[i]; + debug("dump: used %d session %d %p channel %d pid %d", + s->used, + s->self, + s, + s->chanid, + s->pid); + } +} + +int +session_open(int chanid) +{ + Session *s = session_new(); + debug("session_open: channel %d", chanid); + if (s == NULL) { + error("no more sessions"); + return 0; + } + s->pw = auth_get_user(); + if (s->pw == NULL) + fatal("no user for session %i", s->self); + debug("session_open: session %d: link with channel %d", s->self, chanid); + s->chanid = 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(); + packet_done(); + 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; + char *term_modes; /* encoded terminal modes */ + + if (s->ttyfd != -1) + return 0; + 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(); + term_modes = packet_get_string(&len); + packet_done(); + + 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); + xfree(term_modes); + return 0; + } + 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); + + session_proctitle(s); + + /* XXX parse and set terminal modes */ + xfree(term_modes); + return 1; +} + +int +session_subsystem_req(Session *s) +{ + unsigned int len; + int success = 0; + char *subsys = packet_get_string(&len); + + packet_done(); + log("subsystem request for %s", subsys); + + xfree(subsys); + return success; +} + +int +session_x11_req(Session *s) +{ + if (!options.x11_forwarding) { + debug("X11 forwarding disabled in server configuration file."); + return 0; + } + if (xauthfile != NULL) { + debug("X11 fwd already started."); + return 0; + } + + debug("Received request for X11 forwarding with auth spoofing."); + if (s->display != NULL) + packet_disconnect("Protocol error: X11 display already set."); + + s->single_connection = packet_get_char(); + s->auth_proto = packet_get_string(NULL); + s->auth_data = packet_get_string(NULL); + s->screen = packet_get_int(); + packet_done(); + + s->display = x11_create_display_inet(s->screen, options.x11_display_offset); + if (s->display == NULL) { + xfree(s->auth_proto); + xfree(s->auth_data); + return 0; + } + xauthfile = xmalloc(MAXPATHLEN); + strlcpy(xauthfile, "/tmp/ssh-XXXXXXXX", MAXPATHLEN); + temporarily_use_uid(s->pw->pw_uid); + if (mkdtemp(xauthfile) == NULL) { + restore_uid(); + error("private X11 dir: mkdtemp %s failed: %s", + xauthfile, strerror(errno)); + xfree(xauthfile); + xauthfile = NULL; + xfree(s->auth_proto); + xfree(s->auth_data); + /* XXXX remove listening channels */ + return 0; + } + strlcat(xauthfile, "/cookies", MAXPATHLEN); + open(xauthfile, O_RDWR|O_CREAT|O_EXCL, 0600); + restore_uid(); + fatal_add_cleanup(xauthfile_cleanup_proc, s); + 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) { + packet_done(); + s->extended = 1; + 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); + packet_done(); + s->extended = 1; + 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) { + success = session_pty_req(s); + } else if (strcmp(rtype, "x11-req") == 0) { + success = session_x11_req(s); + } else if (strcmp(rtype, "subsystem") == 0) { + success = session_subsystem_req(s); + } + } + 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) +{ + if (s == NULL || s->ttyfd == -1) + return; + + debug("session_pty_cleanup: session %i release %s", s->self, s->tty); + + /* Cancel the cleanup function. */ + fatal_remove_cleanup(pty_cleanup_proc, (void *)s); + + /* Record that the user has logged out. */ + record_logout(s->pid, s->tty); + + /* Release the pseudo-tty. */ + pty_release(s->tty); + + /* + * Close the server side of the socket pairs. We must do this after + * the pty cleanup, so that another process doesn't get this pty + * while we're still cleaning up. + */ + 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); + /* + * emulate a write failure with 'chan_write_failed', nobody will be + * interested in data we write. + * Note that we must not call 'chan_read_failed', since there could + * be some more data waiting in the pipe. + */ + if (c->ostate != CHAN_OUTPUT_CLOSED) + 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); + session_proctitle(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)); + } +} + +char * +session_tty_list(void) +{ + static char buf[1024]; + int i; + buf[0] = '\0'; + for(i = 0; i < MAX_SESSIONS; i++) { + Session *s = &sessions[i]; + if (s->used && s->ttyfd != -1) { + if (buf[0] != '\0') + strlcat(buf, ",", sizeof buf); + strlcat(buf, strrchr(s->tty, '/') + 1, sizeof buf); + } + } + if (buf[0] == '\0') + strlcpy(buf, "notty", sizeof buf); + return buf; +} + +void +session_proctitle(Session *s) +{ + if (s->pw == NULL) + error("no user for session %d", s->self); + else + setproctitle("%s@%s", s->pw->pw_name, session_tty_list()); +} + +void +do_authenticated2(void) +{ + /* + * Cancel the alarm we set to limit the time taken for + * authentication. + */ + alarm(0); + server_loop2(); + if (xauthfile) + xauthfile_cleanup_proc(NULL); +} diff --git a/crypto/openssh/ssh2.h b/crypto/openssh/ssh2.h new file mode 100644 index 000000000000..1fa4c0a0ddae --- /dev/null +++ b/crypto/openssh/ssh2.h @@ -0,0 +1,112 @@ +/* + * draft-ietf-secsh-architecture-05.txt + * + * Transport layer protocol: + * + * 1-19 Transport layer generic (e.g. disconnect, ignore, debug, + * etc) + * 20-29 Algorithm negotiation + * 30-49 Key exchange method specific (numbers can be reused for + * different authentication methods) + * + * User authentication protocol: + * + * 50-59 User authentication generic + * 60-79 User authentication method specific (numbers can be reused + * for different authentication methods) + * + * Connection protocol: + * + * 80-89 Connection protocol generic + * 90-127 Channel related messages + * + * Reserved for client protocols: + * + * 128-191 Reserved + * + * Local extensions: + * + * 192-255 Local extensions + */ +/* RCSID("$OpenBSD: ssh2.h,v 1.3 2000/05/15 07:03:12 markus Exp $"); */ + +/* transport layer: generic */ + +#define SSH2_MSG_DISCONNECT 1 +#define SSH2_MSG_IGNORE 2 +#define SSH2_MSG_UNIMPLEMENTED 3 +#define SSH2_MSG_DEBUG 4 +#define SSH2_MSG_SERVICE_REQUEST 5 +#define SSH2_MSG_SERVICE_ACCEPT 6 + +/* transport layer: alg negotiation */ + +#define SSH2_MSG_KEXINIT 20 +#define SSH2_MSG_NEWKEYS 21 + +/* transport layer: kex specific messages, can be reused */ + +#define SSH2_MSG_KEXDH_INIT 30 +#define SSH2_MSG_KEXDH_REPLY 31 + +/* user authentication: generic */ + +#define SSH2_MSG_USERAUTH_REQUEST 50 +#define SSH2_MSG_USERAUTH_FAILURE 51 +#define SSH2_MSG_USERAUTH_SUCCESS 52 +#define SSH2_MSG_USERAUTH_BANNER 53 + +/* user authentication: method specific, can be reused */ + +#define SSH2_MSG_USERAUTH_PK_OK 60 +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 + +/* connection protocol: generic */ + +#define SSH2_MSG_GLOBAL_REQUEST 80 +#define SSH2_MSG_REQUEST_SUCCESS 81 +#define SSH2_MSG_REQUEST_FAILURE 82 + +/* channel related messages */ + +#define SSH2_MSG_CHANNEL_OPEN 90 +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH2_MSG_CHANNEL_DATA 94 +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH2_MSG_CHANNEL_EOF 96 +#define SSH2_MSG_CHANNEL_CLOSE 97 +#define SSH2_MSG_CHANNEL_REQUEST 98 +#define SSH2_MSG_CHANNEL_SUCCESS 99 +#define SSH2_MSG_CHANNEL_FAILURE 100 + +/* disconnect reason code */ + +#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 +#define SSH2_DISCONNECT_RESERVED 4 +#define SSH2_DISCONNECT_MAC_ERROR 5 +#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH2_DISCONNECT_CONNECTION_LOST 10 +#define SSH2_DISCONNECT_BY_APPLICATION 11 +#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 + +/* misc */ + +#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 +#define SSH2_OPEN_CONNECT_FAILED 2 +#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 +#define SSH2_OPEN_RESOURCE_SHORTAGE 4 + +#define SSH2_EXTENDED_DATA_STDERR 1 diff --git a/crypto/openssh/sshconnect2.c b/crypto/openssh/sshconnect2.c new file mode 100644 index 000000000000..0abcf89a0a07 --- /dev/null +++ b/crypto/openssh/sshconnect2.c @@ -0,0 +1,451 @@ +/* + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Markus Friedl. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "includes.h" +RCSID("$OpenBSD: sshconnect2.c,v 1.11 2000/05/25 20:45:20 markus Exp $"); + +#include <openssl/bn.h> +#include <openssl/rsa.h> +#include <openssl/dsa.h> +#include <openssl/md5.h> +#include <openssl/dh.h> +#include <openssl/hmac.h> + +#include "ssh.h" +#include "xmalloc.h" +#include "rsa.h" +#include "buffer.h" +#include "packet.h" +#include "cipher.h" +#include "uidswap.h" +#include "compat.h" +#include "readconf.h" +#include "bufaux.h" +#include "ssh2.h" +#include "kex.h" +#include "myproposal.h" +#include "key.h" +#include "dsa.h" +#include "sshconnect.h" +#include "authfile.h" + +/* import */ +extern char *client_version_string; +extern char *server_version_string; +extern Options options; + +/* + * SSH2 key exchange + */ + +unsigned char *session_id2 = NULL; +int session_id2_len = 0; + +void +ssh_kex_dh(Kex *kex, char *host, struct sockaddr *hostaddr, + Buffer *client_kexinit, Buffer *server_kexinit) +{ + int i; + int plen, dlen; + unsigned int klen, kout; + char *signature = NULL; + unsigned int slen; + char *server_host_key_blob = NULL; + Key *server_host_key; + unsigned int sbloblen; + DH *dh; + BIGNUM *dh_server_pub = 0; + BIGNUM *shared_secret = 0; + unsigned char *kbuf; + unsigned char *hash; + + debug("Sending SSH2_MSG_KEXDH_INIT."); + /* generate and send 'e', client DH public key */ + dh = dh_new_group1(); + packet_start(SSH2_MSG_KEXDH_INIT); + packet_put_bignum2(dh->pub_key); + packet_send(); + packet_write_wait(); + +#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"); + DHparams_print_fp(stderr, dh); +#endif + + debug("Wait SSH2_MSG_KEXDH_REPLY."); + + packet_read_expect(&plen, SSH2_MSG_KEXDH_REPLY); + + debug("Got SSH2_MSG_KEXDH_REPLY."); + + /* key, cert */ + server_host_key_blob = packet_get_string(&sbloblen); + server_host_key = dsa_key_from_blob(server_host_key_blob, sbloblen); + if (server_host_key == NULL) + fatal("cannot decode server_host_key_blob"); + + check_host_key(host, hostaddr, server_host_key, + options.user_hostfile2, options.system_hostfile2); + + /* DH paramter f, server public DH key */ + dh_server_pub = BN_new(); + if (dh_server_pub == NULL) + fatal("dh_server_pub == NULL"); + packet_get_bignum2(dh_server_pub, &dlen); + +#ifdef DEBUG_KEXDH + fprintf(stderr, "\ndh_server_pub= "); + bignum_print(dh_server_pub); + fprintf(stderr, "\n"); + debug("bits %d", BN_num_bits(dh_server_pub)); +#endif + + /* signed H */ + signature = packet_get_string(&slen); + packet_done(); + + if (!dh_pub_is_valid(dh, dh_server_pub)) + packet_disconnect("bad server public DH value"); + + klen = DH_size(dh); + kbuf = xmalloc(klen); + kout = DH_compute_key(kbuf, dh_server_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); + + /* calc and verify H */ + 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), + server_host_key_blob, sbloblen, + dh->pub_key, + dh_server_pub, + shared_secret + ); + xfree(server_host_key_blob); + DH_free(dh); +#ifdef DEBUG_KEXDH + fprintf(stderr, "hash == "); + for (i = 0; i< 20; i++) + fprintf(stderr, "%02x", (hash[i])&0xff); + fprintf(stderr, "\n"); +#endif + if (dsa_verify(server_host_key, (unsigned char *)signature, slen, hash, 20) != 1) + fatal("dsa_verify failed for server_host_key"); + key_free(server_host_key); + + kex_derive_keys(kex, hash, shared_secret); + packet_set_kex(kex); + + /* save session id */ + session_id2_len = 20; + session_id2 = xmalloc(session_id2_len); + memcpy(session_id2, hash, session_id2_len); +} + +void +ssh_kex2(char *host, struct sockaddr *hostaddr) +{ + int i, plen; + Kex *kex; + Buffer *client_kexinit, *server_kexinit; + char *sprop[PROPOSAL_MAX]; + + if (options.ciphers != NULL) { + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; + } else if (options.cipher == SSH_CIPHER_3DES) { + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + myproposal[PROPOSAL_ENC_ALGS_STOC] = + (char *) cipher_name(SSH_CIPHER_3DES_CBC); + } else if (options.cipher == SSH_CIPHER_BLOWFISH) { + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + myproposal[PROPOSAL_ENC_ALGS_STOC] = + (char *) cipher_name(SSH_CIPHER_BLOWFISH_CBC); + } + if (options.compression) { + myproposal[PROPOSAL_COMP_ALGS_CTOS] = "zlib"; + myproposal[PROPOSAL_COMP_ALGS_STOC] = "zlib"; + } else { + myproposal[PROPOSAL_COMP_ALGS_CTOS] = "none"; + myproposal[PROPOSAL_COMP_ALGS_STOC] = "none"; + } + + /* buffers with raw kexinit messages */ + server_kexinit = xmalloc(sizeof(*server_kexinit)); + buffer_init(server_kexinit); + client_kexinit = kex_init(myproposal); + + /* algorithm negotiation */ + kex_exchange_kexinit(client_kexinit, server_kexinit, sprop); + kex = kex_choose_conf(myproposal, sprop, 0); + for (i = 0; i < PROPOSAL_MAX; i++) + xfree(sprop[i]); + + /* server authentication and session key agreement */ + ssh_kex_dh(kex, host, hostaddr, client_kexinit, server_kexinit); + + buffer_free(client_kexinit); + buffer_free(server_kexinit); + xfree(client_kexinit); + xfree(server_kexinit); + + debug("Wait SSH2_MSG_NEWKEYS."); + packet_read_expect(&plen, SSH2_MSG_NEWKEYS); + packet_done(); + debug("GOT SSH2_MSG_NEWKEYS."); + + debug("send SSH2_MSG_NEWKEYS."); + packet_start(SSH2_MSG_NEWKEYS); + packet_send(); + packet_write_wait(); + debug("done: send SSH2_MSG_NEWKEYS."); + +#ifdef DEBUG_KEXDH + /* send 1st encrypted/maced/compressed message */ + packet_start(SSH2_MSG_IGNORE); + packet_put_cstring("markus"); + packet_send(); + packet_write_wait(); +#endif + debug("done: KEX2."); +} + +/* + * Authenticate user + */ +int +ssh2_try_passwd(const char *server_user, const char *host, const char *service) +{ + static int attempt = 0; + char prompt[80]; + char *password; + + if (attempt++ > options.number_of_password_prompts) + return 0; + + snprintf(prompt, sizeof(prompt), "%.30s@%.40s's password: ", + server_user, host); + password = read_passphrase(prompt, 0); + packet_start(SSH2_MSG_USERAUTH_REQUEST); + packet_put_cstring(server_user); + packet_put_cstring(service); + packet_put_cstring("password"); + packet_put_char(0); + packet_put_cstring(password); + memset(password, 0, strlen(password)); + xfree(password); + packet_send(); + packet_write_wait(); + return 1; +} + +int +ssh2_try_pubkey(char *filename, + const char *server_user, const char *host, const char *service) +{ + Buffer b; + Key *k; + unsigned char *blob, *signature; + int bloblen, slen; + struct stat st; + + if (stat(filename, &st) != 0) { + debug("key does not exist: %s", filename); + return 0; + } + debug("try pubkey: %s", filename); + + k = key_new(KEY_DSA); + if (!load_private_key(filename, "", k, NULL)) { + int success = 0; + char *passphrase; + char prompt[300]; + snprintf(prompt, sizeof prompt, + "Enter passphrase for DSA key '%.100s': ", + filename); + passphrase = read_passphrase(prompt, 0); + success = load_private_key(filename, passphrase, k, NULL); + memset(passphrase, 0, strlen(passphrase)); + xfree(passphrase); + if (!success) + return 0; + } + dsa_make_key_blob(k, &blob, &bloblen); + + /* data to be signed */ + buffer_init(&b); + buffer_append(&b, session_id2, session_id2_len); + buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST); + buffer_put_cstring(&b, server_user); + buffer_put_cstring(&b, + datafellows & SSH_BUG_PUBKEYAUTH ? + "ssh-userauth" : + service); + buffer_put_cstring(&b, "publickey"); + buffer_put_char(&b, 1); + buffer_put_cstring(&b, KEX_DSS); + buffer_put_string(&b, blob, bloblen); + + /* generate signature */ + dsa_sign(k, &signature, &slen, buffer_ptr(&b), buffer_len(&b)); + key_free(k); +#ifdef DEBUG_DSS + buffer_dump(&b); +#endif + if (datafellows & SSH_BUG_PUBKEYAUTH) { + /* e.g. ssh-2.0.13: data-to-be-signed != data-on-the-wire */ + buffer_clear(&b); + buffer_append(&b, session_id2, session_id2_len); + buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST); + buffer_put_cstring(&b, server_user); + buffer_put_cstring(&b, service); + buffer_put_cstring(&b, "publickey"); + buffer_put_char(&b, 1); + buffer_put_cstring(&b, KEX_DSS); + buffer_put_string(&b, blob, bloblen); + } + xfree(blob); + /* append signature */ + buffer_put_string(&b, signature, slen); + xfree(signature); + + /* skip session id and packet type */ + if (buffer_len(&b) < session_id2_len + 1) + fatal("ssh2_try_pubkey: internal error"); + buffer_consume(&b, session_id2_len + 1); + + /* put remaining data from buffer into packet */ + packet_start(SSH2_MSG_USERAUTH_REQUEST); + packet_put_raw(buffer_ptr(&b), buffer_len(&b)); + buffer_free(&b); + + /* send */ + packet_send(); + packet_write_wait(); + return 1; +} + +void +ssh_userauth2(const char *server_user, char *host) +{ + int type; + int plen; + int sent; + unsigned int dlen; + int partial; + int i = 0; + char *auths; + char *service = "ssh-connection"; /* service name */ + + debug("send SSH2_MSG_SERVICE_REQUEST"); + packet_start(SSH2_MSG_SERVICE_REQUEST); + packet_put_cstring("ssh-userauth"); + packet_send(); + packet_write_wait(); + + type = packet_read(&plen); + if (type != SSH2_MSG_SERVICE_ACCEPT) { + fatal("denied SSH2_MSG_SERVICE_ACCEPT: %d", type); + } + if (packet_remaining() > 0) { + char *reply = packet_get_string(&plen); + debug("service_accept: %s", reply); + xfree(reply); + } else { + /* payload empty for ssh-2.0.13 ?? */ + debug("buggy server: service_accept w/o service"); + } + packet_done(); + debug("got SSH2_MSG_SERVICE_ACCEPT"); + + /* INITIAL request for auth */ + packet_start(SSH2_MSG_USERAUTH_REQUEST); + packet_put_cstring(server_user); + packet_put_cstring(service); + packet_put_cstring("none"); + packet_send(); + packet_write_wait(); + + for (;;) { + sent = 0; + type = packet_read(&plen); + if (type == SSH2_MSG_USERAUTH_SUCCESS) + break; + if (type != SSH2_MSG_USERAUTH_FAILURE) + fatal("access denied: %d", type); + /* SSH2_MSG_USERAUTH_FAILURE means: try again */ + auths = packet_get_string(&dlen); + debug("authentications that can continue: %s", auths); + partial = packet_get_char(); + packet_done(); + if (partial) + debug("partial success"); + if (options.dsa_authentication && + strstr(auths, "publickey") != NULL) { + while (i < options.num_identity_files2) { + sent = ssh2_try_pubkey( + options.identity_files2[i++], + server_user, host, service); + if (sent) + break; + } + } + if (!sent) { + if (options.password_authentication && + !options.batch_mode && + strstr(auths, "password") != NULL) { + sent = ssh2_try_passwd(server_user, host, service); + } + } + if (!sent) + fatal("Permission denied (%s).", auths); + xfree(auths); + } + packet_done(); + debug("ssh-userauth2 successfull"); +} |
