diff options
Diffstat (limited to 'src')
139 files changed, 33109 insertions, 0 deletions
diff --git a/src/agentfwd.h b/src/agentfwd.h new file mode 100644 index 0000000..d913aea --- /dev/null +++ b/src/agentfwd.h @@ -0,0 +1,66 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ +#ifndef DROPBEAR_AGENTFWD_H_ +#define DROPBEAR_AGENTFWD_H_ + +#include "includes.h" +#include "chansession.h" +#include "channel.h" +#include "auth.h" +#include "list.h" + +#if DROPBEAR_CLI_AGENTFWD + +/* From OpenSSH authfd.h */ +#define SSH_AGENT_RSA_SHA2_256 0x02 + +/* An agent reply can be reasonably large, as it can + * contain a list of all public keys held by the agent. + * 10000 is arbitrary */ +#define MAX_AGENT_REPLY 10000 + +/* client functions */ +void cli_load_agent_keys(m_list * ret_list); +void agent_buf_sign(buffer *sigblob, sign_key *key, + const buffer *data_buf, enum signature_type type); +void cli_setup_agent(const struct Channel *channel); + +#ifdef __hpux +#define seteuid(a) setresuid(-1, (a), -1) +#define setegid(a) setresgid(-1, (a), -1) +#endif + +extern const struct ChanType cli_chan_agent; + +#endif /* DROPBEAR_CLI_AGENTFWD */ + +#if DROPBEAR_SVR_AGENTFWD + +int svr_agentreq(struct ChanSess * chansess); +void svr_agentcleanup(struct ChanSess * chansess); +void svr_agentset(const struct ChanSess *chansess); + +#endif /* DROPBEAR_SVR_AGENTFWD */ + +#endif /* DROPBEAR_AGENTFWD_H_ */ diff --git a/src/algo.h b/src/algo.h new file mode 100644 index 0000000..c46b409 --- /dev/null +++ b/src/algo.h @@ -0,0 +1,147 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_ALGO_H_ + +#define DROPBEAR_ALGO_H_ + +#include "includes.h" +#include "buffer.h" + +#define DROPBEAR_MODE_UNUSED 0 +#define DROPBEAR_MODE_CBC 1 +#define DROPBEAR_MODE_CTR 2 + +struct Algo_Type { + + const char *name; /* identifying name */ + char val; /* a value for this cipher, or -1 for invalid */ + const void *data; /* algorithm specific data */ + char usable; /* whether we can use this algorithm */ + const void *mode; /* the mode, currently only used for ciphers, + points to a 'struct dropbear_cipher_mode' */ +}; + +typedef struct Algo_Type algo_type; + +/* lists mapping ssh types of algorithms to internal values */ +extern algo_type sshkex[]; +extern algo_type sigalgs[]; +extern algo_type sshciphers[]; +extern algo_type sshhashes[]; +extern algo_type ssh_compress[]; +extern algo_type ssh_delaycompress[]; +extern algo_type ssh_nocompress[]; + +extern const struct dropbear_cipher dropbear_nocipher; +extern const struct dropbear_cipher_mode dropbear_mode_none; +extern const struct dropbear_hash dropbear_nohash; + +struct dropbear_cipher { + const struct ltc_cipher_descriptor *cipherdesc; + const unsigned long keysize; + const unsigned char blocksize; +}; + +struct dropbear_cipher_mode { + int (*start)(int cipher, const unsigned char *IV, + const unsigned char *key, + int keylen, int num_rounds, void *cipher_state); + int (*encrypt)(const unsigned char *pt, unsigned char *ct, + unsigned long len, void *cipher_state); + int (*decrypt)(const unsigned char *ct, unsigned char *pt, + unsigned long len, void *cipher_state); + int (*aead_crypt)(unsigned int seq, + const unsigned char *in, unsigned char *out, + unsigned long len, unsigned long taglen, + void *cipher_state, int direction); + int (*aead_getlength)(unsigned int seq, + const unsigned char *in, unsigned int *outlen, + unsigned long len, void *cipher_state); + const struct dropbear_hash *aead_mac; +}; + +struct dropbear_hash { + const struct ltc_hash_descriptor *hash_desc; + const unsigned long keysize; + /* hashsize may be truncated from the size returned by hash_desc, + eg sha1-96 */ + const unsigned char hashsize; +}; + +enum dropbear_kex_mode { +#if DROPBEAR_NORMAL_DH + DROPBEAR_KEX_NORMAL_DH, +#endif +#if DROPBEAR_ECDH + DROPBEAR_KEX_ECDH, +#endif +#if DROPBEAR_CURVE25519 + DROPBEAR_KEX_CURVE25519, +#endif +}; + +struct dropbear_kex { + enum dropbear_kex_mode mode; + + /* "normal" DH KEX */ + const unsigned char *dh_p_bytes; + const int dh_p_len; + + /* elliptic curve DH KEX */ +#if DROPBEAR_ECDH + const struct dropbear_ecc_curve *ecc_curve; +#else + const void* dummy; +#endif + + /* both */ + const struct ltc_hash_descriptor *hash_desc; +}; + +/* Includes all algorithms is useall is set */ +void buf_put_algolist_all(buffer * buf, const algo_type localalgos[], int useall); +/* Includes "usable" algorithms */ +void buf_put_algolist(buffer * buf, const algo_type localalgos[]); + +#define KEXGUESS2_ALGO_NAME "kexguess2@matt.ucc.asn.au" + +int buf_has_algo(buffer *buf, const char *algo); +algo_type * first_usable_algo(algo_type algos[]); +algo_type * buf_match_algo(buffer* buf, algo_type localalgos[], + int kexguess2, int *goodguess); + +#if DROPBEAR_USER_ALGO_LIST +int check_user_algos(const char* user_algo_list, algo_type * algos, + const char *algo_desc); +char * algolist_string(const algo_type algos[]); +#endif + +enum { + DROPBEAR_COMP_NONE, + DROPBEAR_COMP_ZLIB, + DROPBEAR_COMP_ZLIB_DELAY, +}; + +#endif /* DROPBEAR_ALGO_H_ */ diff --git a/src/atomicio.c b/src/atomicio.c new file mode 100644 index 0000000..2aacf51 --- /dev/null +++ b/src/atomicio.c @@ -0,0 +1,59 @@ +/* $OpenBSD: atomicio.c,v 1.17 2006/04/01 05:51:34 djm Exp $ */ +/* + * Copied from OpenSSH/OpenBSD. + * + * Copyright (c) 2005 Anil Madhavapeddy. All rights reserved. + * Copyright (c) 1995,1999 Theo de Raadt. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "includes.h" + +#include "atomicio.h" + +/* + * ensure all of data on socket comes through. f==read || f==vwrite + */ +size_t +atomicio(ssize_t (*f) (int, void *, size_t), int fd, void *_s, size_t n) +{ + char *s = _s; + size_t pos = 0; + ssize_t res; + + while (n > pos) { + res = (f) (fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + return 0; + case 0: + errno = EPIPE; + return pos; + default: + pos += (size_t)res; + } + } + return (pos); +} diff --git a/src/atomicio.h b/src/atomicio.h new file mode 100644 index 0000000..0bd019f --- /dev/null +++ b/src/atomicio.h @@ -0,0 +1,35 @@ +/* $OpenBSD: atomicio.h,v 1.7 2006/03/25 22:22:42 djm Exp $ */ + +/* + * Copied from OpenSSH/OpenBSD, required for loginrec.c + * + * Copyright (c) 1995,1999 Theo de Raadt. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Ensure all of data on socket comes through. f==read || f==vwrite + */ +size_t atomicio(ssize_t (*)(int, void *, size_t), int, void *, size_t); + +#define vwrite (ssize_t (*)(int, void *, size_t))write diff --git a/src/auth.h b/src/auth.h new file mode 100644 index 0000000..0e854fb --- /dev/null +++ b/src/auth.h @@ -0,0 +1,163 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_AUTH_H_ +#define DROPBEAR_AUTH_H_ + +#include "includes.h" +#include "signkey.h" +#include "chansession.h" +#include "list.h" + +void svr_authinitialise(void); + +/* Server functions */ +void recv_msg_userauth_request(void); +void send_msg_userauth_failure(int partial, int incrfail); +void send_msg_userauth_success(void); +void send_msg_userauth_banner(const buffer *msg); +void svr_auth_password(int valid_user); +void svr_auth_pubkey(int valid_user); +void svr_auth_pam(int valid_user); + +#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT +int svr_pubkey_allows_agentfwd(void); +int svr_pubkey_allows_tcpfwd(void); +int svr_pubkey_allows_x11fwd(void); +int svr_pubkey_allows_pty(void); +int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port); +void svr_pubkey_set_forced_command(struct ChanSess *chansess); +void svr_pubkey_options_cleanup(void); +int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filename); +#else +/* no option : success */ +#define svr_pubkey_allows_agentfwd() 1 +#define svr_pubkey_allows_tcpfwd() 1 +#define svr_pubkey_allows_x11fwd() 1 +#define svr_pubkey_allows_pty() 1 +static inline int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) + { (void)host; (void)port; return 1; } + +static inline void svr_pubkey_set_forced_command(struct ChanSess *chansess) { } +static inline void svr_pubkey_options_cleanup(void) { } +#define svr_add_pubkey_options(x,y,z) DROPBEAR_SUCCESS +#endif + +/* Client functions */ +void recv_msg_userauth_failure(void); +void recv_msg_userauth_success(void); +void recv_msg_userauth_specific_60(void); +void recv_msg_userauth_pk_ok(void); +void recv_msg_userauth_info_request(void); +void cli_get_user(void); +void cli_auth_getmethods(void); +int cli_auth_try(void); +void recv_msg_userauth_banner(void); +void cli_pubkeyfail(void); +void cli_auth_password(void); +int cli_auth_pubkey(void); +void cli_auth_interactive(void); +char* getpass_or_cancel(const char* prompt); +void cli_auth_pubkey_cleanup(void); + + +#define MAX_USERNAME_LEN 100 /* arbitrary for the moment */ + +#define AUTH_TYPE_NONE 1 +#define AUTH_TYPE_PUBKEY (1 << 1) +#define AUTH_TYPE_PASSWORD (1 << 2) +#define AUTH_TYPE_INTERACT (1 << 3) + +#define AUTH_METHOD_NONE "none" +#define AUTH_METHOD_NONE_LEN 4 +#define AUTH_METHOD_PUBKEY "publickey" +#define AUTH_METHOD_PUBKEY_LEN 9 +#define AUTH_METHOD_PASSWORD "password" +#define AUTH_METHOD_PASSWORD_LEN 8 +#define AUTH_METHOD_INTERACT "keyboard-interactive" +#define AUTH_METHOD_INTERACT_LEN 20 + +#define PUBKEY_OPTIONS_ANY_PORT UINT_MAX + + +/* This structure is shared between server and client - it contains + * relatively little extraneous bits when used for the client rather than the + * server */ +struct AuthState { + char *username; /* This is the username the client presents to check. It + is updated each run through, used for auth checking */ + unsigned char authtypes; /* Flags indicating which auth types are still + valid */ + unsigned int failcount; /* Number of (failed) authentication attempts.*/ + unsigned int authdone; /* 0 if we haven't authed, 1 if we have. Applies for + client and server (though has differing + meanings). */ + + unsigned int perm_warn; /* Server only, set if bad permissions on + ~/.ssh/authorized_keys have already been + logged. */ + unsigned int checkusername_failed; /* Server only, set if checkusername + has already failed */ + struct timespec auth_starttime; /* Server only, time of receiving current + SSH_MSG_USERAUTH_REQUEST */ + + /* These are only used for the server */ + uid_t pw_uid; + gid_t pw_gid; + char *pw_dir; + char *pw_shell; + char *pw_name; + char *pw_passwd; +#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT + struct PubKeyOptions* pubkey_options; + char *pubkey_info; +#endif +}; + +#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT +struct PubKeyOptions; +struct PubKeyOptions { + /* Flags */ + int no_port_forwarding_flag; + int no_agent_forwarding_flag; + int no_x11_forwarding_flag; + int no_pty_flag; + /* "command=" option. */ + char * forced_command; + /* "permitopen=" option */ + m_list *permit_open_destinations; + +#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 + int no_touch_required_flag; + int verify_required_flag; +#endif +}; + +struct PermitTCPFwdEntry { + char *host; + unsigned int port; +}; +#endif + +#endif /* DROPBEAR_AUTH_H_ */ diff --git a/src/bignum.c b/src/bignum.c new file mode 100644 index 0000000..c2b39b1 --- /dev/null +++ b/src/bignum.c @@ -0,0 +1,104 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* Contains helper functions for mp_int handling */ + +#include "includes.h" +#include "dbutil.h" + +/* wrapper for mp_init, failing fatally on errors (memory allocation) */ +void m_mp_init(mp_int *mp) { + + if (mp_init(mp) != MP_OKAY) { + dropbear_exit("Mem alloc error"); + } +} + +/* simplified duplication of bn_mp_multi's mp_init_multi, but die fatally + * on error */ +void m_mp_init_multi(mp_int *mp, ...) +{ + mp_int* cur_arg = mp; + va_list args; + + va_start(args, mp); /* init args to next argument from caller */ + while (cur_arg != NULL) { + if (mp_init(cur_arg) != MP_OKAY) { + dropbear_exit("Mem alloc error"); + } + cur_arg = va_arg(args, mp_int*); + } + va_end(args); +} + +void m_mp_alloc_init_multi(mp_int **mp, ...) +{ + mp_int** cur_arg = mp; + va_list args; + + va_start(args, mp); /* init args to next argument from caller */ + while (cur_arg != NULL) { + *cur_arg = m_malloc(sizeof(mp_int)); + if (mp_init(*cur_arg) != MP_OKAY) { + dropbear_exit("Mem alloc error"); + } + cur_arg = va_arg(args, mp_int**); + } + va_end(args); +} + +void m_mp_free_multi(mp_int **mp, ...) +{ + mp_int** cur_arg = mp; + va_list args; + + va_start(args, mp); /* init args to next argument from caller */ + while (cur_arg != NULL) { + if (*cur_arg) { + mp_clear(*cur_arg); + } + m_free(*cur_arg); + cur_arg = va_arg(args, mp_int**); + } + va_end(args); +} + +void bytes_to_mp(mp_int *mp, const unsigned char* bytes, unsigned int len) { + + if (mp_from_ubin(mp, (unsigned char*)bytes, len) != MP_OKAY) { + dropbear_exit("Mem alloc error"); + } +} + +/* hash the ssh representation of the mp_int mp */ +void hash_process_mp(const struct ltc_hash_descriptor *hash_desc, + hash_state *hs, const mp_int *mp) { + buffer * buf; + + buf = buf_new(512 + 20); /* max buffer is a 4096 bit key, + plus header + some leeway*/ + buf_putmpint(buf, mp); + hash_desc->process(hs, buf->data, buf->len); + buf_burn_free(buf); +} diff --git a/src/bignum.h b/src/bignum.h new file mode 100644 index 0000000..861acb0 --- /dev/null +++ b/src/bignum.h @@ -0,0 +1,38 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_BIGNUM_H_ +#define DROPBEAR_BIGNUM_H_ + +#include "dbhelpers.h" + +void m_mp_init(mp_int *mp); +void m_mp_init_multi(mp_int *mp, ...) ATTRIB_SENTINEL; +void m_mp_alloc_init_multi(mp_int **mp, ...) ATTRIB_SENTINEL; +void m_mp_free_multi(mp_int **mp, ...) ATTRIB_SENTINEL; +void bytes_to_mp(mp_int *mp, const unsigned char* bytes, unsigned int len); +void hash_process_mp(const struct ltc_hash_descriptor *hash_desc, + hash_state *hs, const mp_int *mp); + +#endif /* DROPBEAR_BIGNUM_H_ */ diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..1377b77 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,372 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* Buffer handling routines, designed to avoid overflows/using invalid data */ + +#include "includes.h" +#include "dbutil.h" +#include "buffer.h" + +/* Prevent integer overflows when incrementing buffer position/length. + * Calling functions should check arguments first, but this provides a + * backstop */ +#define BUF_MAX_INCR 1000000000 +#define BUF_MAX_SIZE 1000000000 + +/* avoid excessively large numbers, > ~8192 bits */ +#define BUF_MAX_MPINT (8240 / 8) + +/* Create (malloc) a new buffer of size */ +buffer* buf_new(unsigned int size) { + buffer* buf; + if (size > BUF_MAX_SIZE) { + dropbear_exit("buf->size too big"); + } + + buf = (buffer*)m_malloc(sizeof(buffer)+size); + buf->data = (unsigned char*)buf + sizeof(buffer); + buf->size = size; + return buf; +} + +/* free the buffer's data and the buffer itself */ +void buf_free(buffer* buf) { + m_free(buf); +} + +/* overwrite the contents of the buffer then free it */ +void buf_burn_free(buffer* buf) { + m_burn(buf->data, buf->size); + m_free(buf); +} + + +/* resize a buffer, pos and len will be repositioned if required when + * downsizing */ +buffer* buf_resize(buffer *buf, unsigned int newsize) { + if (newsize > BUF_MAX_SIZE) { + dropbear_exit("buf->size too big"); + } + + buf = m_realloc(buf, sizeof(buffer)+newsize); + buf->data = (unsigned char*)buf + sizeof(buffer); + buf->size = newsize; + buf->len = MIN(newsize, buf->len); + buf->pos = MIN(newsize, buf->pos); + return buf; +} + +/* Create a copy of buf, allocating required memory etc. */ +/* The new buffer is sized the same as the length of the source buffer. */ +buffer* buf_newcopy(const buffer* buf) { + + buffer* ret; + + ret = buf_new(buf->len); + ret->len = buf->len; + if (buf->len > 0) { + memcpy(ret->data, buf->data, buf->len); + } + return ret; +} + +/* Set the length of the buffer */ +void buf_setlen(buffer* buf, unsigned int len) { + if (len > buf->size) { + dropbear_exit("Bad buf_setlen"); + } + buf->len = len; + buf->pos = MIN(buf->pos, buf->len); +} + +/* Increment the length of the buffer */ +void buf_incrlen(buffer* buf, unsigned int incr) { + if (incr > BUF_MAX_INCR || buf->len + incr > buf->size) { + dropbear_exit("Bad buf_incrlen"); + } + buf->len += incr; +} +/* Set the position of the buffer */ +void buf_setpos(buffer* buf, unsigned int pos) { + + if (pos > buf->len) { + dropbear_exit("Bad buf_setpos"); + } + buf->pos = pos; +} + +/* increment the position by incr, increasing the buffer length if required */ +void buf_incrwritepos(buffer* buf, unsigned int incr) { + if (incr > BUF_MAX_INCR || buf->pos + incr > buf->size) { + dropbear_exit("Bad buf_incrwritepos"); + } + buf->pos += incr; + if (buf->pos > buf->len) { + buf->len = buf->pos; + } +} + +/* increment the position by incr */ +void buf_incrpos(buffer* buf, unsigned int incr) { + if (incr > BUF_MAX_INCR + || (buf->pos + incr) > buf->len) { + dropbear_exit("Bad buf_incrpos"); + } + buf->pos += incr; +} + +/* decrement the position by decr */ +void buf_decrpos(buffer* buf, unsigned int decr) { + if (decr > buf->pos) { + dropbear_exit("Bad buf_decrpos"); + } + buf->pos -= decr; +} + +/* Get a byte from the buffer and increment the pos */ +unsigned char buf_getbyte(buffer* buf) { + + /* This check is really just ==, but the >= allows us to check for the + * bad case of pos > len, which should _never_ happen. */ + if (buf->pos >= buf->len) { + dropbear_exit("Bad buf_getbyte"); + } + return buf->data[buf->pos++]; +} + +/* Get a bool from the buffer and increment the pos */ +unsigned char buf_getbool(buffer* buf) { + + unsigned char b; + b = buf_getbyte(buf); + if (b != 0) + b = 1; + return b; +} + +/* put a byte, incrementing the length if required */ +void buf_putbyte(buffer* buf, unsigned char val) { + + if (buf->pos >= buf->len) { + buf_incrlen(buf, 1); + } + buf->data[buf->pos] = val; + buf->pos++; +} + +/* returns an in-place pointer to the buffer, checking that + * the next len bytes from that position can be used */ +unsigned char* buf_getptr(const buffer* buf, unsigned int len) { + + if (len > BUF_MAX_INCR || buf->pos + len > buf->len) { + dropbear_exit("Bad buf_getptr"); + } + return &buf->data[buf->pos]; +} + +/* like buf_getptr, but checks against total size, not used length. + * This allows writing past the used length, but not past the size */ +unsigned char* buf_getwriteptr(const buffer* buf, unsigned int len) { + + if (len > BUF_MAX_INCR || buf->pos + len > buf->size) { + dropbear_exit("Bad buf_getwriteptr"); + } + return &buf->data[buf->pos]; +} + +/* Return a null-terminated string, it is malloced, so must be free()ed + * Note that the string isn't checked for null bytes, hence the retlen + * may be longer than what is returned by strlen */ +char* buf_getstring(buffer* buf, unsigned int *retlen) { + + unsigned int len; + char* ret; + void* src = NULL; + len = buf_getint(buf); + if (len > MAX_STRING_LEN) { + dropbear_exit("String too long"); + } + + if (retlen != NULL) { + *retlen = len; + } + src = buf_getptr(buf, len); + ret = m_malloc(len+1); + memcpy(ret, src, len); + buf_incrpos(buf, len); + ret[len] = '\0'; + + return ret; +} + +/* Return a string as a newly allocated buffer */ +static buffer * buf_getstringbuf_int(buffer *buf, int incllen) { + buffer *ret = NULL; + unsigned int len = buf_getint(buf); + int extra = 0; + if (len > MAX_STRING_LEN) { + dropbear_exit("String too long"); + } + if (incllen) { + extra = 4; + } + ret = buf_new(len+extra); + if (incllen) { + buf_putint(ret, len); + } + memcpy(buf_getwriteptr(ret, len), buf_getptr(buf, len), len); + buf_incrpos(buf, len); + buf_incrlen(ret, len); + buf_setpos(ret, 0); + return ret; +} + +/* Return a string as a newly allocated buffer */ +buffer * buf_getstringbuf(buffer *buf) { + return buf_getstringbuf_int(buf, 0); +} + +/* Returns a string in a new buffer, including the length */ +buffer * buf_getbuf(buffer *buf) { + return buf_getstringbuf_int(buf, 1); +} + +/* Just increment the buffer position the same as if we'd used buf_getstring, + * but don't bother copying/malloc()ing for it */ +void buf_eatstring(buffer *buf) { + + buf_incrpos( buf, buf_getint(buf) ); +} + +/* Get an uint32 from the buffer and increment the pos */ +unsigned int buf_getint(buffer* buf) { + unsigned int ret; + + LOAD32H(ret, buf_getptr(buf, 4)); + buf_incrpos(buf, 4); + return ret; +} + +/* put a 32bit uint into the buffer, incr bufferlen & pos if required */ +void buf_putint(buffer* buf, int unsigned val) { + + STORE32H(val, buf_getwriteptr(buf, 4)); + buf_incrwritepos(buf, 4); + +} + +/* put a SSH style string into the buffer, increasing buffer len if required */ +void buf_putstring(buffer* buf, const char* str, unsigned int len) { + + buf_putint(buf, len); + buf_putbytes(buf, (const unsigned char*)str, len); + +} + +/* puts an entire buffer as a SSH string. ignore pos of buf_str. */ +void buf_putbufstring(buffer *buf, const buffer* buf_str) { + buf_putstring(buf, (const char*)buf_str->data, buf_str->len); +} + +/* put the set of len bytes into the buffer, incrementing the pos, increasing + * len if required */ +void buf_putbytes(buffer *buf, const unsigned char *bytes, unsigned int len) { + memcpy(buf_getwriteptr(buf, len), bytes, len); + buf_incrwritepos(buf, len); +} + + +/* for our purposes we only need positive (or 0) numbers, so will + * fail if we get negative numbers */ +void buf_putmpint(buffer* buf, const mp_int * mp) { + size_t written; + unsigned int len, pad = 0; + TRACE2(("enter buf_putmpint")) + + dropbear_assert(mp != NULL); + + if (mp_isneg(mp)) { + dropbear_exit("negative bignum"); + } + + /* zero check */ + if (mp_iszero(mp)) { + len = 0; + } else { + /* SSH spec requires padding for mpints with the MSB set, this code + * implements it */ + len = mp_count_bits(mp); + /* if the top bit of MSB is set, we need to pad */ + pad = (len%8 == 0) ? 1 : 0; + len = len / 8 + 1; /* don't worry about rounding, we need it for + padding anyway when len%8 == 0 */ + + } + + /* store the length */ + buf_putint(buf, len); + + /* store the actual value */ + if (len > 0) { + if (pad) { + buf_putbyte(buf, 0x00); + } + if (mp_to_ubin(mp, buf_getwriteptr(buf, len-pad), len-pad, &written) != MP_OKAY) { + dropbear_exit("mpint error"); + } + buf_incrwritepos(buf, written); + } + + TRACE2(("leave buf_putmpint")) +} + +/* Retrieve an mp_int from the buffer. + * Will fail for -ve since they shouldn't be required here. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int buf_getmpint(buffer* buf, mp_int* mp) { + + unsigned int len; + len = buf_getint(buf); + + if (len == 0) { + mp_zero(mp); + return DROPBEAR_SUCCESS; + } + + if (len > BUF_MAX_MPINT) { + return DROPBEAR_FAILURE; + } + + /* check for negative */ + if (*buf_getptr(buf, 1) & (1 << (CHAR_BIT-1))) { + return DROPBEAR_FAILURE; + } + + if (mp_from_ubin(mp, buf_getptr(buf, len), len) != MP_OKAY) { + return DROPBEAR_FAILURE; + } + + buf_incrpos(buf, len); + return DROPBEAR_SUCCESS; +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 0000000..0ba6683 --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,72 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_BUFFER_H_ + +#define DROPBEAR_BUFFER_H_ + +#include "includes.h" + +struct buf { + /* don't manipulate data member outside of buffer.c - it + is a pointer into the malloc holding buffer itself */ + unsigned char * data; + unsigned int len; /* the used size */ + unsigned int pos; + unsigned int size; /* the memory size */ + +}; + +typedef struct buf buffer; + +buffer * buf_new(unsigned int size); +/* Possibly returns a new buffer*, like realloc() */ +buffer * buf_resize(buffer *buf, unsigned int newsize); +void buf_free(buffer* buf); +void buf_burn_free(buffer* buf); +buffer* buf_newcopy(const buffer* buf); +void buf_setlen(buffer* buf, unsigned int len); +void buf_incrlen(buffer* buf, unsigned int incr); +void buf_setpos(buffer* buf, unsigned int pos); +void buf_incrpos(buffer* buf, unsigned int incr); +void buf_decrpos(buffer* buf, unsigned int decr); +void buf_incrwritepos(buffer* buf, unsigned int incr); +unsigned char buf_getbyte(buffer* buf); +unsigned char buf_getbool(buffer* buf); +void buf_putbyte(buffer* buf, unsigned char val); +unsigned char* buf_getptr(const buffer* buf, unsigned int len); +unsigned char* buf_getwriteptr(const buffer* buf, unsigned int len); +char* buf_getstring(buffer* buf, unsigned int *retlen); +buffer * buf_getstringbuf(buffer *buf); +buffer * buf_getbuf(buffer *buf); +void buf_eatstring(buffer *buf); +void buf_putint(buffer* buf, unsigned int val); +void buf_putstring(buffer* buf, const char* str, unsigned int len); +void buf_putbufstring(buffer *buf, const buffer* buf_str); +void buf_putbytes(buffer *buf, const unsigned char *bytes, unsigned int len); +void buf_putmpint(buffer* buf, const mp_int * mp); +int buf_getmpint(buffer* buf, mp_int* mp); +unsigned int buf_getint(buffer* buf); + +#endif /* DROPBEAR_BUFFER_H_ */ diff --git a/src/chachapoly.c b/src/chachapoly.c new file mode 100644 index 0000000..c065fac --- /dev/null +++ b/src/chachapoly.c @@ -0,0 +1,148 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2020 by Vladislav Grishenko + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "algo.h" +#include "dbutil.h" +#include "chachapoly.h" + +#if DROPBEAR_CHACHA20POLY1305 + +#define CHACHA20_KEY_LEN 32 +#define CHACHA20_BLOCKSIZE 8 +#define POLY1305_KEY_LEN 32 +#define POLY1305_TAG_LEN 16 + +static const struct ltc_cipher_descriptor dummy = {.name = NULL}; + +static const struct dropbear_hash dropbear_chachapoly_mac = + {NULL, POLY1305_KEY_LEN, POLY1305_TAG_LEN}; + +const struct dropbear_cipher dropbear_chachapoly = + {&dummy, CHACHA20_KEY_LEN*2, CHACHA20_BLOCKSIZE}; + +static int dropbear_chachapoly_start(int UNUSED(cipher), const unsigned char* UNUSED(IV), + const unsigned char *key, int keylen, + int UNUSED(num_rounds), dropbear_chachapoly_state *state) { + int err; + + TRACE2(("enter dropbear_chachapoly_start")) + + if (keylen != CHACHA20_KEY_LEN*2) { + return CRYPT_ERROR; + } + + if ((err = chacha_setup(&state->chacha, key, + CHACHA20_KEY_LEN, 20)) != CRYPT_OK) { + return err; + } + + if ((err = chacha_setup(&state->header, key + CHACHA20_KEY_LEN, + CHACHA20_KEY_LEN, 20) != CRYPT_OK)) { + return err; + } + + TRACE2(("leave dropbear_chachapoly_start")) + return CRYPT_OK; +} + +static int dropbear_chachapoly_crypt(unsigned int seq, + const unsigned char *in, unsigned char *out, + unsigned long len, unsigned long taglen, + dropbear_chachapoly_state *state, int direction) { + poly1305_state poly; + unsigned char seqbuf[8], key[POLY1305_KEY_LEN], tag[POLY1305_TAG_LEN]; + int err; + + TRACE2(("enter dropbear_chachapoly_crypt")) + + if (len < 4 || taglen != POLY1305_TAG_LEN) { + return CRYPT_ERROR; + } + + STORE64H((uint64_t)seq, seqbuf); + chacha_ivctr64(&state->chacha, seqbuf, sizeof(seqbuf), 0); + if ((err = chacha_keystream(&state->chacha, key, sizeof(key))) != CRYPT_OK) { + return err; + } + + poly1305_init(&poly, key, sizeof(key)); + if (direction == LTC_DECRYPT) { + poly1305_process(&poly, in, len); + poly1305_done(&poly, tag, &taglen); + if (constant_time_memcmp(in + len, tag, taglen) != 0) { + return CRYPT_ERROR; + } + } + + chacha_ivctr64(&state->header, seqbuf, sizeof(seqbuf), 0); + if ((err = chacha_crypt(&state->header, in, 4, out)) != CRYPT_OK) { + return err; + } + + chacha_ivctr64(&state->chacha, seqbuf, sizeof(seqbuf), 1); + if ((err = chacha_crypt(&state->chacha, in + 4, len - 4, out + 4)) != CRYPT_OK) { + return err; + } + + if (direction == LTC_ENCRYPT) { + poly1305_process(&poly, out, len); + poly1305_done(&poly, out + len, &taglen); + } + + TRACE2(("leave dropbear_chachapoly_crypt")) + return CRYPT_OK; +} + +static int dropbear_chachapoly_getlength(unsigned int seq, + const unsigned char *in, unsigned int *outlen, + unsigned long len, dropbear_chachapoly_state *state) { + unsigned char seqbuf[8], buf[4]; + int err; + + TRACE2(("enter dropbear_chachapoly_getlength")) + + if (len < sizeof(buf)) { + return CRYPT_ERROR; + } + + STORE64H((uint64_t)seq, seqbuf); + chacha_ivctr64(&state->header, seqbuf, sizeof(seqbuf), 0); + if ((err = chacha_crypt(&state->header, in, sizeof(buf), buf)) != CRYPT_OK) { + return err; + } + + LOAD32H(*outlen, buf); + + TRACE2(("leave dropbear_chachapoly_getlength")) + return CRYPT_OK; +} + +const struct dropbear_cipher_mode dropbear_mode_chachapoly = + {(void *)dropbear_chachapoly_start, NULL, NULL, + (void *)dropbear_chachapoly_crypt, + (void *)dropbear_chachapoly_getlength, &dropbear_chachapoly_mac}; + +#endif /* DROPBEAR_CHACHA20POLY1305 */ diff --git a/src/chachapoly.h b/src/chachapoly.h new file mode 100644 index 0000000..5a7c5b2 --- /dev/null +++ b/src/chachapoly.h @@ -0,0 +1,44 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2020 by Vladislav Grishenko + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_DROPBEAR_CHACHAPOLY_H_ +#define DROPBEAR_DROPBEAR_CHACHAPOLY_H_ + +#include "includes.h" +#include "algo.h" + +#if DROPBEAR_CHACHA20POLY1305 + +typedef struct { + chacha_state chacha; + chacha_state header; +} dropbear_chachapoly_state; + +extern const struct dropbear_cipher dropbear_chachapoly; +extern const struct dropbear_cipher_mode dropbear_mode_chachapoly; + +#endif /* DROPBEAR_CHACHA20POLY1305 */ + +#endif /* DROPBEAR_DROPBEAR_CHACHAPOLY_H_ */ diff --git a/src/channel.h b/src/channel.h new file mode 100644 index 0000000..dd174aa --- /dev/null +++ b/src/channel.h @@ -0,0 +1,144 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_CHANNEL_H_ +#define DROPBEAR_CHANNEL_H_ + +#include "includes.h" +#include "buffer.h" +#include "circbuffer.h" +#include "netio.h" + +#define SSH_OPEN_ADMINISTRATIVELY_PROHIBITED 1 +#define SSH_OPEN_CONNECT_FAILED 2 +#define SSH_OPEN_UNKNOWN_CHANNEL_TYPE 3 +#define SSH_OPEN_RESOURCE_SHORTAGE 4 + +/* Not a real type */ +#define SSH_OPEN_IN_PROGRESS 99 + +#define CHAN_EXTEND_SIZE 3 /* how many extra slots to add when we need more */ + +struct ChanType; + +struct Channel { + + unsigned int index; /* the local channel index */ + unsigned int remotechan; + unsigned int recvwindow, transwindow; + unsigned int recvdonelen; + unsigned int recvmaxpacket, transmaxpacket; + void* typedata; /* a pointer to type specific data */ + int writefd; /* read from wire, written to insecure side */ + int readfd; /* read from insecure side, written to wire */ + int errfd; /* used like writefd or readfd, depending if it's client or server. + Doesn't exactly belong here, but is cleaner here */ + int bidir_fd; /* a boolean indicating that writefd/readfd are the same + file descriptor (bidirectional), such as a network socket or PTY. + That is handled differently when closing FDs */ + circbuffer *writebuf; /* data from the wire, for local consumption. Can be + initially NULL */ + circbuffer *extrabuf; /* extended-data for the program - used like writebuf + but for stderr */ + + /* whether close/eof messages have been exchanged */ + int sent_close, recv_close; + int recv_eof, sent_eof; + /* once flushing is set, readfd will close once no more data is available + (not waiting for EOF) */ + int flushing; + + struct dropbear_progress_connection *conn_pending; + int initconn; /* used for TCP forwarding, whether the channel has been + fully initialised */ + + int await_open; /* flag indicating whether we've sent an open request + for this channel (and are awaiting a confirmation + or failure). */ + + /* Used by client chansession to handle ~ escaping, NULL ignored otherwise */ + void (*read_mangler)(const struct Channel*, const unsigned char* bytes, int *len); + + const struct ChanType* type; + + enum dropbear_prio prio; +}; + +struct ChanType { + + const char *name; + /* Sets up the channel */ + int (*inithandler)(struct Channel*); + /* Called to check whether a channel should close, separately from the FD being EOF. + Used for noticing process exiting */ + int (*check_close)(struct Channel*); + /* Handler for ssh_msg_channel_request */ + void (*reqhandler)(struct Channel*); + /* Called prior to sending ssh_msg_channel_close, used for sending exit status */ + void (*closehandler)(const struct Channel*); + /* Frees resources, called just prior to channel being removed */ + void (*cleanup)(const struct Channel*); +}; + +/* Callback for connect_remote. errstring may be NULL if result == DROPBEAR_SUCCESS */ +void channel_connect_done(int result, int sock, void* user_data, const char* errstring); + +void chaninitialise(const struct ChanType *chantypes[]); +void chancleanup(void); +void setchannelfds(fd_set *readfds, fd_set *writefds, int allow_reads); +void channelio(const fd_set *readfd, const fd_set *writefd); +struct Channel* getchannel(void); +/* Returns an arbitrary channel that is in a ready state - not +being initialised and no EOF in either direction. NULL if none. */ +struct Channel* get_any_ready_channel(void); + +void recv_msg_channel_open(void); +void recv_msg_channel_request(void); +void send_msg_channel_failure(const struct Channel *channel); +void send_msg_channel_success(const struct Channel *channel); +void recv_msg_channel_data(void); +void recv_msg_channel_extended_data(void); +void recv_msg_channel_window_adjust(void); +void recv_msg_channel_close(void); +void recv_msg_channel_eof(void); + +void common_recv_msg_channel_data(struct Channel *channel, int fd, + circbuffer * buf); + +#if DROPBEAR_CLIENT +extern const struct ChanType clichansess; +#endif + +#if DROPBEAR_LISTENERS || DROPBEAR_CLIENT +int send_msg_channel_open_init(int fd, const struct ChanType *type); +void recv_msg_channel_open_confirmation(void); +void recv_msg_channel_open_failure(void); +#endif +void start_send_channel_request(const struct Channel *channel, const char *type); + +void send_msg_request_success(void); +void send_msg_request_failure(void); + + +#endif /* DROPBEAR_CHANNEL_H_ */ diff --git a/src/chansession.h b/src/chansession.h new file mode 100644 index 0000000..cf4fba3 --- /dev/null +++ b/src/chansession.h @@ -0,0 +1,106 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_CHANSESSION_H_ +#define DROPBEAR_CHANSESSION_H_ + +#include "loginrec.h" +#include "channel.h" +#include "listener.h" + +struct exitinfo { + + int exitpid; /* -1 if not exited */ + int exitstatus; + int exitsignal; + int exitcore; +}; + +struct ChanSess { + + char * cmd; /* command to exec */ + pid_t pid; /* child process pid */ + /* command that was sent by the client, if authorized_keys command= or + dropbear -c was used */ + char *original_command; + + /* pty details */ + int master; /* the master terminal fd*/ + int slave; + char * tty; + char * term; + + /* exit details */ + struct exitinfo exit; + + + /* These are only set temporarily before forking */ + /* Used to set $SSH_CONNECTION in the child session. */ + char *connection_string; + /* Used to set $SSH_CLIENT in the child session. */ + char *client_string; + +#if DROPBEAR_X11FWD + struct Listener * x11listener; + int x11port; + char * x11authprot; + char * x11authcookie; + unsigned int x11screennum; + unsigned char x11singleconn; +#endif + +#if DROPBEAR_SVR_AGENTFWD + struct Listener * agentlistener; + char * agentfile; + char * agentdir; +#endif +}; + +struct ChildPid { + pid_t pid; + struct ChanSess * chansess; +}; + + +void addnewvar(const char* param, const char* var); + +void cli_send_chansess_request(void); +void cli_tty_cleanup(void); +void cli_chansess_winchange(void); +#if DROPBEAR_CLI_NETCAT +void cli_send_netcat_request(void); +#endif + +void svr_chansessinitialise(void); +void svr_chansess_checksignal(void); +extern const struct ChanType svrchansess; + +struct SigMap { + int signal; + char* name; +}; + +extern const struct SigMap signames[]; + +#endif /* DROPBEAR_CHANSESSION_H_ */ diff --git a/src/circbuffer.c b/src/circbuffer.c new file mode 100644 index 0000000..aabd9dc --- /dev/null +++ b/src/circbuffer.c @@ -0,0 +1,133 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002-2004 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "circbuffer.h" + +#define MAX_CBUF_SIZE 100000000 + +circbuffer * cbuf_new(unsigned int size) { + + circbuffer *cbuf = NULL; + + if (size > MAX_CBUF_SIZE) { + dropbear_exit("Bad cbuf size"); + } + + cbuf = (circbuffer*)m_malloc(sizeof(circbuffer)); + /* data is malloced on first write */ + cbuf->data = NULL; + cbuf->used = 0; + cbuf->readpos = 0; + cbuf->writepos = 0; + cbuf->size = size; + + return cbuf; +} + +void cbuf_free(circbuffer * cbuf) { + + if (cbuf->data) { + m_burn(cbuf->data, cbuf->size); + m_free(cbuf->data); + } + m_free(cbuf); +} + +unsigned int cbuf_getused(const circbuffer * cbuf) { + + return cbuf->used; + +} + +unsigned int cbuf_getavail(const circbuffer * cbuf) { + + return cbuf->size - cbuf->used; + +} + +unsigned int cbuf_writelen(const circbuffer *cbuf) { + + dropbear_assert(cbuf->used <= cbuf->size); + dropbear_assert(((2*cbuf->size)+cbuf->writepos-cbuf->readpos)%cbuf->size == cbuf->used%cbuf->size); + dropbear_assert(((2*cbuf->size)+cbuf->readpos-cbuf->writepos)%cbuf->size == (cbuf->size-cbuf->used)%cbuf->size); + + if (cbuf->used == cbuf->size) { + TRACE(("cbuf_writelen: full buffer")) + return 0; /* full */ + } + + if (cbuf->writepos < cbuf->readpos) { + return cbuf->readpos - cbuf->writepos; + } + + return cbuf->size - cbuf->writepos; +} + +void cbuf_readptrs(const circbuffer *cbuf, + unsigned char **p1, unsigned int *len1, + unsigned char **p2, unsigned int *len2) { + *p1 = &cbuf->data[cbuf->readpos]; + *len1 = MIN(cbuf->used, cbuf->size - cbuf->readpos); + + if (*len1 < cbuf->used) { + *p2 = cbuf->data; + *len2 = cbuf->used - *len1; + } else { + *p2 = NULL; + *len2 = 0; + } +} + +unsigned char* cbuf_writeptr(circbuffer *cbuf, unsigned int len) { + + if (len > cbuf_writelen(cbuf)) { + dropbear_exit("Bad cbuf write"); + } + + if (!cbuf->data) { + /* lazy allocation */ + cbuf->data = (unsigned char*)m_malloc(cbuf->size); + } + + return &cbuf->data[cbuf->writepos]; +} + +void cbuf_incrwrite(circbuffer *cbuf, unsigned int len) { + if (len > cbuf_writelen(cbuf)) { + dropbear_exit("Bad cbuf write"); + } + + cbuf->used += len; + dropbear_assert(cbuf->used <= cbuf->size); + cbuf->writepos = (cbuf->writepos + len) % cbuf->size; +} + + +void cbuf_incrread(circbuffer *cbuf, unsigned int len) { + dropbear_assert(cbuf->used >= len); + cbuf->used -= len; + cbuf->readpos = (cbuf->readpos + len) % cbuf->size; +} diff --git a/src/circbuffer.h b/src/circbuffer.h new file mode 100644 index 0000000..5aaa762 --- /dev/null +++ b/src/circbuffer.h @@ -0,0 +1,52 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002-2004 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_CIRCBUFFER_H_ +#define DROPBEAR_CIRCBUFFER_H_ +struct circbuf { + + unsigned int size; + unsigned int readpos; + unsigned int writepos; + unsigned int used; + unsigned char* data; +}; + +typedef struct circbuf circbuffer; + +circbuffer * cbuf_new(unsigned int size); +void cbuf_free(circbuffer * cbuf); + +unsigned int cbuf_getused(const circbuffer * cbuf); /* how much data stored */ +unsigned int cbuf_getavail(const circbuffer * cbuf); /* how much we can write */ +unsigned int cbuf_writelen(const circbuffer *cbuf); /* max linear write len */ + +/* returns pointers to the two portions of the circular buffer that can be read */ +void cbuf_readptrs(const circbuffer *cbuf, + unsigned char **p1, unsigned int *len1, + unsigned char **p2, unsigned int *len2); +unsigned char* cbuf_writeptr(circbuffer *cbuf, unsigned int len); +void cbuf_incrwrite(circbuffer *cbuf, unsigned int len); +void cbuf_incrread(circbuffer *cbuf, unsigned int len); +#endif diff --git a/src/cli-agentfwd.c b/src/cli-agentfwd.c new file mode 100644 index 0000000..6fb5c4b --- /dev/null +++ b/src/cli-agentfwd.c @@ -0,0 +1,316 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2005 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" + +#if DROPBEAR_CLI_AGENTFWD + +#include "agentfwd.h" +#include "session.h" +#include "ssh.h" +#include "dbutil.h" +#include "chansession.h" +#include "channel.h" +#include "packet.h" +#include "buffer.h" +#include "dbrandom.h" +#include "listener.h" +#include "runopts.h" +#include "atomicio.h" +#include "signkey.h" +#include "auth.h" + +/* The protocol implemented to talk to OpenSSH's SSH2 agent is documented in + PROTOCOL.agent in recent OpenSSH source distributions (5.1p1 has it). */ + +static int new_agent_chan(struct Channel * channel); + +const struct ChanType cli_chan_agent = { + "auth-agent@openssh.com", + new_agent_chan, + NULL, + NULL, + NULL, + NULL +}; + +static int connect_agent() { + + int fd = -1; + char* agent_sock = NULL; + + agent_sock = getenv("SSH_AUTH_SOCK"); + if (agent_sock == NULL) + return -1; + + fd = connect_unix(agent_sock); + + if (fd < 0) { + dropbear_log(LOG_INFO, "Failed to connect to agent"); + } + + return fd; +} + +/* handle a request for a connection to the locally running ssh-agent + or forward. */ +static int new_agent_chan(struct Channel * channel) { + + int fd = -1; + + if (!cli_opts.agent_fwd) + return SSH_OPEN_ADMINISTRATIVELY_PROHIBITED; + + fd = connect_agent(); + if (fd < 0) { + return SSH_OPEN_CONNECT_FAILED; + } + + setnonblocking(fd); + + ses.maxfd = MAX(ses.maxfd, fd); + + channel->readfd = fd; + channel->writefd = fd; + channel->bidir_fd = 1; + + return 0; +} + +/* Sends a request to the agent, returning a newly allocated buffer + * with the response */ +/* This function will block waiting for a response - it will + * only be used by client authentication (not for forwarded requests) + * won't cause problems for interactivity. */ +/* Packet format (from draft-ylonen) + 4 bytes Length, msb first. Does not include length itself. + 1 byte Packet type. The value 255 is reserved for future extensions. + data Any data, depending on packet type. Encoding as in the ssh packet + protocol. +*/ +static buffer * agent_request(unsigned char type, const buffer *data) { + + buffer * payload = NULL; + buffer * inbuf = NULL; + size_t readlen = 0; + ssize_t ret; + const int fd = cli_opts.agent_fd; + unsigned int data_len = 0; + if (data) + { + data_len = data->len; + } + + payload = buf_new(4 + 1 + data_len); + + buf_putint(payload, 1 + data_len); + buf_putbyte(payload, type); + if (data) { + buf_putbytes(payload, data->data, data->len); + } + buf_setpos(payload, 0); + + ret = atomicio(vwrite, fd, buf_getptr(payload, payload->len), payload->len); + if ((size_t)ret != payload->len) { + TRACE(("write failed fd %d for agent_request, %s", fd, strerror(errno))) + goto out; + } + + buf_free(payload); + payload = NULL; + TRACE(("Wrote out bytes for agent_request")) + /* Now we read the response */ + inbuf = buf_new(4); + ret = atomicio(read, fd, buf_getwriteptr(inbuf, 4), 4); + if (ret != 4) { + TRACE(("read of length failed for agent_request")) + goto out; + } + buf_setpos(inbuf, 0); + buf_setlen(inbuf, ret); + + readlen = buf_getint(inbuf); + if (readlen > MAX_AGENT_REPLY) { + TRACE(("agent reply is too big")); + goto out; + } + + inbuf = buf_resize(inbuf, readlen); + buf_setpos(inbuf, 0); + ret = atomicio(read, fd, buf_getwriteptr(inbuf, readlen), readlen); + if ((size_t)ret != readlen) { + TRACE(("read of data failed for agent_request")) + goto out; + } + buf_incrwritepos(inbuf, readlen); + buf_setpos(inbuf, 0); + +out: + if (payload) + buf_free(payload); + + return inbuf; +} + +static void agent_get_key_list(m_list * ret_list) +{ + buffer * inbuf = NULL; + unsigned int num = 0; + unsigned char packet_type; + unsigned int i; + int ret; + + inbuf = agent_request(SSH2_AGENTC_REQUEST_IDENTITIES, NULL); + if (!inbuf) { + TRACE(("agent_request failed returning identities")) + goto out; + } + + /* The reply has a format of: + byte SSH2_AGENT_IDENTITIES_ANSWER + uint32 num_keys + Followed by zero or more consecutive keys, encoded as: + string key_blob + string key_comment + */ + packet_type = buf_getbyte(inbuf); + if (packet_type != SSH2_AGENT_IDENTITIES_ANSWER) { + goto out; + } + + num = buf_getint(inbuf); + for (i = 0; i < num; i++) { + sign_key * pubkey = NULL; + enum signkey_type key_type = DROPBEAR_SIGNKEY_ANY; + buffer * key_buf; + + /* each public key is encoded as a string */ + key_buf = buf_getstringbuf(inbuf); + pubkey = new_sign_key(); + ret = buf_get_pub_key(key_buf, pubkey, &key_type); + buf_free(key_buf); + if (ret != DROPBEAR_SUCCESS) { + TRACE(("Skipping bad/unknown type pubkey from agent")); + sign_key_free(pubkey); + } else { + pubkey->type = key_type; + pubkey->source = SIGNKEY_SOURCE_AGENT; + + list_append(ret_list, pubkey); + } + + /* We'll ignore the comment for now. might want it later.*/ + buf_eatstring(inbuf); + } + +out: + if (inbuf) { + buf_free(inbuf); + inbuf = NULL; + } +} + +void cli_setup_agent(const struct Channel *channel) { + if (!getenv("SSH_AUTH_SOCK")) { + return; + } + + start_send_channel_request(channel, "auth-agent-req@openssh.com"); + /* Don't want replies */ + buf_putbyte(ses.writepayload, 0); + encrypt_packet(); +} + +/* Returned keys are prepended to ret_list, which will + be updated. */ +void cli_load_agent_keys(m_list *ret_list) { + /* agent_fd will be closed after successful auth */ + cli_opts.agent_fd = connect_agent(); + if (cli_opts.agent_fd < 0) { + return; + } + + agent_get_key_list(ret_list); +} + +void agent_buf_sign(buffer *sigblob, sign_key *key, + const buffer *data_buf, enum signature_type sigtype) { + buffer *request_data = NULL; + buffer *response = NULL; + unsigned int siglen; + int packet_type; + int flags = 0; + + /* Request format + byte SSH2_AGENTC_SIGN_REQUEST + string key_blob + string data + uint32 flags + */ + request_data = buf_new(MAX_PUBKEY_SIZE + data_buf->len + 12); + buf_put_pub_key(request_data, key, key->type); + + buf_putbufstring(request_data, data_buf); +#if DROPBEAR_RSA_SHA256 + if (sigtype == DROPBEAR_SIGNATURE_RSA_SHA256) { + flags |= SSH_AGENT_RSA_SHA2_256; + } +#endif + buf_putint(request_data, flags); + + response = agent_request(SSH2_AGENTC_SIGN_REQUEST, request_data); + + if (!response) { + goto fail; + } + + packet_type = buf_getbyte(response); + if (packet_type != SSH2_AGENT_SIGN_RESPONSE) { + goto fail; + } + + /* Response format + byte SSH2_AGENT_SIGN_RESPONSE + string signature_blob + */ + siglen = buf_getint(response); + buf_putbytes(sigblob, buf_getptr(response, siglen), siglen); + goto cleanup; + +fail: + /* XXX don't fail badly here. instead propagate a failure code back up to + the cli auth pubkey code, and just remove this key from the list of + ones to try. */ + dropbear_exit("Agent failed signing key"); + +cleanup: + if (request_data) { + buf_free(request_data); + } + if (response) { + buf_free(response); + } +} + +#endif diff --git a/src/cli-auth.c b/src/cli-auth.c new file mode 100644 index 0000000..20d6371 --- /dev/null +++ b/src/cli-auth.c @@ -0,0 +1,359 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2004 by Mihnea Stoenescu + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "session.h" +#include "auth.h" +#include "dbutil.h" +#include "buffer.h" +#include "ssh.h" +#include "packet.h" +#include "runopts.h" + +/* Send a "none" auth request to get available methods */ +void cli_auth_getmethods() { + TRACE(("enter cli_auth_getmethods")) + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST); + buf_putstring(ses.writepayload, cli_opts.username, + strlen(cli_opts.username)); + buf_putstring(ses.writepayload, SSH_SERVICE_CONNECTION, + SSH_SERVICE_CONNECTION_LEN); + buf_putstring(ses.writepayload, "none", 4); /* 'none' method */ + + encrypt_packet(); + +#if DROPBEAR_CLI_IMMEDIATE_AUTH + /* We can't haven't two auth requests in-flight with delayed zlib mode + since if the first one succeeds then the remote side will + expect the second one to be compressed. + Race described at + http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/zlib-openssh.html + */ + if (ses.keys->trans.algo_comp != DROPBEAR_COMP_ZLIB_DELAY) { + ses.authstate.authtypes = AUTH_TYPE_PUBKEY; +#if DROPBEAR_USE_PASSWORD_ENV + if (getenv(DROPBEAR_PASSWORD_ENV)) { + ses.authstate.authtypes |= AUTH_TYPE_PASSWORD | AUTH_TYPE_INTERACT; + } +#endif + if (cli_auth_try() == DROPBEAR_SUCCESS) { + TRACE(("skipped initial none auth query")) + /* Note that there will be two auth responses in-flight */ + cli_ses.ignore_next_auth_response = 1; + } + } +#endif + TRACE(("leave cli_auth_getmethods")) +} + +void recv_msg_userauth_banner() { + + char* banner = NULL; + unsigned int bannerlen; + unsigned int i, linecount; + int truncated = 0; + + TRACE(("enter recv_msg_userauth_banner")) + if (ses.authstate.authdone) { + TRACE(("leave recv_msg_userauth_banner: banner after auth done")) + return; + } + + if (cli_opts.quiet) { + TRACE(("not showing banner")) + return; + } + + banner = buf_getstring(ses.payload, &bannerlen); + buf_eatstring(ses.payload); /* The language string */ + + if (bannerlen > MAX_BANNER_SIZE) { + TRACE(("recv_msg_userauth_banner: bannerlen too long: %d", bannerlen)) + truncated = 1; + } else { + cleantext(banner); + + /* Limit to 24 lines */ + linecount = 1; + for (i = 0; i < bannerlen; i++) { + if (banner[i] == '\n') { + if (linecount >= MAX_BANNER_LINES) { + banner[i] = '\0'; + truncated = 1; + break; + } + linecount++; + } + } + fprintf(stderr, "%s\n", banner); + } + + if (truncated) { + fprintf(stderr, "[Banner from the server is too long]\n"); + } + + m_free(banner); + TRACE(("leave recv_msg_userauth_banner")) +} + +/* This handles the message-specific types which + * all have a value of 60. These are + * SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, + * SSH_MSG_USERAUTH_PK_OK, & + * SSH_MSG_USERAUTH_INFO_REQUEST. */ +void recv_msg_userauth_specific_60() { + +#if DROPBEAR_CLI_PUBKEY_AUTH + if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) { + recv_msg_userauth_pk_ok(); + return; + } +#endif + +#if DROPBEAR_CLI_INTERACT_AUTH + if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT) { + recv_msg_userauth_info_request(); + return; + } +#endif + +#if DROPBEAR_CLI_PASSWORD_AUTH + if (cli_ses.lastauthtype == AUTH_TYPE_PASSWORD) { + /* Eventually there could be proper password-changing + * support. However currently few servers seem to + * implement it, and password auth is last-resort + * regardless - keyboard-interactive is more likely + * to be used anyway. */ + dropbear_close("Your password has expired."); + } +#endif + + dropbear_exit("Unexpected userauth packet"); +} + +void recv_msg_userauth_failure() { + + char * methods = NULL; + char * tok = NULL; + unsigned int methlen = 0; + unsigned int partial = 0; + unsigned int i = 0; + + TRACE(("<- MSG_USERAUTH_FAILURE")) + TRACE(("enter recv_msg_userauth_failure")) + + if (ses.authstate.authdone) { + TRACE(("leave recv_msg_userauth_failure, already authdone.")) + return; + } + + if (cli_ses.state != USERAUTH_REQ_SENT) { + /* Perhaps we should be more fatal? */ + dropbear_exit("Unexpected userauth failure"); + } + + /* When DROPBEAR_CLI_IMMEDIATE_AUTH is set there will be an initial response for + the "none" auth request, and then a response to the immediate auth request. + We need to be careful handling them. */ + if (cli_ses.ignore_next_auth_response) { + cli_ses.state = USERAUTH_REQ_SENT; + cli_ses.ignore_next_auth_response = 0; + TRACE(("leave recv_msg_userauth_failure, ignored response, state set to USERAUTH_REQ_SENT")); + return; + } else { +#if DROPBEAR_CLI_PUBKEY_AUTH + /* If it was a pubkey auth request, we should cross that key + * off the list. */ + if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) { + cli_pubkeyfail(); + } +#endif + +#if DROPBEAR_CLI_INTERACT_AUTH + /* If we get a failure message for keyboard interactive without + * receiving any request info packet, then we don't bother trying + * keyboard interactive again */ + if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT + && !cli_ses.interact_request_received) { + TRACE(("setting auth_interact_failed = 1")) + cli_ses.auth_interact_failed = 1; + } +#endif + cli_ses.state = USERAUTH_FAIL_RCVD; + cli_ses.lastauthtype = AUTH_TYPE_NONE; + } + + methods = buf_getstring(ses.payload, &methlen); + + partial = buf_getbool(ses.payload); + + if (partial) { + dropbear_log(LOG_INFO, "Authentication partially succeeded, more attempts required"); + } else { + ses.authstate.failcount++; + } + + TRACE(("Methods (len %d): '%s'", methlen, methods)) + + ses.authstate.authdone=0; + ses.authstate.authtypes=0; + + /* Split with nulls rather than commas */ + for (i = 0; i < methlen; i++) { + if (methods[i] == ',') { + methods[i] = '\0'; + } + } + + tok = methods; /* tok stores the next method we'll compare */ + for (i = 0; i <= methlen; i++) { + if (methods[i] == '\0') { + TRACE(("auth method '%s'", tok)) +#if DROPBEAR_CLI_PUBKEY_AUTH + if (strncmp(AUTH_METHOD_PUBKEY, tok, + AUTH_METHOD_PUBKEY_LEN) == 0) { + ses.authstate.authtypes |= AUTH_TYPE_PUBKEY; + } +#endif +#if DROPBEAR_CLI_INTERACT_AUTH + if (strncmp(AUTH_METHOD_INTERACT, tok, + AUTH_METHOD_INTERACT_LEN) == 0) { + ses.authstate.authtypes |= AUTH_TYPE_INTERACT; + } +#endif +#if DROPBEAR_CLI_PASSWORD_AUTH + if (strncmp(AUTH_METHOD_PASSWORD, tok, + AUTH_METHOD_PASSWORD_LEN) == 0) { + ses.authstate.authtypes |= AUTH_TYPE_PASSWORD; + } +#endif + tok = &methods[i+1]; /* Must make sure we don't use it after the + last loop, since it'll point to something + undefined */ + } + } + + m_free(methods); + + TRACE(("leave recv_msg_userauth_failure")) +} + +void recv_msg_userauth_success() { + /* This function can validly get called multiple times + if DROPBEAR_CLI_IMMEDIATE_AUTH is set */ + + DEBUG1(("received msg_userauth_success")) + if (cli_opts.disable_trivial_auth && cli_ses.is_trivial_auth) { + dropbear_exit("trivial authentication not allowed"); + } + /* Note: in delayed-zlib mode, setting authdone here + * will enable compression in the transport layer */ + ses.authstate.authdone = 1; + cli_ses.state = USERAUTH_SUCCESS_RCVD; + cli_ses.lastauthtype = AUTH_TYPE_NONE; + +#if DROPBEAR_CLI_PUBKEY_AUTH + cli_auth_pubkey_cleanup(); +#endif +} + +int cli_auth_try() { + + int finished = 0; + TRACE(("enter cli_auth_try")) + + CHECKCLEARTOWRITE(); + + /* Order to try is pubkey, interactive, password. + * As soon as "finished" is set for one, we don't do any more. */ +#if DROPBEAR_CLI_PUBKEY_AUTH + if (ses.authstate.authtypes & AUTH_TYPE_PUBKEY) { + finished = cli_auth_pubkey(); + cli_ses.lastauthtype = AUTH_TYPE_PUBKEY; + } +#endif + +#if DROPBEAR_CLI_INTERACT_AUTH + if (!finished && (ses.authstate.authtypes & AUTH_TYPE_INTERACT)) { + if (ses.keys->trans.algo_crypt->cipherdesc == NULL) { + fprintf(stderr, "Sorry, I won't let you use interactive auth unencrypted.\n"); + } else { + if (!cli_ses.auth_interact_failed) { + cli_auth_interactive(); + cli_ses.lastauthtype = AUTH_TYPE_INTERACT; + finished = 1; + } + } + } +#endif + +#if DROPBEAR_CLI_PASSWORD_AUTH + if (!finished && (ses.authstate.authtypes & AUTH_TYPE_PASSWORD)) { + if (ses.keys->trans.algo_crypt->cipherdesc == NULL) { + fprintf(stderr, "Sorry, I won't let you use password auth unencrypted.\n"); + } else { + cli_auth_password(); + finished = 1; + cli_ses.lastauthtype = AUTH_TYPE_PASSWORD; + } + } +#endif + + TRACE(("cli_auth_try lastauthtype %d", cli_ses.lastauthtype)) + + if (finished) { + TRACE(("leave cli_auth_try success")) + return DROPBEAR_SUCCESS; + } + TRACE(("leave cli_auth_try failure")) + return DROPBEAR_FAILURE; +} + +#if DROPBEAR_CLI_PASSWORD_AUTH || DROPBEAR_CLI_INTERACT_AUTH +/* A helper for getpass() that exits if the user cancels. The returned + * password is statically allocated by getpass() */ +char* getpass_or_cancel(const char* prompt) +{ + char* password = NULL; + +#if DROPBEAR_USE_PASSWORD_ENV + /* Password provided in an environment var */ + password = getenv(DROPBEAR_PASSWORD_ENV); + if (password) + { + return password; + } +#endif + + password = getpass(prompt); + + /* 0x03 is a ctrl-c character in the buffer. */ + if (password == NULL || strchr(password, '\3') != NULL) { + dropbear_close("Interrupted."); + } + return password; +} +#endif diff --git a/src/cli-authinteract.c b/src/cli-authinteract.c new file mode 100644 index 0000000..6d2fad7 --- /dev/null +++ b/src/cli-authinteract.c @@ -0,0 +1,176 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2005 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "buffer.h" +#include "dbutil.h" +#include "session.h" +#include "ssh.h" +#include "runopts.h" + +#if DROPBEAR_CLI_INTERACT_AUTH + +static char* get_response(char* prompt) +{ + FILE* tty = NULL; + char* response = NULL; + /* not a password, but a reasonable limit */ + char buf[DROPBEAR_MAX_CLI_PASS]; + char* ret = NULL; + + fprintf(stderr, "%s", prompt); + + tty = fopen(_PATH_TTY, "r"); + if (tty) { + ret = fgets(buf, sizeof(buf), tty); + fclose(tty); + } else { + ret = fgets(buf, sizeof(buf), stdin); + } + + if (ret == NULL) { + response = m_strdup(""); + } else { + unsigned int buflen = strlen(buf); + /* fgets includes newlines */ + if (buflen > 0 && buf[buflen-1] == '\n') + buf[buflen-1] = '\0'; + response = m_strdup(buf); + } + + m_burn(buf, sizeof(buf)); + + return response; +} + +void recv_msg_userauth_info_request() { + + char *name = NULL; + char *instruction = NULL; + unsigned int num_prompts = 0; + unsigned int i; + + char *prompt = NULL; + unsigned int echo = 0; + char *response = NULL; + + TRACE(("enter recv_msg_recv_userauth_info_request")) + + /* Let the user know what password/host they are authing for */ + if (!cli_ses.interact_request_received) { + fprintf(stderr, "Login for %s@%s\n", cli_opts.username, + cli_opts.remotehost); + } + cli_ses.interact_request_received = 1; + + name = buf_getstring(ses.payload, NULL); + instruction = buf_getstring(ses.payload, NULL); + + /* language tag */ + buf_eatstring(ses.payload); + + num_prompts = buf_getint(ses.payload); + + if (num_prompts >= DROPBEAR_MAX_CLI_INTERACT_PROMPTS) { + dropbear_exit("Too many prompts received for keyboard-interactive"); + } + + /* we'll build the response as we go */ + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_INFO_RESPONSE); + buf_putint(ses.writepayload, num_prompts); + + if (strlen(name) > 0) { + cleantext(name); + fprintf(stderr, "%s", name); + } + m_free(name); + + if (strlen(instruction) > 0) { + cleantext(instruction); + fprintf(stderr, "%s", instruction); + } + m_free(instruction); + + for (i = 0; i < num_prompts; i++) { + unsigned int response_len = 0; + cli_ses.is_trivial_auth = 0; + prompt = buf_getstring(ses.payload, NULL); + cleantext(prompt); + + echo = buf_getbool(ses.payload); + + if (!echo) { + char* p = getpass_or_cancel(prompt); + response = m_strdup(p); + m_burn(p, strlen(p)); + } else { + response = get_response(prompt); + } + + response_len = strlen(response); + buf_putstring(ses.writepayload, response, response_len); + m_burn(response, response_len); + m_free(prompt); + m_free(response); + } + + encrypt_packet(); + + + TRACE(("leave recv_msg_recv_userauth_info_request")) +} + +void cli_auth_interactive() { + + TRACE(("enter cli_auth_interactive")) + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST); + + /* username */ + buf_putstring(ses.writepayload, cli_opts.username, + strlen(cli_opts.username)); + + /* service name */ + buf_putstring(ses.writepayload, SSH_SERVICE_CONNECTION, + SSH_SERVICE_CONNECTION_LEN); + + /* method */ + buf_putstring(ses.writepayload, AUTH_METHOD_INTERACT, + AUTH_METHOD_INTERACT_LEN); + + /* empty language tag */ + buf_putstring(ses.writepayload, "", 0); + + /* empty submethods */ + buf_putstring(ses.writepayload, "", 0); + + encrypt_packet(); + cli_ses.interact_request_received = 0; + + TRACE(("leave cli_auth_interactive")) + +} +#endif /* DROPBEAR_CLI_INTERACT_AUTH */ diff --git a/src/cli-authpasswd.c b/src/cli-authpasswd.c new file mode 100644 index 0000000..91790ce --- /dev/null +++ b/src/cli-authpasswd.c @@ -0,0 +1,161 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "buffer.h" +#include "dbutil.h" +#include "session.h" +#include "ssh.h" +#include "runopts.h" + +#if DROPBEAR_CLI_PASSWORD_AUTH + +#if DROPBEAR_CLI_ASKPASS_HELPER +/* Returns 1 if we want to use the askpass program, 0 otherwise */ +static int want_askpass() +{ + char* askpass_prog = NULL; + + askpass_prog = getenv("SSH_ASKPASS"); + return askpass_prog && + ((!isatty(STDIN_FILENO) && getenv("DISPLAY") ) + || getenv("SSH_ASKPASS_ALWAYS")); +} + +/* returns a statically allocated password from a helper app, or NULL + * on failure */ +static char *gui_getpass(const char *prompt) { + + pid_t pid; + int p[2], maxlen, len, status; + static char buf[DROPBEAR_MAX_CLI_PASS + 1]; + char* helper = NULL; + + TRACE(("enter gui_getpass")) + + helper = getenv("SSH_ASKPASS"); + if (!helper) + { + TRACE(("leave gui_getpass: no askpass program")) + return NULL; + } + + if (pipe(p) < 0) { + TRACE(("error creating child pipe")) + return NULL; + } + + pid = fork(); + + if (pid < 0) { + TRACE(("fork error")) + return NULL; + } + + if (!pid) { + /* child */ + close(p[0]); + if (dup2(p[1], STDOUT_FILENO) < 0) { + TRACE(("error redirecting stdout")) + exit(1); + } + close(p[1]); + execlp(helper, helper, prompt, (char *)0); + TRACE(("execlp error")) + exit(1); + } + + close(p[1]); + maxlen = sizeof(buf); + while (maxlen > 0) { + len = read(p[0], buf + sizeof(buf) - maxlen, maxlen); + if (len > 0) { + maxlen -= len; + } else { + if (errno != EINTR) + break; + } + } + + close(p[0]); + + while (waitpid(pid, &status, 0) < 0 && errno == EINTR) + ; + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + return(NULL); + + len = sizeof(buf) - maxlen; + buf[len] = '\0'; + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + TRACE(("leave gui_getpass")) + return(buf); +} +#endif /* DROPBEAR_CLI_ASKPASS_HELPER */ + +void cli_auth_password() { + + char* password = NULL; + char prompt[80]; + + DEBUG1(("enter cli_auth_password")) + CHECKCLEARTOWRITE(); + + snprintf(prompt, sizeof(prompt), "%s@%s's password: ", + cli_opts.username, cli_opts.remotehost); +#if DROPBEAR_CLI_ASKPASS_HELPER + if (want_askpass()) + { + password = gui_getpass(prompt); + if (!password) { + dropbear_exit("No password"); + } + } else +#endif + { + password = getpass_or_cancel(prompt); + } + + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST); + + buf_putstring(ses.writepayload, cli_opts.username, + strlen(cli_opts.username)); + + buf_putstring(ses.writepayload, SSH_SERVICE_CONNECTION, + SSH_SERVICE_CONNECTION_LEN); + + buf_putstring(ses.writepayload, AUTH_METHOD_PASSWORD, + AUTH_METHOD_PASSWORD_LEN); + + buf_putbyte(ses.writepayload, 0); /* FALSE - so says the spec */ + + buf_putstring(ses.writepayload, password, strlen(password)); + + encrypt_packet(); + m_burn(password, strlen(password)); + cli_ses.is_trivial_auth = 0; + TRACE(("leave cli_auth_password")) +} +#endif /* DROPBEAR_CLI_PASSWORD_AUTH */ diff --git a/src/cli-authpubkey.c b/src/cli-authpubkey.c new file mode 100644 index 0000000..975d3bd --- /dev/null +++ b/src/cli-authpubkey.c @@ -0,0 +1,291 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2004 by Mihnea Stoenescu + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "buffer.h" +#include "dbutil.h" +#include "session.h" +#include "ssh.h" +#include "runopts.h" +#include "auth.h" +#include "agentfwd.h" + +#if DROPBEAR_CLI_PUBKEY_AUTH +static void send_msg_userauth_pubkey(sign_key *key, enum signature_type sigtype, int realsign); + +/* Called when we receive a SSH_MSG_USERAUTH_FAILURE for a pubkey request. + * We use it to remove the key we tried from the list */ +void cli_pubkeyfail() { + m_list_elem *iter; + for (iter = cli_opts.privkeys->first; iter; iter = iter->next) { + sign_key *iter_key = (sign_key*)iter->item; + + if (iter_key == cli_ses.lastprivkey) + { + /* found the failing key */ + list_remove(iter); + sign_key_free(iter_key); + cli_ses.lastprivkey = NULL; + return; + } + } +} + +void recv_msg_userauth_pk_ok() { + m_list_elem *iter; + buffer* keybuf = NULL; + char* algotype = NULL; + unsigned int algolen; + enum signkey_type keytype; + enum signature_type sigtype; + unsigned int remotelen; + + TRACE(("enter recv_msg_userauth_pk_ok")) + + algotype = buf_getstring(ses.payload, &algolen); + sigtype = signature_type_from_name(algotype, algolen); + keytype = signkey_type_from_signature(sigtype); + TRACE(("recv_msg_userauth_pk_ok: type %d", sigtype)) + m_free(algotype); + + keybuf = buf_new(MAX_PUBKEY_SIZE); + + remotelen = buf_getint(ses.payload); + + /* Iterate through our keys, find which one it was that matched, and + * send a real request with that key */ + for (iter = cli_opts.privkeys->first; iter; iter = iter->next) { + sign_key *key = (sign_key*)iter->item; + if (key->type != keytype) { + /* Types differed */ + TRACE(("types differed")) + continue; + } + + /* Now we compare the contents of the key */ + keybuf->pos = keybuf->len = 0; + buf_put_pub_key(keybuf, key, keytype); + buf_setpos(keybuf, 0); + buf_incrpos(keybuf, 4); /* first int is the length of the remainder (ie + remotelen) which has already been taken from + the remote buffer */ + + + if (keybuf->len-4 != remotelen) { + TRACE(("lengths differed: localh %d remote %d", keybuf->len, remotelen)) + /* Lengths differed */ + continue; + } + if (memcmp(buf_getptr(keybuf, remotelen), + buf_getptr(ses.payload, remotelen), remotelen) != 0) { + /* Data didn't match this key */ + TRACE(("data differed")) + continue; + } + + /* Success */ + break; + } + buf_free(keybuf); + + if (iter != NULL) { + TRACE(("matching key")) + /* XXX TODO: if it's an encrypted key, here we ask for their + * password */ + send_msg_userauth_pubkey((sign_key*)iter->item, sigtype, 1); + } else { + TRACE(("That was whacky. We got told that a key was valid, but it didn't match our list. Sounds like dodgy code on Dropbear's part")) + } + + TRACE(("leave recv_msg_userauth_pk_ok")) +} + +static void cli_buf_put_sign(buffer* buf, sign_key *key, enum signature_type sigtype, + const buffer *data_buf) { +#if DROPBEAR_CLI_AGENTFWD + /* TODO: rsa-sha256 agent */ + if (key->source == SIGNKEY_SOURCE_AGENT) { + /* Format the agent signature ourselves, as buf_put_sign would. */ + buffer *sigblob; + sigblob = buf_new(MAX_PUBKEY_SIZE); + agent_buf_sign(sigblob, key, data_buf, sigtype); + buf_putbufstring(buf, sigblob); + buf_free(sigblob); + } else +#endif /* DROPBEAR_CLI_AGENTFWD */ + { + buf_put_sign(buf, key, sigtype, data_buf); + } +} + +static void send_msg_userauth_pubkey(sign_key *key, enum signature_type sigtype, int realsign) { + + const char *algoname = NULL; + unsigned int algolen; + buffer* sigbuf = NULL; + enum signkey_type keytype = signkey_type_from_signature(sigtype); + + DEBUG1(("enter send_msg_userauth_pubkey %s", signature_name_from_type(sigtype, NULL))) + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST); + + buf_putstring(ses.writepayload, cli_opts.username, + strlen(cli_opts.username)); + + buf_putstring(ses.writepayload, SSH_SERVICE_CONNECTION, + SSH_SERVICE_CONNECTION_LEN); + + buf_putstring(ses.writepayload, AUTH_METHOD_PUBKEY, + AUTH_METHOD_PUBKEY_LEN); + + buf_putbyte(ses.writepayload, realsign); + + algoname = signature_name_from_type(sigtype, &algolen); + buf_putstring(ses.writepayload, algoname, algolen); + buf_put_pub_key(ses.writepayload, key, keytype); + + if (realsign) { + TRACE(("realsign")) + /* We put the signature as well - this contains string(session id), then + * the contents of the write payload to this point */ + sigbuf = buf_new(4 + ses.session_id->len + ses.writepayload->len); + buf_putbufstring(sigbuf, ses.session_id); + buf_putbytes(sigbuf, ses.writepayload->data, ses.writepayload->len); + cli_buf_put_sign(ses.writepayload, key, sigtype, sigbuf); + buf_free(sigbuf); /* Nothing confidential in the buffer */ + cli_ses.is_trivial_auth = 0; + } + + encrypt_packet(); + TRACE(("leave send_msg_userauth_pubkey")) +} + +/* Returns 1 if a key was tried */ +int cli_auth_pubkey() { + enum signature_type sigtype = DROPBEAR_SIGNATURE_NONE; + TRACE(("enter cli_auth_pubkey")) + +#if DROPBEAR_CLI_AGENTFWD + if (!cli_opts.agent_keys_loaded) { + /* get the list of available keys from the agent */ + cli_load_agent_keys(cli_opts.privkeys); + cli_opts.agent_keys_loaded = 1; + TRACE(("cli_auth_pubkey: agent keys loaded")) + } +#endif + + /* iterate through privkeys to remove ones not allowed in server-sig-algs */ + while (cli_opts.privkeys->first) { + sign_key * key = (sign_key*)cli_opts.privkeys->first->item; + if (cli_ses.server_sig_algs) { +#if DROPBEAR_RSA + if (key->type == DROPBEAR_SIGNKEY_RSA) { +#if DROPBEAR_RSA_SHA256 + if (buf_has_algo(cli_ses.server_sig_algs, SSH_SIGNATURE_RSA_SHA256) + == DROPBEAR_SUCCESS) { + sigtype = DROPBEAR_SIGNATURE_RSA_SHA256; + TRACE(("server-sig-algs allows rsa sha256")) + break; + } +#endif /* DROPBEAR_RSA_SHA256 */ +#if DROPBEAR_RSA_SHA1 + if (buf_has_algo(cli_ses.server_sig_algs, SSH_SIGNKEY_RSA) + == DROPBEAR_SUCCESS) { + sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; + TRACE(("server-sig-algs allows rsa sha1")) + break; + } +#endif /* DROPBEAR_RSA_SHA256 */ + } else +#endif /* DROPBEAR_RSA */ + { + /* Not RSA */ + const char *name = NULL; + sigtype = signature_type_from_signkey(key->type); + name = signature_name_from_type(sigtype, NULL); + if (buf_has_algo(cli_ses.server_sig_algs, name) + == DROPBEAR_SUCCESS) { + TRACE(("server-sig-algs allows %s", name)) + break; + } + } + + /* No match, skip this key */ + TRACE(("server-sig-algs no match keytype %d, skipping", key->type)) + key = list_remove(cli_opts.privkeys->first); + sign_key_free(key); + continue; + } else { + /* Server didn't provide a server-sig-algs list, we'll + assume all except rsa-sha256 are OK. */ +#if DROPBEAR_RSA + if (key->type == DROPBEAR_SIGNKEY_RSA) { +#if DROPBEAR_RSA_SHA1 + sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; + TRACE(("no server-sig-algs, using rsa sha1")) + break; +#else + /* only support rsa-sha256, skip this key */ + TRACE(("no server-sig-algs, skipping rsa sha256")) + key = list_remove(cli_opts.privkeys->first); + sign_key_free(key); + continue; +#endif + } /* key->type == DROPBEAR_SIGNKEY_RSA */ +#endif /* DROPBEAR_RSA */ + sigtype = signature_type_from_signkey(key->type); + TRACE(("no server-sig-algs, using key")) + break; + } + } + + if (cli_opts.privkeys->first) { + sign_key * key = (sign_key*)cli_opts.privkeys->first->item; + /* Send a trial request */ + send_msg_userauth_pubkey(key, sigtype, 0); + cli_ses.lastprivkey = key; + TRACE(("leave cli_auth_pubkey-success")) + return 1; + } else { + /* no more keys left */ + TRACE(("leave cli_auth_pubkey-failure")) + return 0; + } +} + +void cli_auth_pubkey_cleanup() { + +#if DROPBEAR_CLI_AGENTFWD + m_close(cli_opts.agent_fd); + cli_opts.agent_fd = -1; +#endif + + while (cli_opts.privkeys->first) { + sign_key * key = list_remove(cli_opts.privkeys->first); + sign_key_free(key); + } +} +#endif /* Pubkey auth */ diff --git a/src/cli-channel.c b/src/cli-channel.c new file mode 100644 index 0000000..b88e913 --- /dev/null +++ b/src/cli-channel.c @@ -0,0 +1,59 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002-2004 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "channel.h" +#include "buffer.h" +#include "circbuffer.h" +#include "dbutil.h" +#include "session.h" +#include "ssh.h" + +/* We receive channel data - only used by the client chansession code*/ +void recv_msg_channel_extended_data() { + + struct Channel *channel; + unsigned int datatype; + + TRACE(("enter recv_msg_channel_extended_data")) + + channel = getchannel(); + + if (channel->type != &clichansess) { + TRACE(("leave recv_msg_channel_extended_data: chantype is wrong")) + return; /* we just ignore it */ + } + + datatype = buf_getint(ses.payload); + + if (datatype != SSH_EXTENDED_DATA_STDERR) { + TRACE(("leave recv_msg_channel_extended_data: wrong datatype: %d", + datatype)) + return; + } + + common_recv_msg_channel_data(channel, channel->errfd, channel->extrabuf); + + TRACE(("leave recv_msg_channel_extended_data")) +} diff --git a/src/cli-chansession.c b/src/cli-chansession.c new file mode 100644 index 0000000..73bee17 --- /dev/null +++ b/src/cli-chansession.c @@ -0,0 +1,484 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2004 by Mihnea Stoenescu + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "packet.h" +#include "buffer.h" +#include "session.h" +#include "dbutil.h" +#include "channel.h" +#include "ssh.h" +#include "runopts.h" +#include "termcodes.h" +#include "chansession.h" +#include "agentfwd.h" + +static void cli_closechansess(const struct Channel *channel); +static int cli_initchansess(struct Channel *channel); +static void cli_chansessreq(struct Channel *channel); +static void send_chansess_pty_req(const struct Channel *channel); +static void send_chansess_shell_req(const struct Channel *channel); +static void cli_escape_handler(const struct Channel *channel, const unsigned char* buf, int *len); +static int cli_init_netcat(struct Channel *channel); + +static void cli_tty_setup(void); + +const struct ChanType clichansess = { + "session", /* name */ + cli_initchansess, /* inithandler */ + NULL, /* checkclosehandler */ + cli_chansessreq, /* reqhandler */ + cli_closechansess, /* closehandler */ + NULL, /* cleanup */ +}; + +static void cli_chansessreq(struct Channel *channel) { + + char* type = NULL; + int wantreply; + + TRACE(("enter cli_chansessreq")) + + type = buf_getstring(ses.payload, NULL); + wantreply = buf_getbool(ses.payload); + + if (strcmp(type, "exit-status") == 0) { + cli_ses.retval = buf_getint(ses.payload); + TRACE(("got exit-status of '%d'", cli_ses.retval)) + } else if (strcmp(type, "exit-signal") == 0) { + TRACE(("got exit-signal, ignoring it")) + } else { + TRACE(("unknown request '%s'", type)) + if (wantreply) { + send_msg_channel_failure(channel); + } + goto out; + } + +out: + m_free(type); +} + + +/* If the main session goes, we close it up */ +static void cli_closechansess(const struct Channel *UNUSED(channel)) { + cli_tty_cleanup(); /* Restore tty modes etc */ + + /* This channel hasn't gone yet, so we have > 1 */ + if (ses.chancount > 1) { + dropbear_log(LOG_INFO, "Waiting for other channels to close..."); + } +} + +/* Taken from OpenSSH's sshtty.c: + * RCSID("OpenBSD: sshtty.c,v 1.5 2003/09/19 17:43:35 markus Exp "); */ +static void cli_tty_setup() { + + struct termios tio; + + TRACE(("enter cli_pty_setup")) + + if (cli_ses.tty_raw_mode == 1) { + TRACE(("leave cli_tty_setup: already in raw mode!")) + return; + } + + if (tcgetattr(STDIN_FILENO, &tio) == -1) { + dropbear_exit("Failed to set raw TTY mode"); + } + + /* make a copy */ + cli_ses.saved_tio = tio; + + tio.c_iflag |= IGNPAR; + tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); +#ifdef IUCLC + tio.c_iflag &= ~IUCLC; +#endif + tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); +#ifdef IEXTEN + tio.c_lflag &= ~IEXTEN; +#endif + tio.c_oflag &= ~OPOST; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + if (tcsetattr(STDIN_FILENO, TCSADRAIN, &tio) == -1) { + dropbear_exit("Failed to set raw TTY mode"); + } + + cli_ses.tty_raw_mode = 1; + TRACE(("leave cli_tty_setup")) +} + +void cli_tty_cleanup() { + + TRACE(("enter cli_tty_cleanup")) + + if (cli_ses.tty_raw_mode == 0) { + TRACE(("leave cli_tty_cleanup: not in raw mode")) + return; + } + + if (tcsetattr(STDIN_FILENO, TCSADRAIN, &cli_ses.saved_tio) == -1) { + dropbear_log(LOG_WARNING, "Failed restoring TTY"); + } else { + cli_ses.tty_raw_mode = 0; + } + + TRACE(("leave cli_tty_cleanup")) +} + +static void put_termcodes() { + + struct termios tio; + unsigned int sshcode; + const struct TermCode *termcode; + unsigned int value; + unsigned int mapcode; + + unsigned int bufpos1, bufpos2; + + TRACE(("enter put_termcodes")) + + if (tcgetattr(STDIN_FILENO, &tio) == -1) { + dropbear_log(LOG_WARNING, "Failed reading termmodes"); + buf_putint(ses.writepayload, 1); /* Just the terminator */ + buf_putbyte(ses.writepayload, 0); /* TTY_OP_END */ + return; + } + + bufpos1 = ses.writepayload->pos; + buf_putint(ses.writepayload, 0); /* A placeholder for the final length */ + + /* As with Dropbear server, we ignore baud rates for now */ + for (sshcode = 1; sshcode < MAX_TERMCODE; sshcode++) { + + termcode = &termcodes[sshcode]; + mapcode = termcode->mapcode; + + switch (termcode->type) { + + case TERMCODE_NONE: + continue; + + case TERMCODE_CONTROLCHAR: + value = tio.c_cc[mapcode]; + break; + + case TERMCODE_INPUT: + value = tio.c_iflag & mapcode; + break; + + case TERMCODE_OUTPUT: + value = tio.c_oflag & mapcode; + break; + + case TERMCODE_LOCAL: + value = tio.c_lflag & mapcode; + break; + + case TERMCODE_CONTROL: + value = tio.c_cflag & mapcode; + break; + + default: + continue; + + } + + /* If we reach here, we have something to say */ + buf_putbyte(ses.writepayload, sshcode); + buf_putint(ses.writepayload, value); + } + + buf_putbyte(ses.writepayload, 0); /* THE END, aka TTY_OP_END */ + + /* Put the string length at the start of the buffer */ + bufpos2 = ses.writepayload->pos; + + buf_setpos(ses.writepayload, bufpos1); /* Jump back */ + buf_putint(ses.writepayload, bufpos2 - bufpos1 - 4); /* len(termcodes) */ + buf_setpos(ses.writepayload, bufpos2); /* Back where we were */ + + TRACE(("leave put_termcodes")) +} + +static void put_winsize() { + + struct winsize ws; + + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) { + /* Some sane defaults */ + ws.ws_row = 25; + ws.ws_col = 80; + ws.ws_xpixel = 0; + ws.ws_ypixel = 0; + } + + buf_putint(ses.writepayload, ws.ws_col); /* Cols */ + buf_putint(ses.writepayload, ws.ws_row); /* Rows */ + buf_putint(ses.writepayload, ws.ws_xpixel); /* Width */ + buf_putint(ses.writepayload, ws.ws_ypixel); /* Height */ + +} + +static void sigwinch_handler(int UNUSED(unused)) { + + cli_ses.winchange = 1; + +} + +void cli_chansess_winchange() { + + unsigned int i; + struct Channel *channel = NULL; + + for (i = 0; i < ses.chansize; i++) { + channel = ses.channels[i]; + if (channel != NULL && channel->type == &clichansess) { + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST); + buf_putint(ses.writepayload, channel->remotechan); + buf_putstring(ses.writepayload, "window-change", 13); + buf_putbyte(ses.writepayload, 0); /* FALSE says the spec */ + put_winsize(); + encrypt_packet(); + } + } + cli_ses.winchange = 0; +} + +static void send_chansess_pty_req(const struct Channel *channel) { + + char* term = NULL; + + TRACE(("enter send_chansess_pty_req")) + + start_send_channel_request(channel, "pty-req"); + + /* Don't want replies */ + buf_putbyte(ses.writepayload, 0); + + /* Get the terminal */ + term = getenv("TERM"); + if (term == NULL) { + term = "vt100"; /* Seems a safe default */ + } + buf_putstring(ses.writepayload, term, strlen(term)); + + /* Window size */ + put_winsize(); + + /* Terminal mode encoding */ + put_termcodes(); + + encrypt_packet(); + + /* Set up a window-change handler */ + if (signal(SIGWINCH, sigwinch_handler) == SIG_ERR) { + dropbear_exit("Signal error"); + } + TRACE(("leave send_chansess_pty_req")) +} + +static void send_chansess_shell_req(const struct Channel *channel) { + + char* reqtype = NULL; + + TRACE(("enter send_chansess_shell_req")) + + if (cli_opts.cmd) { + if (cli_opts.is_subsystem) { + reqtype = "subsystem"; + } else { + reqtype = "exec"; + } + } else { + reqtype = "shell"; + } + + start_send_channel_request(channel, reqtype); + + /* XXX TODO */ + buf_putbyte(ses.writepayload, 0); /* Don't want replies */ + if (cli_opts.cmd) { + buf_putstring(ses.writepayload, cli_opts.cmd, strlen(cli_opts.cmd)); + } + + encrypt_packet(); + TRACE(("leave send_chansess_shell_req")) +} + +/* Shared for normal client channel and netcat-alike */ +static int cli_init_stdpipe_sess(struct Channel *channel) { + channel->writefd = STDOUT_FILENO; + setnonblocking(STDOUT_FILENO); + + channel->readfd = STDIN_FILENO; + setnonblocking(STDIN_FILENO); + + channel->errfd = STDERR_FILENO; + setnonblocking(STDERR_FILENO); + + channel->extrabuf = cbuf_new(opts.recv_window); + channel->bidir_fd = 0; + return 0; +} + +static int cli_init_netcat(struct Channel *channel) { + return cli_init_stdpipe_sess(channel); +} + +static int cli_initchansess(struct Channel *channel) { + + cli_init_stdpipe_sess(channel); + +#if DROPBEAR_CLI_AGENTFWD + if (cli_opts.agent_fwd) { + cli_setup_agent(channel); + } +#endif + if (cli_opts.wantpty) { + send_chansess_pty_req(channel); + channel->prio = DROPBEAR_PRIO_LOWDELAY; + } + + send_chansess_shell_req(channel); + + if (cli_opts.wantpty) { + cli_tty_setup(); + channel->read_mangler = cli_escape_handler; + cli_ses.last_char = '\r'; + } + + return 0; /* Success */ +} + +#if DROPBEAR_CLI_NETCAT + +static const struct ChanType cli_chan_netcat = { + "direct-tcpip", + cli_init_netcat, /* inithandler */ + NULL, + NULL, + cli_closechansess, + NULL, +}; + +void cli_send_netcat_request() { + + const char* source_host = "127.0.0.1"; + const int source_port = 22; + + TRACE(("enter cli_send_netcat_request")) + cli_opts.wantpty = 0; + + if (send_msg_channel_open_init(STDIN_FILENO, &cli_chan_netcat) + == DROPBEAR_FAILURE) { + dropbear_exit("Couldn't open initial channel"); + } + + buf_putstring(ses.writepayload, cli_opts.netcat_host, + strlen(cli_opts.netcat_host)); + buf_putint(ses.writepayload, cli_opts.netcat_port); + + /* originator ip - localhost is accurate enough */ + buf_putstring(ses.writepayload, source_host, strlen(source_host)); + buf_putint(ses.writepayload, source_port); + + encrypt_packet(); + TRACE(("leave cli_send_netcat_request")) +} +#endif + +void cli_send_chansess_request() { + + TRACE(("enter cli_send_chansess_request")) + + if (send_msg_channel_open_init(STDIN_FILENO, &clichansess) + == DROPBEAR_FAILURE) { + dropbear_exit("Couldn't open initial channel"); + } + + /* No special channel request data */ + encrypt_packet(); + TRACE(("leave cli_send_chansess_request")) + +} + +/* returns 1 if the character should be consumed, 0 to pass through */ +static int +do_escape(unsigned char c) { + switch (c) { + case '.': + dropbear_exit("Terminated"); + return 1; + case 0x1a: + /* ctrl-z */ + cli_tty_cleanup(); + kill(getpid(), SIGTSTP); + /* after continuation */ + cli_tty_setup(); + cli_ses.winchange = 1; + return 1; + default: + return 0; + } +} + +static +void cli_escape_handler(const struct Channel* UNUSED(channel), const unsigned char* buf, int *len) { + char c; + int skip_char = 0; + + /* only handle escape characters if they are read one at a time. simplifies + the code and avoids nasty people putting ~. at the start of a line to paste */ + if (*len != 1) { + cli_ses.last_char = 0x0; + return; + } + + c = buf[0]; + + if (cli_ses.last_char == DROPBEAR_ESCAPE_CHAR) { + skip_char = do_escape(c); + cli_ses.last_char = 0x0; + } else { + if (c == DROPBEAR_ESCAPE_CHAR) { + if (cli_ses.last_char == '\r') { + cli_ses.last_char = DROPBEAR_ESCAPE_CHAR; + skip_char = 1; + } else { + cli_ses.last_char = 0x0; + } + } else { + cli_ses.last_char = c; + } + } + + if (skip_char) { + *len = 0; + } +} diff --git a/src/cli-kex.c b/src/cli-kex.c new file mode 100644 index 0000000..6cb75c2 --- /dev/null +++ b/src/cli-kex.c @@ -0,0 +1,465 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002-2004 Matt Johnston + * Copyright (c) 2004 by Mihnea Stoenescu + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "session.h" +#include "dbutil.h" +#include "algo.h" +#include "buffer.h" +#include "session.h" +#include "kex.h" +#include "ssh.h" +#include "packet.h" +#include "bignum.h" +#include "dbrandom.h" +#include "runopts.h" +#include "signkey.h" +#include "ecc.h" + + +static void checkhostkey(const unsigned char* keyblob, unsigned int keybloblen); +#define MAX_KNOWNHOSTS_LINE 4500 + +void send_msg_kexdh_init() { + TRACE(("send_msg_kexdh_init()")) + + CHECKCLEARTOWRITE(); + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing && fuzz.skip_kexmaths) { + return; + } +#endif + + buf_putbyte(ses.writepayload, SSH_MSG_KEXDH_INIT); + switch (ses.newkeys->algo_kex->mode) { +#if DROPBEAR_NORMAL_DH + case DROPBEAR_KEX_NORMAL_DH: + if (ses.newkeys->algo_kex != cli_ses.param_kex_algo + || !cli_ses.dh_param) { + if (cli_ses.dh_param) { + free_kexdh_param(cli_ses.dh_param); + } + cli_ses.dh_param = gen_kexdh_param(); + } + buf_putmpint(ses.writepayload, &cli_ses.dh_param->pub); + break; +#endif +#if DROPBEAR_ECDH + case DROPBEAR_KEX_ECDH: + if (ses.newkeys->algo_kex != cli_ses.param_kex_algo + || !cli_ses.ecdh_param) { + if (cli_ses.ecdh_param) { + free_kexecdh_param(cli_ses.ecdh_param); + } + cli_ses.ecdh_param = gen_kexecdh_param(); + } + buf_put_ecc_raw_pubkey_string(ses.writepayload, &cli_ses.ecdh_param->key); + break; +#endif +#if DROPBEAR_CURVE25519 + case DROPBEAR_KEX_CURVE25519: + if (ses.newkeys->algo_kex != cli_ses.param_kex_algo + || !cli_ses.curve25519_param) { + if (cli_ses.curve25519_param) { + free_kexcurve25519_param(cli_ses.curve25519_param); + } + cli_ses.curve25519_param = gen_kexcurve25519_param(); + } + buf_putstring(ses.writepayload, cli_ses.curve25519_param->pub, CURVE25519_LEN); + break; +#endif + } + + cli_ses.param_kex_algo = ses.newkeys->algo_kex; + encrypt_packet(); +} + +/* Handle a diffie-hellman key exchange reply. */ +void recv_msg_kexdh_reply() { + + sign_key *hostkey = NULL; + unsigned int keytype, keybloblen; + unsigned char* keyblob = NULL; + + TRACE(("enter recv_msg_kexdh_reply")) + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing && fuzz.skip_kexmaths) { + return; + } +#endif + + if (cli_ses.kex_state != KEXDH_INIT_SENT) { + dropbear_exit("Received out-of-order kexdhreply"); + } + keytype = ses.newkeys->algo_hostkey; + TRACE(("keytype is %d", keytype)) + + hostkey = new_sign_key(); + keybloblen = buf_getint(ses.payload); + + keyblob = buf_getptr(ses.payload, keybloblen); + if (!ses.kexstate.donefirstkex) { + /* Only makes sense the first time */ + checkhostkey(keyblob, keybloblen); + } + + if (buf_get_pub_key(ses.payload, hostkey, &keytype) != DROPBEAR_SUCCESS) { + TRACE(("failed getting pubkey")) + dropbear_exit("Bad KEX packet"); + } + + switch (ses.newkeys->algo_kex->mode) { +#if DROPBEAR_NORMAL_DH + case DROPBEAR_KEX_NORMAL_DH: + { + DEF_MP_INT(dh_f); + m_mp_init(&dh_f); + if (buf_getmpint(ses.payload, &dh_f) != DROPBEAR_SUCCESS) { + TRACE(("failed getting mpint")) + dropbear_exit("Bad KEX packet"); + } + + kexdh_comb_key(cli_ses.dh_param, &dh_f, hostkey); + mp_clear(&dh_f); + } + break; +#endif +#if DROPBEAR_ECDH + case DROPBEAR_KEX_ECDH: + { + buffer *ecdh_qs = buf_getstringbuf(ses.payload); + kexecdh_comb_key(cli_ses.ecdh_param, ecdh_qs, hostkey); + buf_free(ecdh_qs); + } + break; +#endif +#if DROPBEAR_CURVE25519 + case DROPBEAR_KEX_CURVE25519: + { + buffer *ecdh_qs = buf_getstringbuf(ses.payload); + kexcurve25519_comb_key(cli_ses.curve25519_param, ecdh_qs, hostkey); + buf_free(ecdh_qs); + } + break; +#endif + } + +#if DROPBEAR_NORMAL_DH + if (cli_ses.dh_param) { + free_kexdh_param(cli_ses.dh_param); + cli_ses.dh_param = NULL; + } +#endif +#if DROPBEAR_ECDH + if (cli_ses.ecdh_param) { + free_kexecdh_param(cli_ses.ecdh_param); + cli_ses.ecdh_param = NULL; + } +#endif +#if DROPBEAR_CURVE25519 + if (cli_ses.curve25519_param) { + free_kexcurve25519_param(cli_ses.curve25519_param); + cli_ses.curve25519_param = NULL; + } +#endif + + cli_ses.param_kex_algo = NULL; + if (buf_verify(ses.payload, hostkey, ses.newkeys->algo_signature, + ses.hash) != DROPBEAR_SUCCESS) { + dropbear_exit("Bad hostkey signature"); + } + + sign_key_free(hostkey); + hostkey = NULL; + + send_msg_newkeys(); + ses.requirenext = SSH_MSG_NEWKEYS; + TRACE(("leave recv_msg_kexdh_init")) +} + +static void ask_to_confirm(const unsigned char* keyblob, unsigned int keybloblen, + const char* algoname) { + + char* fp = NULL; + FILE *tty = NULL; + int response = 'z'; + + fp = sign_key_fingerprint(keyblob, keybloblen); + if (cli_opts.always_accept_key) { + dropbear_log(LOG_INFO, "\nHost '%s' key accepted unconditionally.\n(%s fingerprint %s)\n", + cli_opts.remotehost, + algoname, + fp); + m_free(fp); + return; + } + fprintf(stderr, "\nHost '%s' is not in the trusted hosts file.\n(%s fingerprint %s)\nDo you want to continue connecting? (y/n) ", + cli_opts.remotehost, + algoname, + fp); + m_free(fp); + + tty = fopen(_PATH_TTY, "r"); + if (tty) { + response = getc(tty); + fclose(tty); + } else { + response = getc(stdin); + /* flush stdin buffer */ + while ((getchar()) != '\n'); + } + + if (response == 'y') { + return; + } + + dropbear_exit("Didn't validate host key"); +} + +static FILE* open_known_hosts_file(int * readonly) +{ + FILE * hostsfile = NULL; + char * filename = NULL; + char * homedir = NULL; + + homedir = getenv("HOME"); + + if (!homedir) { + struct passwd * pw = NULL; + pw = getpwuid(getuid()); + if (pw) { + homedir = pw->pw_dir; + } + } + + if (homedir) { + unsigned int len; + len = strlen(homedir); + filename = m_malloc(len + 18); /* "/.ssh/known_hosts" and null-terminator*/ + + snprintf(filename, len+18, "%s/.ssh", homedir); + /* Check that ~/.ssh exists - easiest way is just to mkdir */ + if (mkdir(filename, S_IRWXU) != 0) { + if (errno != EEXIST) { + dropbear_log(LOG_INFO, "Warning: failed creating %s/.ssh: %s", + homedir, strerror(errno)); + TRACE(("mkdir didn't work: %s", strerror(errno))) + goto out; + } + } + + snprintf(filename, len+18, "%s/.ssh/known_hosts", homedir); + hostsfile = fopen(filename, "a+"); + + if (hostsfile != NULL) { + *readonly = 0; + fseek(hostsfile, 0, SEEK_SET); + } else { + /* We mightn't have been able to open it if it was read-only */ + if (errno == EACCES || errno == EROFS) { + TRACE(("trying readonly: %s", strerror(errno))) + *readonly = 1; + hostsfile = fopen(filename, "r"); + } + } + } + + if (hostsfile == NULL) { + TRACE(("hostsfile didn't open: %s", strerror(errno))) + dropbear_log(LOG_WARNING, "Failed to open %s/.ssh/known_hosts", + homedir); + goto out; + } + +out: + m_free(filename); + return hostsfile; +} + +static void checkhostkey(const unsigned char* keyblob, unsigned int keybloblen) { + + FILE *hostsfile = NULL; + int readonly = 0; + unsigned int hostlen, algolen; + unsigned long len; + const char *algoname = NULL; + char * fingerprint = NULL; + buffer * line = NULL; + int ret; + + if (cli_opts.no_hostkey_check) { + dropbear_log(LOG_INFO, "Caution, skipping hostkey check for %s\n", cli_opts.remotehost); + return; + } + + algoname = signkey_name_from_type(ses.newkeys->algo_hostkey, &algolen); + + hostsfile = open_known_hosts_file(&readonly); + if (!hostsfile) { + ask_to_confirm(keyblob, keybloblen, algoname); + /* ask_to_confirm will exit upon failure */ + return; + } + + line = buf_new(MAX_KNOWNHOSTS_LINE); + hostlen = strlen(cli_opts.remotehost); + + do { + if (buf_getline(line, hostsfile) == DROPBEAR_FAILURE) { + TRACE(("failed reading line: prob EOF")) + break; + } + + /* The line is too short to be sensible */ + /* "30" is 'enough to hold ssh-dss plus the spaces, ie so we don't + * buf_getfoo() past the end and die horribly - the base64 parsing + * code is what tiptoes up to the end nicely */ + if (line->len < (hostlen+30) ) { + TRACE(("line is too short to be sensible")) + continue; + } + + /* Compare hostnames */ + if (strncmp(cli_opts.remotehost, (const char *) buf_getptr(line, hostlen), + hostlen) != 0) { + continue; + } + + buf_incrpos(line, hostlen); + if (buf_getbyte(line) != ' ') { + /* there wasn't a space after the hostname, something dodgy */ + TRACE(("missing space afte matching hostname")) + continue; + } + + if (strncmp((const char *) buf_getptr(line, algolen), algoname, algolen) != 0) { + TRACE(("algo doesn't match")) + continue; + } + + buf_incrpos(line, algolen); + if (buf_getbyte(line) != ' ') { + TRACE(("missing space after algo")) + continue; + } + + /* Now we're at the interesting hostkey */ + ret = cmp_base64_key(keyblob, keybloblen, (const unsigned char *) algoname, algolen, + line, &fingerprint); + + if (ret == DROPBEAR_SUCCESS) { + /* Good matching key */ + DEBUG1(("server match %s", fingerprint)) + goto out; + } + + /* The keys didn't match. eep. Note that we're "leaking" + the fingerprint strings here, but we're exiting anyway */ + dropbear_exit("\n\n%s host key mismatch for %s !\n" + "Fingerprint is %s\n" + "Expected %s\n" + "If you know that the host key is correct you can\nremove the bad entry from ~/.ssh/known_hosts", + algoname, + cli_opts.remotehost, + sign_key_fingerprint(keyblob, keybloblen), + fingerprint ? fingerprint : "UNKNOWN"); + } while (1); /* keep going 'til something happens */ + + /* Key doesn't exist yet */ + ask_to_confirm(keyblob, keybloblen, algoname); + + /* If we get here, they said yes */ + + if (readonly) { + TRACE(("readonly")) + goto out; + } + + if (!cli_opts.always_accept_key) { + /* put the new entry in the file */ + fseek(hostsfile, 0, SEEK_END); /* In case it wasn't opened append */ + buf_setpos(line, 0); + buf_setlen(line, 0); + buf_putbytes(line, (const unsigned char *) cli_opts.remotehost, hostlen); + buf_putbyte(line, ' '); + buf_putbytes(line, (const unsigned char *) algoname, algolen); + buf_putbyte(line, ' '); + len = line->size - line->pos; + /* The only failure with base64 is buffer_overflow, but buf_getwriteptr + * will die horribly in the case anyway */ + base64_encode(keyblob, keybloblen, buf_getwriteptr(line, len), &len); + buf_incrwritepos(line, len); + buf_putbyte(line, '\n'); + buf_setpos(line, 0); + fwrite(buf_getptr(line, line->len), line->len, 1, hostsfile); + /* We ignore errors, since there's not much we can do about them */ + } + +out: + if (hostsfile != NULL) { + fclose(hostsfile); + } + if (line != NULL) { + buf_free(line); + } + m_free(fingerprint); +} + +void recv_msg_ext_info(void) { + /* This message is not client-specific in the protocol but Dropbear only handles + a server-sent message at present. */ + unsigned int num_ext; + unsigned int i; + + TRACE(("enter recv_msg_ext_info")) + + /* Must be after the first SSH_MSG_NEWKEYS */ + TRACE(("last %d, donefirst %d, donescond %d", ses.lastpacket, ses.kexstate.donefirstkex, ses.kexstate.donesecondkex)) + if (!(ses.lastpacket == SSH_MSG_NEWKEYS && !ses.kexstate.donesecondkex)) { + TRACE(("leave recv_msg_ext_info: ignoring packet received at the wrong time")) + return; + } + + num_ext = buf_getint(ses.payload); + TRACE(("received SSH_MSG_EXT_INFO with %d items", num_ext)) + + for (i = 0; i < num_ext; i++) { + unsigned int name_len; + char *ext_name = buf_getstring(ses.payload, &name_len); + TRACE(("extension %d name '%s'", i, ext_name)) + if (cli_ses.server_sig_algs == NULL + && name_len == strlen(SSH_SERVER_SIG_ALGS) + && strcmp(ext_name, SSH_SERVER_SIG_ALGS) == 0) { + cli_ses.server_sig_algs = buf_getbuf(ses.payload); + } else { + /* valid extension values could be >MAX_STRING_LEN */ + buf_eatstring(ses.payload); + } + m_free(ext_name); + } + TRACE(("leave recv_msg_ext_info")) +} diff --git a/src/cli-main.c b/src/cli-main.c new file mode 100644 index 0000000..065fd76 --- /dev/null +++ b/src/cli-main.c @@ -0,0 +1,155 @@ +/* + * Dropbear - a SSH2 server + * SSH client implementation + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2004 by Mihnea Stoenescu + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "runopts.h" +#include "session.h" +#include "dbrandom.h" +#include "crypto_desc.h" +#include "netio.h" +#include "fuzz.h" + +#if DROPBEAR_CLI_PROXYCMD +static void cli_proxy_cmd(int *sock_in, int *sock_out, pid_t *pid_out); +static void kill_proxy_sighandler(int signo); +#endif + +#if defined(DBMULTI_dbclient) || !DROPBEAR_MULTI +#if defined(DBMULTI_dbclient) && DROPBEAR_MULTI +int cli_main(int argc, char ** argv) { +#else +int main(int argc, char ** argv) { +#endif + + int sock_in, sock_out; + struct dropbear_progress_connection *progress = NULL; + pid_t proxy_cmd_pid = 0; + + _dropbear_exit = cli_dropbear_exit; + _dropbear_log = cli_dropbear_log; + + disallow_core(); + + seedrandom(); + crypto_init(); + + cli_getopts(argc, argv); + +#ifndef DISABLE_SYSLOG + if (opts.usingsyslog) { + startsyslog("dbclient"); + } +#endif + + if (cli_opts.bind_address) { + DEBUG1(("connect to: user=%s host=%s/%s bind_address=%s:%s", cli_opts.username, + cli_opts.remotehost, cli_opts.remoteport, cli_opts.bind_address, cli_opts.bind_port)) + } else { + DEBUG1(("connect to: user=%s host=%s/%s",cli_opts.username,cli_opts.remotehost,cli_opts.remoteport)) + } + + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { + dropbear_exit("signal() error"); + } + +#if DROPBEAR_CLI_PROXYCMD + if (cli_opts.proxycmd) { + cli_proxy_cmd(&sock_in, &sock_out, &proxy_cmd_pid); + m_free(cli_opts.proxycmd); + if (signal(SIGINT, kill_proxy_sighandler) == SIG_ERR || + signal(SIGTERM, kill_proxy_sighandler) == SIG_ERR || + signal(SIGHUP, kill_proxy_sighandler) == SIG_ERR) { + dropbear_exit("signal() error"); + } + } else +#endif + { + progress = connect_remote(cli_opts.remotehost, cli_opts.remoteport, + cli_connected, &ses, cli_opts.bind_address, cli_opts.bind_port, + DROPBEAR_PRIO_LOWDELAY); + sock_in = sock_out = -1; + } + + cli_session(sock_in, sock_out, progress, proxy_cmd_pid); + + /* not reached */ + return -1; +} +#endif /* DBMULTI stuff */ + +static void exec_proxy_cmd(const void *user_data_cmd) { + const char *cmd = user_data_cmd; + char *usershell; + + usershell = m_strdup(get_user_shell()); + run_shell_command(cmd, ses.maxfd, usershell); + dropbear_exit("Failed to run '%s'\n", cmd); +} + +#if DROPBEAR_CLI_PROXYCMD +static void cli_proxy_cmd(int *sock_in, int *sock_out, pid_t *pid_out) { + char * ex_cmd = NULL; + size_t ex_cmdlen; + int ret; + + /* File descriptor "-j &3" */ + if (*cli_opts.proxycmd == '&') { + char *p = cli_opts.proxycmd + 1; + int sock = strtoul(p, &p, 10); + /* must be a single number, and not stdin/stdout/stderr */ + if (sock > 2 && sock < 1024 && *p == '\0') { + *sock_in = sock; + *sock_out = sock; + return; + } + } + + /* Normal proxycommand */ + + /* So that spawn_command knows which shell to run */ + fill_passwd(cli_opts.own_user); + + ex_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */ + ex_cmd = m_malloc(ex_cmdlen); + snprintf(ex_cmd, ex_cmdlen, "exec %s", cli_opts.proxycmd); + + ret = spawn_command(exec_proxy_cmd, ex_cmd, + sock_out, sock_in, NULL, pid_out); + DEBUG1(("cmd: %s pid=%d", ex_cmd,*pid_out)) + m_free(ex_cmd); + if (ret == DROPBEAR_FAILURE) { + dropbear_exit("Failed running proxy command"); + *sock_in = *sock_out = -1; + } +} + +static void kill_proxy_sighandler(int UNUSED(signo)) { + kill_proxy_command(); + _exit(1); +} + +#endif /* DROPBEAR_CLI_PROXYCMD */ diff --git a/src/cli-runopts.c b/src/cli-runopts.c new file mode 100644 index 0000000..38a73f7 --- /dev/null +++ b/src/cli-runopts.c @@ -0,0 +1,929 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "runopts.h" +#include "signkey.h" +#include "buffer.h" +#include "dbutil.h" +#include "algo.h" +#include "tcpfwd.h" +#include "list.h" + +cli_runopts cli_opts; /* GLOBAL */ + +static void printhelp(void); +static void parse_hostname(const char* orighostarg); +static void parse_multihop_hostname(const char* orighostarg, const char* argv0); +static void fill_own_user(void); +#if DROPBEAR_CLI_PUBKEY_AUTH +static void loadidentityfile(const char* filename, int warnfail); +#endif +#if DROPBEAR_CLI_ANYTCPFWD +static void addforward(const char* str, m_list *fwdlist); +#endif +#if DROPBEAR_CLI_NETCAT +static void add_netcat(const char *str); +#endif +static void add_extendedopt(const char *str); + +static void printhelp() { + + fprintf(stderr, "Dropbear SSH client v%s https://matt.ucc.asn.au/dropbear/dropbear.html\n" +#if DROPBEAR_CLI_MULTIHOP + "Usage: %s [options] [user@]host[/port][,[user@]host/port],...] [command]\n" +#else + "Usage: %s [options] [user@]host[/port] [command]\n" +#endif + "-p <remoteport>\n" + "-l <username>\n" + "-t Allocate a pty\n" + "-T Don't allocate a pty\n" + "-N Don't run a remote command\n" + "-f Run in background after auth\n" + "-q quiet, don't show remote banner\n" + "-y Always accept remote host key if unknown\n" + "-y -y Don't perform any remote host key checking (caution)\n" + "-s Request a subsystem (use by external sftp)\n" + "-o option Set option in OpenSSH-like format ('-o help' to list options)\n" +#if DROPBEAR_CLI_PUBKEY_AUTH + "-i <identityfile> (multiple allowed, default %s)\n" +#endif +#if DROPBEAR_CLI_AGENTFWD + "-A Enable agent auth forwarding\n" +#endif +#if DROPBEAR_CLI_LOCALTCPFWD + "-L <[listenaddress:]listenport:remotehost:remoteport> Local port forwarding\n" + "-g Allow remote hosts to connect to forwarded ports\n" +#endif +#if DROPBEAR_CLI_REMOTETCPFWD + "-R <[listenaddress:]listenport:remotehost:remoteport> Remote port forwarding\n" +#endif + "-W <receive_window_buffer> (default %d, larger may be faster, max 10MB)\n" + "-K <keepalive> (0 is never, default %d)\n" + "-I <idle_timeout> (0 is never, default %d)\n" + "-z disable QoS\n" +#if DROPBEAR_CLI_NETCAT + "-B <endhost:endport> Netcat-alike forwarding\n" +#endif +#if DROPBEAR_CLI_PROXYCMD + "-J <proxy_program> Use program pipe rather than TCP connection\n" +#endif +#if DROPBEAR_USER_ALGO_LIST + "-c <cipher list> Specify preferred ciphers ('-c help' to list options)\n" + "-m <MAC list> Specify preferred MACs for packet verification (or '-m help')\n" +#endif + "-b [bind_address][:bind_port]\n" + "-V Version\n" +#if DEBUG_TRACE + "-v verbose (repeat for more verbose)\n" +#endif + ,DROPBEAR_VERSION, cli_opts.progname, +#if DROPBEAR_CLI_PUBKEY_AUTH + DROPBEAR_DEFAULT_CLI_AUTHKEY, +#endif + DEFAULT_RECV_WINDOW, DEFAULT_KEEPALIVE, DEFAULT_IDLE_TIMEOUT); + +} + +void cli_getopts(int argc, char ** argv) { + unsigned int i, j; + char ** next = NULL; + enum { + OPT_EXTENDED_OPTIONS, +#if DROPBEAR_CLI_PUBKEY_AUTH + OPT_AUTHKEY, +#endif +#if DROPBEAR_CLI_LOCALTCPFWD + OPT_LOCALTCPFWD, +#endif +#if DROPBEAR_CLI_REMOTETCPFWD + OPT_REMOTETCPFWD, +#endif +#if DROPBEAR_CLI_NETCAT + OPT_NETCAT, +#endif + /* a flag (no arg) if 'next' is NULL, a string-valued option otherwise */ + OPT_OTHER + } opt; + unsigned int cmdlen; + + char* recv_window_arg = NULL; + char* keepalive_arg = NULL; + char* idle_timeout_arg = NULL; + char *host_arg = NULL; + char *bind_arg = NULL; + char c; + + /* see printhelp() for options */ + cli_opts.progname = argv[0]; + cli_opts.remotehost = NULL; + cli_opts.remoteport = NULL; + cli_opts.username = NULL; + cli_opts.cmd = NULL; + cli_opts.no_cmd = 0; + cli_opts.quiet = 0; + cli_opts.backgrounded = 0; + cli_opts.wantpty = 9; /* 9 means "it hasn't been touched", gets set later */ + cli_opts.always_accept_key = 0; + cli_opts.no_hostkey_check = 0; + cli_opts.is_subsystem = 0; +#if DROPBEAR_CLI_PUBKEY_AUTH + cli_opts.privkeys = list_new(); +#endif +#if DROPBEAR_CLI_ANYTCPFWD + cli_opts.exit_on_fwd_failure = 0; +#endif + cli_opts.disable_trivial_auth = 0; +#if DROPBEAR_CLI_LOCALTCPFWD + cli_opts.localfwds = list_new(); + opts.listen_fwd_all = 0; +#endif +#if DROPBEAR_CLI_REMOTETCPFWD + cli_opts.remotefwds = list_new(); +#endif +#if DROPBEAR_CLI_AGENTFWD + cli_opts.agent_fwd = 0; + cli_opts.agent_fd = -1; + cli_opts.agent_keys_loaded = 0; +#endif +#if DROPBEAR_CLI_PROXYCMD + cli_opts.proxycmd = NULL; +#endif + cli_opts.bind_address = NULL; + cli_opts.bind_port = NULL; +#ifndef DISABLE_ZLIB + opts.compress_mode = DROPBEAR_COMPRESS_ON; +#endif +#if DROPBEAR_USER_ALGO_LIST + opts.cipher_list = NULL; + opts.mac_list = NULL; +#endif +#ifndef DISABLE_SYSLOG + opts.usingsyslog = 0; +#endif + /* not yet + opts.ipv4 = 1; + opts.ipv6 = 1; + */ + opts.recv_window = DEFAULT_RECV_WINDOW; + opts.keepalive_secs = DEFAULT_KEEPALIVE; + opts.idle_timeout_secs = DEFAULT_IDLE_TIMEOUT; + + fill_own_user(); + + for (i = 1; i < (unsigned int)argc; i++) { + /* Handle non-flag arguments such as hostname or commands for the remote host */ + if (argv[i][0] != '-') + { + if (host_arg == NULL) { + host_arg = argv[i]; + continue; + } + /* Commands to pass to the remote host. No more flag handling, + commands are consumed below */ + break; + } + + /* Begins with '-' */ + opt = OPT_OTHER; + for (j = 1; (c = argv[i][j]) != '\0' && !next && opt == OPT_OTHER; j++) { + switch (c) { + case 'y': /* always accept the remote hostkey */ + if (cli_opts.always_accept_key) { + /* twice means no checking at all */ + cli_opts.no_hostkey_check = 1; + } + cli_opts.always_accept_key = 1; + break; + case 'q': /* quiet */ + cli_opts.quiet = 1; + break; + case 'p': /* remoteport */ + next = (char**)&cli_opts.remoteport; + break; +#if DROPBEAR_CLI_PUBKEY_AUTH + case 'i': /* an identityfile */ + opt = OPT_AUTHKEY; + break; +#endif + case 't': /* we want a pty */ + cli_opts.wantpty = 1; + break; + case 'T': /* don't want a pty */ + cli_opts.wantpty = 0; + break; + case 'N': + cli_opts.no_cmd = 1; + break; + case 'f': + cli_opts.backgrounded = 1; + break; + case 's': + cli_opts.is_subsystem = 1; + break; + case 'o': + opt = OPT_EXTENDED_OPTIONS; + break; +#if DROPBEAR_CLI_LOCALTCPFWD + case 'L': + opt = OPT_LOCALTCPFWD; + break; + case 'g': + opts.listen_fwd_all = 1; + break; +#endif +#if DROPBEAR_CLI_REMOTETCPFWD + case 'R': + opt = OPT_REMOTETCPFWD; + break; +#endif +#if DROPBEAR_CLI_NETCAT + case 'B': + opt = OPT_NETCAT; + break; +#endif +#if DROPBEAR_CLI_PROXYCMD + case 'J': + next = &cli_opts.proxycmd; + break; +#endif + case 'l': + next = &cli_opts.username; + break; + case 'h': + printhelp(); + exit(EXIT_SUCCESS); + break; + case 'u': + /* backwards compatibility with old urandom option */ + break; + case 'W': + next = &recv_window_arg; + break; + case 'K': + next = &keepalive_arg; + break; + case 'I': + next = &idle_timeout_arg; + break; +#if DROPBEAR_CLI_AGENTFWD + case 'A': + cli_opts.agent_fwd = 1; + break; +#endif +#if DROPBEAR_USER_ALGO_LIST + case 'c': + next = &opts.cipher_list; + break; + case 'm': + next = &opts.mac_list; + break; +#endif +#if DEBUG_TRACE + case 'v': + debug_trace++; + break; +#endif + case 'F': + case 'e': +#if !DROPBEAR_USER_ALGO_LIST + case 'c': + case 'm': +#endif + case 'D': +#if !DROPBEAR_CLI_REMOTETCPFWD + case 'R': +#endif +#if !DROPBEAR_CLI_LOCALTCPFWD + case 'L': +#endif + case 'V': + print_version(); + exit(EXIT_SUCCESS); + break; + case 'b': + next = &bind_arg; + break; + case 'z': + opts.disable_ip_tos = 1; + break; + default: + fprintf(stderr, + "WARNING: Ignoring unknown option -%c\n", c); + break; + } /* Switch */ + } + + if (!next && opt == OPT_OTHER) /* got a flag */ + continue; + + if (c == '\0') { + i++; + j = 0; + if (!argv[i]) + dropbear_exit("Missing argument"); + } + + if (opt == OPT_EXTENDED_OPTIONS) { + TRACE(("opt extended")) + add_extendedopt(&argv[i][j]); + } + else +#if DROPBEAR_CLI_PUBKEY_AUTH + if (opt == OPT_AUTHKEY) { + TRACE(("opt authkey")) + loadidentityfile(&argv[i][j], 1); + } + else +#endif +#if DROPBEAR_CLI_REMOTETCPFWD + if (opt == OPT_REMOTETCPFWD) { + TRACE(("opt remotetcpfwd")) + addforward(&argv[i][j], cli_opts.remotefwds); + } + else +#endif +#if DROPBEAR_CLI_LOCALTCPFWD + if (opt == OPT_LOCALTCPFWD) { + TRACE(("opt localtcpfwd")) + addforward(&argv[i][j], cli_opts.localfwds); + } + else +#endif +#if DROPBEAR_CLI_NETCAT + if (opt == OPT_NETCAT) { + TRACE(("opt netcat")) + add_netcat(&argv[i][j]); + } + else +#endif + if (next) { + /* The previous flag set a value to assign */ + *next = &argv[i][j]; + if (*next == NULL) + dropbear_exit("Invalid null argument"); + next = NULL; + } + } + +#if DROPBEAR_USER_ALGO_LIST + /* -c help doesn't need a hostname */ + parse_ciphers_macs(); +#endif + + /* Done with options/flags; now handle the hostname (which may not + * start with a hyphen) and optional command */ + + if (host_arg == NULL) { /* missing hostname */ + printhelp(); + exit(EXIT_FAILURE); + } + TRACE(("host is: %s", host_arg)) + + if (i < (unsigned int)argc) { + /* Build the command to send */ + cmdlen = 0; + for (j = i; j < (unsigned int)argc; j++) + cmdlen += strlen(argv[j]) + 1; /* +1 for spaces */ + + /* Allocate the space */ + cli_opts.cmd = (char*)m_malloc(cmdlen); + cli_opts.cmd[0] = '\0'; + + /* Append all the bits */ + for (j = i; j < (unsigned int)argc; j++) { + strlcat(cli_opts.cmd, argv[j], cmdlen); + strlcat(cli_opts.cmd, " ", cmdlen); + } + /* It'll be null-terminated here */ + TRACE(("cmd is: %s", cli_opts.cmd)) + } + + /* And now a few sanity checks and setup */ + +#if DROPBEAR_CLI_PROXYCMD + if (cli_opts.proxycmd) { + /* To match the common path of m_freeing it */ + cli_opts.proxycmd = m_strdup(cli_opts.proxycmd); + } +#endif + + if (cli_opts.remoteport == NULL) { + cli_opts.remoteport = "22"; + } + + if (bind_arg) { + if (split_address_port(bind_arg, + &cli_opts.bind_address, &cli_opts.bind_port) + == DROPBEAR_FAILURE) { + dropbear_exit("Bad -b argument"); + } + } + + /* If not explicitly specified with -t or -T, we don't want a pty if + * there's a command, but we do otherwise */ + if (cli_opts.wantpty == 9) { + if (cli_opts.cmd == NULL) { + cli_opts.wantpty = 1; + } else { + cli_opts.wantpty = 0; + } + } + + if (cli_opts.backgrounded && cli_opts.cmd == NULL + && cli_opts.no_cmd == 0) { + dropbear_exit("Command required for -f"); + } + + if (recv_window_arg) { + parse_recv_window(recv_window_arg); + } + if (keepalive_arg) { + unsigned int val; + if (m_str_to_uint(keepalive_arg, &val) == DROPBEAR_FAILURE) { + dropbear_exit("Bad keepalive '%s'", keepalive_arg); + } + opts.keepalive_secs = val; + } + + if (idle_timeout_arg) { + unsigned int val; + if (m_str_to_uint(idle_timeout_arg, &val) == DROPBEAR_FAILURE) { + dropbear_exit("Bad idle_timeout '%s'", idle_timeout_arg); + } + opts.idle_timeout_secs = val; + } + +#if DROPBEAR_CLI_NETCAT + if (cli_opts.cmd && cli_opts.netcat_host) { + dropbear_log(LOG_INFO, "Ignoring command '%s' in netcat mode", cli_opts.cmd); + } +#endif + + /* The hostname gets set up last, since + * in multi-hop mode it will require knowledge + * of other flags such as -i */ +#if DROPBEAR_CLI_MULTIHOP + parse_multihop_hostname(host_arg, argv[0]); +#else + parse_hostname(host_arg); +#endif + + /* We don't want to include default id_dropbear as a + -i argument for multihop, so handle it later. */ +#if (DROPBEAR_CLI_PUBKEY_AUTH) + { + char *expand_path = expand_homedir_path(DROPBEAR_DEFAULT_CLI_AUTHKEY); + loadidentityfile(expand_path, 0); + m_free(expand_path); + } +#endif + +} + +#if DROPBEAR_CLI_PUBKEY_AUTH +static void loadidentityfile(const char* filename, int warnfail) { + sign_key *key; + enum signkey_type keytype; + + TRACE(("loadidentityfile %s", filename)) + + key = new_sign_key(); + keytype = DROPBEAR_SIGNKEY_ANY; + if ( readhostkey(filename, key, &keytype) != DROPBEAR_SUCCESS ) { + if (warnfail) { + dropbear_log(LOG_WARNING, "Failed loading keyfile '%s'\n", filename); + } + sign_key_free(key); + } else { + key->type = keytype; + key->source = SIGNKEY_SOURCE_RAW_FILE; + key->filename = m_strdup(filename); + list_append(cli_opts.privkeys, key); + } +} +#endif + +#if DROPBEAR_CLI_MULTIHOP + +static char* +multihop_passthrough_args() { + char *ret; + unsigned int len, total; + m_list_elem *iter; + /* Fill out -i, -y, -W options that make sense for all + * the intermediate processes */ + len = 30; /* space for "-q -y -y -W <size>\0" */ +#if DROPBEAR_CLI_PUBKEY_AUTH + for (iter = cli_opts.privkeys->first; iter; iter = iter->next) + { + sign_key * key = (sign_key*)iter->item; + len += 3 + strlen(key->filename); + } +#endif /* DROPBEAR_CLI_PUBKEY_AUTH */ + if (cli_opts.proxycmd) { + /* "-J 'cmd'" */ + len += 6 + strlen(cli_opts.proxycmd); + } + + ret = m_malloc(len); + total = 0; + + if (cli_opts.quiet) { + total += m_snprintf(ret+total, len-total, "-q "); + } + + if (cli_opts.no_hostkey_check) { + total += m_snprintf(ret+total, len-total, "-y -y "); + } else if (cli_opts.always_accept_key) { + total += m_snprintf(ret+total, len-total, "-y "); + } + + if (cli_opts.proxycmd) { + total += m_snprintf(ret+total, len-total, "-J '%s' ", cli_opts.proxycmd); + } + + if (opts.recv_window != DEFAULT_RECV_WINDOW) { + total += m_snprintf(ret+total, len-total, "-W %u ", opts.recv_window); + } + +#if DROPBEAR_CLI_PUBKEY_AUTH + for (iter = cli_opts.privkeys->first; iter; iter = iter->next) + { + sign_key * key = (sign_key*)iter->item; + total += m_snprintf(ret+total, len-total, "-i %s ", key->filename); + } +#endif /* DROPBEAR_CLI_PUBKEY_AUTH */ + + return ret; +} + +/* Sets up 'onion-forwarding' connections. This will spawn + * a separate dbclient process for each hop. + * As an example, if the cmdline is + * dbclient wrt,madako,canyons + * then we want to run: + * dbclient -J "dbclient -B canyons:22 wrt,madako" canyons + * and then the inner dbclient will recursively run: + * dbclient -J "dbclient -B madako:22 wrt" madako + * etc for as many hosts as we want. + * + * Note that "-J" arguments aren't actually used, instead + * below sets cli_opts.proxycmd directly. + * + * Ports for hosts can be specified as host/port. + */ +static void parse_multihop_hostname(const char* orighostarg, const char* argv0) { + char *userhostarg = NULL; + char *hostbuf = NULL; + char *last_hop = NULL; + char *remainder = NULL; + + /* both scp and rsync parse a user@host argument + * and turn it into "-l user host". This breaks + * for our multihop syntax, so we suture it back together. + * This will break usernames that have both '@' and ',' in them, + * though that should be fairly uncommon. */ + if (cli_opts.username + && strchr(cli_opts.username, ',') + && strchr(cli_opts.username, '@')) { + unsigned int len = strlen(orighostarg) + strlen(cli_opts.username) + 2; + hostbuf = m_malloc(len); + m_snprintf(hostbuf, len, "%s@%s", cli_opts.username, orighostarg); + } else { + hostbuf = m_strdup(orighostarg); + } + userhostarg = hostbuf; + + last_hop = strrchr(userhostarg, ','); + if (last_hop) { + if (last_hop == userhostarg) { + dropbear_exit("Bad multi-hop hostnames"); + } + *last_hop = '\0'; + last_hop++; + remainder = userhostarg; + userhostarg = last_hop; + } + + parse_hostname(userhostarg); + + if (last_hop) { + /* Set up the proxycmd */ + unsigned int cmd_len = 0; + char *passthrough_args = multihop_passthrough_args(); + if (cli_opts.remoteport == NULL) { + cli_opts.remoteport = "22"; + } + cmd_len = strlen(argv0) + strlen(remainder) + + strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) + + strlen(passthrough_args) + + 30; + /* replace proxycmd. old -J arguments have been copied + to passthrough_args */ + cli_opts.proxycmd = m_realloc(cli_opts.proxycmd, cmd_len); + m_snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s", + argv0, cli_opts.remotehost, cli_opts.remoteport, + passthrough_args, remainder); +#ifndef DISABLE_ZLIB + /* The stream will be incompressible since it's encrypted. */ + opts.compress_mode = DROPBEAR_COMPRESS_OFF; +#endif + m_free(passthrough_args); + } + m_free(hostbuf); +} +#endif /* !DROPBEAR_CLI_MULTIHOP */ + +/* Parses a [user@]hostname[/port] argument. */ +static void parse_hostname(const char* orighostarg) { + char *userhostarg = NULL; + char *port = NULL; + + userhostarg = m_strdup(orighostarg); + + cli_opts.remotehost = strchr(userhostarg, '@'); + if (cli_opts.remotehost == NULL) { + /* no username portion, the cli-auth.c code can figure the + * local user's name */ + cli_opts.remotehost = userhostarg; + } else { + cli_opts.remotehost[0] = '\0'; /* Split the user/host */ + cli_opts.remotehost++; + cli_opts.username = userhostarg; + } + + if (cli_opts.username == NULL) { + cli_opts.username = m_strdup(cli_opts.own_user); + } + + port = strchr(cli_opts.remotehost, '^'); + if (!port) { + /* legacy separator */ + port = strchr(cli_opts.remotehost, '/'); + } + if (port) { + *port = '\0'; + cli_opts.remoteport = port+1; + } + + if (cli_opts.remotehost[0] == '\0') { + dropbear_exit("Bad hostname"); + } +} + +#if DROPBEAR_CLI_NETCAT +static void add_netcat(const char* origstr) { + char *portstr = NULL; + + char * str = m_strdup(origstr); + + portstr = strchr(str, ':'); + if (portstr == NULL) { + TRACE(("No netcat port")) + goto fail; + } + *portstr = '\0'; + portstr++; + + if (strchr(portstr, ':')) { + TRACE(("Multiple netcat colons")) + goto fail; + } + + if (m_str_to_uint(portstr, &cli_opts.netcat_port) == DROPBEAR_FAILURE) { + TRACE(("bad netcat port")) + goto fail; + } + + if (cli_opts.netcat_port > 65535) { + TRACE(("too large netcat port")) + goto fail; + } + + cli_opts.netcat_host = str; + return; + +fail: + dropbear_exit("Bad netcat endpoint '%s'", origstr); +} +#endif + +static void fill_own_user() { + uid_t uid; + struct passwd *pw = NULL; + + uid = getuid(); + + pw = getpwuid(uid); + if (pw && pw->pw_name != NULL) { + cli_opts.own_user = m_strdup(pw->pw_name); + } else { + dropbear_log(LOG_INFO, "Warning: failed to identify current user. Trying anyway."); + cli_opts.own_user = m_strdup("unknown"); + } + +} + +#if DROPBEAR_CLI_ANYTCPFWD +/* Turn a "[listenaddr:]listenport:remoteaddr:remoteport" string into into a forwarding + * set, and add it to the forwarding list */ +static void addforward(const char* origstr, m_list *fwdlist) { + + char *part1 = NULL, *part2 = NULL, *part3 = NULL, *part4 = NULL; + char * listenaddr = NULL; + char * listenport = NULL; + char * connectaddr = NULL; + char * connectport = NULL; + struct TCPFwdEntry* newfwd = NULL; + char * str = NULL; + + TRACE(("enter addforward")) + + /* We need to split the original argument up. This var + is never free()d. */ + str = m_strdup(origstr); + + part1 = str; + + part2 = strchr(str, ':'); + if (part2 == NULL) { + TRACE(("part2 == NULL")) + goto fail; + } + *part2 = '\0'; + part2++; + + part3 = strchr(part2, ':'); + if (part3 == NULL) { + TRACE(("part3 == NULL")) + goto fail; + } + *part3 = '\0'; + part3++; + + part4 = strchr(part3, ':'); + if (part4) { + *part4 = '\0'; + part4++; + } + + if (part4) { + listenaddr = part1; + listenport = part2; + connectaddr = part3; + connectport = part4; + } else { + listenaddr = NULL; + listenport = part1; + connectaddr = part2; + connectport = part3; + } + + newfwd = m_malloc(sizeof(struct TCPFwdEntry)); + + /* Now we check the ports - note that the port ints are unsigned, + * the check later only checks for >= MAX_PORT */ + if (m_str_to_uint(listenport, &newfwd->listenport) == DROPBEAR_FAILURE) { + TRACE(("bad listenport strtoul")) + goto fail; + } + + if (m_str_to_uint(connectport, &newfwd->connectport) == DROPBEAR_FAILURE) { + TRACE(("bad connectport strtoul")) + goto fail; + } + + newfwd->listenaddr = listenaddr; + newfwd->connectaddr = connectaddr; + + if (newfwd->listenport > 65535) { + TRACE(("listenport > 65535")) + goto badport; + } + + if (newfwd->connectport > 65535) { + TRACE(("connectport > 65535")) + goto badport; + } + + newfwd->have_reply = 0; + list_append(fwdlist, newfwd); + + TRACE(("leave addforward: done")) + return; + +fail: + dropbear_exit("Bad TCP forward '%s'", origstr); + +badport: + dropbear_exit("Bad TCP port in '%s'", origstr); +} +#endif + +static int match_extendedopt(const char** strptr, const char *optname) { + int seen_eq = 0; + int optlen = strlen(optname); + const char *str = *strptr; + + while (isspace(*str)) { + ++str; + } + + if (strncasecmp(str, optname, optlen) != 0) { + return DROPBEAR_FAILURE; + } + + str += optlen; + + while (isspace(*str) || (!seen_eq && *str == '=')) { + if (*str == '=') { + seen_eq = 1; + } + ++str; + } + + if (str-*strptr == optlen) { + /* matched just a prefix of optname */ + return DROPBEAR_FAILURE; + } + + *strptr = str; + return DROPBEAR_SUCCESS; +} + +static int parse_flag_value(const char *value) { + if (strcmp(value, "yes") == 0 || strcmp(value, "true") == 0) { + return 1; + } else if (strcmp(value, "no") == 0 || strcmp(value, "false") == 0) { + return 0; + } + + dropbear_exit("Bad yes/no argument '%s'", value); +} + +static void add_extendedopt(const char* origstr) { + const char *optstr = origstr; + + if (strcmp(origstr, "help") == 0) { + dropbear_log(LOG_INFO, "Available options:\n" +#if DROPBEAR_CLI_ANYTCPFWD + "\tExitOnForwardFailure\n" +#endif + "\tDisableTrivialAuth\n" +#ifndef DISABLE_SYSLOG + "\tUseSyslog\n" +#endif + "\tPort\n" + ); + exit(EXIT_SUCCESS); + } + +#if DROPBEAR_CLI_ANYTCPFWD + if (match_extendedopt(&optstr, "ExitOnForwardFailure") == DROPBEAR_SUCCESS) { + cli_opts.exit_on_fwd_failure = parse_flag_value(optstr); + return; + } +#endif + +#ifndef DISABLE_SYSLOG + if (match_extendedopt(&optstr, "UseSyslog") == DROPBEAR_SUCCESS) { + opts.usingsyslog = parse_flag_value(optstr); + return; + } +#endif + + if (match_extendedopt(&optstr, "Port") == DROPBEAR_SUCCESS) { + cli_opts.remoteport = optstr; + return; + } + + if (match_extendedopt(&optstr, "DisableTrivialAuth") == DROPBEAR_SUCCESS) { + cli_opts.disable_trivial_auth = parse_flag_value(optstr); + return; + } + + dropbear_log(LOG_WARNING, "Ignoring unknown configuration option '%s'", origstr); +} diff --git a/src/cli-session.c b/src/cli-session.c new file mode 100644 index 0000000..5981b24 --- /dev/null +++ b/src/cli-session.c @@ -0,0 +1,489 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2004 by Mihnea Stoenescu + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "session.h" +#include "dbutil.h" +#include "kex.h" +#include "ssh.h" +#include "packet.h" +#include "tcpfwd.h" +#include "channel.h" +#include "dbrandom.h" +#include "service.h" +#include "runopts.h" +#include "chansession.h" +#include "agentfwd.h" +#include "crypto_desc.h" +#include "netio.h" + +static void cli_remoteclosed(void) ATTRIB_NORETURN; +static void cli_sessionloop(void); +static void cli_session_init(pid_t proxy_cmd_pid); +static void cli_finished(void) ATTRIB_NORETURN; +static void recv_msg_service_accept(void); +static void cli_session_cleanup(void); +static void recv_msg_global_request_cli(void); + +struct clientsession cli_ses; /* GLOBAL */ + +/* Sorted in decreasing frequency will be more efficient - data and window + * should be first */ +static const packettype cli_packettypes[] = { + /* TYPE, FUNCTION */ + {SSH_MSG_CHANNEL_DATA, recv_msg_channel_data}, + {SSH_MSG_CHANNEL_EXTENDED_DATA, recv_msg_channel_extended_data}, + {SSH_MSG_CHANNEL_WINDOW_ADJUST, recv_msg_channel_window_adjust}, + {SSH_MSG_USERAUTH_FAILURE, recv_msg_userauth_failure}, /* client */ + {SSH_MSG_USERAUTH_SUCCESS, recv_msg_userauth_success}, /* client */ + {SSH_MSG_KEXINIT, recv_msg_kexinit}, + {SSH_MSG_KEXDH_REPLY, recv_msg_kexdh_reply}, /* client */ + {SSH_MSG_NEWKEYS, recv_msg_newkeys}, + {SSH_MSG_SERVICE_ACCEPT, recv_msg_service_accept}, /* client */ + {SSH_MSG_CHANNEL_REQUEST, recv_msg_channel_request}, + {SSH_MSG_CHANNEL_OPEN, recv_msg_channel_open}, + {SSH_MSG_CHANNEL_EOF, recv_msg_channel_eof}, + {SSH_MSG_CHANNEL_CLOSE, recv_msg_channel_close}, + {SSH_MSG_CHANNEL_OPEN_CONFIRMATION, recv_msg_channel_open_confirmation}, + {SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure}, + {SSH_MSG_USERAUTH_BANNER, recv_msg_userauth_banner}, /* client */ + {SSH_MSG_USERAUTH_SPECIFIC_60, recv_msg_userauth_specific_60}, /* client */ + {SSH_MSG_GLOBAL_REQUEST, recv_msg_global_request_cli}, + {SSH_MSG_CHANNEL_SUCCESS, ignore_recv_response}, + {SSH_MSG_CHANNEL_FAILURE, ignore_recv_response}, +#if DROPBEAR_CLI_REMOTETCPFWD + {SSH_MSG_REQUEST_SUCCESS, cli_recv_msg_request_success}, /* client */ + {SSH_MSG_REQUEST_FAILURE, cli_recv_msg_request_failure}, /* client */ +#else + /* For keepalive */ + {SSH_MSG_REQUEST_SUCCESS, ignore_recv_response}, + {SSH_MSG_REQUEST_FAILURE, ignore_recv_response}, +#endif + {SSH_MSG_EXT_INFO, recv_msg_ext_info}, + {0, NULL} /* End */ +}; + +static const struct ChanType *cli_chantypes[] = { +#if DROPBEAR_CLI_REMOTETCPFWD + &cli_chan_tcpremote, +#endif +#if DROPBEAR_CLI_AGENTFWD + &cli_chan_agent, +#endif + NULL /* Null termination */ +}; + +void cli_connected(int result, int sock, void* userdata, const char *errstring) +{ + struct sshsession *myses = userdata; + if (result == DROPBEAR_FAILURE) { + dropbear_exit("Connect failed: %s", errstring); + } + myses->sock_in = myses->sock_out = sock; + DEBUG1(("cli_connected")) + ses.socket_prio = DROPBEAR_PRIO_NORMAL; + /* switches to lowdelay */ + update_channel_prio(); +} + +void cli_session(int sock_in, int sock_out, struct dropbear_progress_connection *progress, pid_t proxy_cmd_pid) { + + common_session_init(sock_in, sock_out); + + if (progress) { + connect_set_writequeue(progress, &ses.writequeue); + } + + chaninitialise(cli_chantypes); + + /* Set up cli_ses vars */ + cli_session_init(proxy_cmd_pid); + + /* Ready to go */ + ses.init_done = 1; + + /* Exchange identification */ + send_session_identification(); + + kexfirstinitialise(); /* initialise the kex state */ + + send_msg_kexinit(); + + session_loop(cli_sessionloop); + + /* Not reached */ + +} + +#if DROPBEAR_KEX_FIRST_FOLLOWS +static void cli_send_kex_first_guess() { + send_msg_kexdh_init(); +} +#endif + +static void cli_session_init(pid_t proxy_cmd_pid) { + + cli_ses.state = STATE_NOTHING; + cli_ses.kex_state = KEX_NOTHING; + + cli_ses.tty_raw_mode = 0; + cli_ses.winchange = 0; + + /* We store std{in,out,err}'s flags, so we can set them back on exit + * (otherwise busybox's ash isn't happy */ + cli_ses.stdincopy = dup(STDIN_FILENO); + cli_ses.stdinflags = fcntl(STDIN_FILENO, F_GETFL, 0); + cli_ses.stdoutcopy = dup(STDOUT_FILENO); + cli_ses.stdoutflags = fcntl(STDOUT_FILENO, F_GETFL, 0); + cli_ses.stderrcopy = dup(STDERR_FILENO); + cli_ses.stderrflags = fcntl(STDERR_FILENO, F_GETFL, 0); + + cli_ses.retval = EXIT_SUCCESS; /* Assume it's clean if we don't get a + specific exit status */ + cli_ses.proxy_cmd_pid = proxy_cmd_pid; + TRACE(("proxy command PID='%d'", proxy_cmd_pid)); + + /* Auth */ + cli_ses.lastprivkey = NULL; + cli_ses.lastauthtype = 0; + cli_ses.is_trivial_auth = 1; + + /* For printing "remote host closed" for the user */ + ses.remoteclosed = cli_remoteclosed; + + ses.extra_session_cleanup = cli_session_cleanup; + + /* packet handlers */ + ses.packettypes = cli_packettypes; + + ses.isserver = 0; + +#if DROPBEAR_KEX_FIRST_FOLLOWS + ses.send_kex_first_guess = cli_send_kex_first_guess; +#endif + +} + +static void send_msg_service_request(const char* servicename) { + + TRACE(("enter send_msg_service_request: servicename='%s'", servicename)) + + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_SERVICE_REQUEST); + buf_putstring(ses.writepayload, servicename, strlen(servicename)); + + encrypt_packet(); + TRACE(("leave send_msg_service_request")) +} + +static void recv_msg_service_accept(void) { + /* do nothing, if it failed then the server MUST have disconnected */ +} + +/* This function drives the progress of the session - it initiates KEX, + * service, userauth and channel requests */ +static void cli_sessionloop() { + + TRACE2(("enter cli_sessionloop")) + + if (ses.lastpacket == 0) { + TRACE2(("exit cli_sessionloop: no real packets yet")) + return; + } + + if (ses.lastpacket == SSH_MSG_KEXINIT && cli_ses.kex_state == KEX_NOTHING) { + /* We initiate the KEXDH. If DH wasn't the correct type, the KEXINIT + * negotiation would have failed. */ + if (!ses.kexstate.our_first_follows_matches) { + send_msg_kexdh_init(); + } + cli_ses.kex_state = KEXDH_INIT_SENT; + TRACE(("leave cli_sessionloop: done with KEXINIT_RCVD")) + return; + } + + /* A KEX has finished, so we should go back to our KEX_NOTHING state */ + if (cli_ses.kex_state != KEX_NOTHING && ses.kexstate.sentnewkeys) { + cli_ses.kex_state = KEX_NOTHING; + } + + /* We shouldn't do anything else if a KEX is in progress */ + if (cli_ses.kex_state != KEX_NOTHING) { + TRACE(("leave cli_sessionloop: kex_state != KEX_NOTHING")) + return; + } + + if (ses.kexstate.donefirstkex == 0) { + /* We might reach here if we have partial packet reads or have + * received SSG_MSG_IGNORE etc. Just skip it */ + TRACE2(("donefirstkex false\n")) + return; + } + + switch (cli_ses.state) { + + case STATE_NOTHING: + /* We've got the transport layer sorted, we now need to request + * userauth */ + send_msg_service_request(SSH_SERVICE_USERAUTH); + /* We aren't using any "implicit server authentication" methods, + so don't need to wait for a response for SSH_SERVICE_USERAUTH + before sending the auth messages (rfc4253 10) */ + cli_auth_getmethods(); + cli_ses.state = USERAUTH_REQ_SENT; + TRACE(("leave cli_sessionloop: sent userauth methods req")) + return; + + case USERAUTH_REQ_SENT: + TRACE(("leave cli_sessionloop: waiting, req_sent")) + return; + + case USERAUTH_FAIL_RCVD: + if (cli_auth_try() == DROPBEAR_FAILURE) { + dropbear_exit("No auth methods could be used."); + } + cli_ses.state = USERAUTH_REQ_SENT; + TRACE(("leave cli_sessionloop: cli_auth_try")) + return; + + case USERAUTH_SUCCESS_RCVD: +#ifndef DISABLE_SYSLOG + if (opts.usingsyslog) { + dropbear_log(LOG_INFO, "Authentication succeeded."); + } +#endif + + if (cli_opts.backgrounded) { + int devnull; + /* keeping stdin open steals input from the terminal and + is confusing, though stdout/stderr could be useful. */ + devnull = open(DROPBEAR_PATH_DEVNULL, O_RDONLY); + if (devnull < 0) { + dropbear_exit("Opening /dev/null: %d %s", + errno, strerror(errno)); + } + dup2(devnull, STDIN_FILENO); + if (daemon(0, 1) < 0) { + dropbear_exit("Backgrounding failed: %d %s", + errno, strerror(errno)); + } + } + +#if DROPBEAR_CLI_NETCAT + if (cli_opts.netcat_host) { + cli_send_netcat_request(); + } else +#endif + if (!cli_opts.no_cmd) { + cli_send_chansess_request(); + } + +#if DROPBEAR_CLI_LOCALTCPFWD + setup_localtcp(); +#endif +#if DROPBEAR_CLI_REMOTETCPFWD + setup_remotetcp(); +#endif + + TRACE(("leave cli_sessionloop: running")) + cli_ses.state = SESSION_RUNNING; + return; + + case SESSION_RUNNING: + if (ses.chancount < 1 && !cli_opts.no_cmd) { + cli_finished(); + } + + if (cli_ses.winchange) { + cli_chansess_winchange(); + } + return; + + /* XXX more here needed */ + + + default: + break; + } + + TRACE2(("leave cli_sessionloop: fell out")) + +} + +void kill_proxy_command(void) { + /* + * Send SIGHUP to proxy command if used. We don't wait() in + * case it hangs and instead rely on init to reap the child + */ + if (cli_ses.proxy_cmd_pid > 1) { + TRACE(("killing proxy command with PID='%d'", cli_ses.proxy_cmd_pid)); + kill(cli_ses.proxy_cmd_pid, SIGHUP); + } +} + +static void cli_session_cleanup(void) { + + if (!ses.init_done) { + return; + } + + kill_proxy_command(); + + /* Set std{in,out,err} back to non-blocking - busybox ash dies nastily if + * we don't revert the flags */ + /* Ignore return value since there's nothing we can do */ + (void)fcntl(cli_ses.stdincopy, F_SETFL, cli_ses.stdinflags); + (void)fcntl(cli_ses.stdoutcopy, F_SETFL, cli_ses.stdoutflags); + (void)fcntl(cli_ses.stderrcopy, F_SETFL, cli_ses.stderrflags); + + /* Don't leak */ + m_close(cli_ses.stdincopy); + m_close(cli_ses.stdoutcopy); + m_close(cli_ses.stderrcopy); + + cli_tty_cleanup(); + if (cli_ses.server_sig_algs) { + buf_free(cli_ses.server_sig_algs); + } +} + +static void cli_finished() { + TRACE(("cli_finished()")) + + session_cleanup(); + fprintf(stderr, "Connection to %s@%s:%s closed.\n", cli_opts.username, + cli_opts.remotehost, cli_opts.remoteport); + exit(cli_ses.retval); +} + + +/* called when the remote side closes the connection */ +static void cli_remoteclosed() { + + /* XXX TODO perhaps print a friendlier message if we get this but have + * already sent/received disconnect message(s) ??? */ + m_close(ses.sock_in); + m_close(ses.sock_out); + ses.sock_in = -1; + ses.sock_out = -1; + dropbear_exit("Remote closed the connection"); +} + +/* Operates in-place turning dirty (untrusted potentially containing control + * characters) text into clean text. + * Note: this is safe only with ascii - other charsets could have problems. */ +void cleantext(char* dirtytext) { + + unsigned int i, j; + char c; + + j = 0; + for (i = 0; dirtytext[i] != '\0'; i++) { + + c = dirtytext[i]; + /* We can ignore '\r's */ + if ( (c >= ' ' && c <= '~') || c == '\n' || c == '\t') { + dirtytext[j] = c; + j++; + } + } + /* Null terminate */ + dirtytext[j] = '\0'; +} + +static void recv_msg_global_request_cli(void) { + unsigned int wantreply = 0; + + buf_eatstring(ses.payload); + wantreply = buf_getbool(ses.payload); + + TRACE(("recv_msg_global_request_cli: want_reply: %u", wantreply)); + + if (wantreply) { + /* Send a proper rejection */ + send_msg_request_failure(); + } +} + +void cli_dropbear_exit(int exitcode, const char* format, va_list param) { + char exitmsg[150]; + char fullmsg[300]; + + /* Note that exit message must be rendered before session cleanup */ + + /* Render the formatted exit message */ + vsnprintf(exitmsg, sizeof(exitmsg), format, param); + TRACE(("Exited, cleaning up: %s", exitmsg)) + + /* Add the prefix depending on session/auth state */ + if (!ses.init_done) { + snprintf(fullmsg, sizeof(fullmsg), "Exited: %s", exitmsg); + } else { + snprintf(fullmsg, sizeof(fullmsg), + "Connection to %s@%s:%s exited: %s", + cli_opts.username, cli_opts.remotehost, + cli_opts.remoteport, exitmsg); + } + + /* Do the cleanup first, since then the terminal will be reset */ + session_cleanup(); + +#if DROPBEAR_FUZZ + if (fuzz.do_jmp) { + longjmp(fuzz.jmp, 1); + } +#endif + + /* Avoid printing onwards from terminal cruft */ + fprintf(stderr, "\n"); + + dropbear_log(LOG_INFO, "%s", fullmsg); + + exit(exitcode); +} + +void cli_dropbear_log(int priority, const char* format, va_list param) { + + char printbuf[1024]; + const char *name; + + name = cli_opts.progname; + if (!name) { + name = "dbclient"; + } + + vsnprintf(printbuf, sizeof(printbuf), format, param); + +#ifndef DISABLE_SYSLOG + if (opts.usingsyslog) { + syslog(priority, "%s", printbuf); + } +#endif + + fprintf(stderr, "%s: %s\n", name, printbuf); + fflush(stderr); +} + diff --git a/src/cli-tcpfwd.c b/src/cli-tcpfwd.c new file mode 100644 index 0000000..1b95615 --- /dev/null +++ b/src/cli-tcpfwd.c @@ -0,0 +1,286 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "tcpfwd.h" +#include "channel.h" +#include "runopts.h" +#include "session.h" +#include "ssh.h" +#include "netio.h" + +#if DROPBEAR_CLI_REMOTETCPFWD +static int newtcpforwarded(struct Channel * channel); + +const struct ChanType cli_chan_tcpremote = { + "forwarded-tcpip", + newtcpforwarded, + NULL, + NULL, + NULL, + NULL +}; +#endif + +#if DROPBEAR_CLI_LOCALTCPFWD +static int cli_localtcp(const char* listenaddr, + unsigned int listenport, + const char* remoteaddr, + unsigned int remoteport); +static const struct ChanType cli_chan_tcplocal = { + "direct-tcpip", + NULL, + NULL, + NULL, + NULL, + NULL +}; +#endif + +#if DROPBEAR_CLI_ANYTCPFWD +static void fwd_failed(const char* format, ...) ATTRIB_PRINTF(1,2); +static void fwd_failed(const char* format, ...) +{ + va_list param; + va_start(param, format); + + if (cli_opts.exit_on_fwd_failure) { + _dropbear_exit(EXIT_FAILURE, format, param); + } else { + _dropbear_log(LOG_WARNING, format, param); + } + + va_end(param); +} +#endif + +#if DROPBEAR_CLI_LOCALTCPFWD +void setup_localtcp() { + m_list_elem *iter; + int ret; + + TRACE(("enter setup_localtcp")) + + for (iter = cli_opts.localfwds->first; iter; iter = iter->next) { + struct TCPFwdEntry * fwd = (struct TCPFwdEntry*)iter->item; + ret = cli_localtcp( + fwd->listenaddr, + fwd->listenport, + fwd->connectaddr, + fwd->connectport); + if (ret == DROPBEAR_FAILURE) { + fwd_failed("Failed local port forward %s:%d:%s:%d", + fwd->listenaddr, + fwd->listenport, + fwd->connectaddr, + fwd->connectport); + } + } + TRACE(("leave setup_localtcp")) + +} + +static int cli_localtcp(const char* listenaddr, + unsigned int listenport, + const char* remoteaddr, + unsigned int remoteport) { + + struct TCPListener* tcpinfo = NULL; + int ret; + + TRACE(("enter cli_localtcp: %d %s %d", listenport, remoteaddr, + remoteport)); + + tcpinfo = (struct TCPListener*)m_malloc(sizeof(struct TCPListener)); + + tcpinfo->sendaddr = m_strdup(remoteaddr); + tcpinfo->sendport = remoteport; + + if (listenaddr) + { + tcpinfo->listenaddr = m_strdup(listenaddr); + } + else + { + if (opts.listen_fwd_all) { + tcpinfo->listenaddr = m_strdup(""); + } else { + tcpinfo->listenaddr = m_strdup("localhost"); + } + } + tcpinfo->listenport = listenport; + + tcpinfo->chantype = &cli_chan_tcplocal; + tcpinfo->tcp_type = direct; + + ret = listen_tcpfwd(tcpinfo, NULL); + + if (ret == DROPBEAR_FAILURE) { + m_free(tcpinfo); + } + TRACE(("leave cli_localtcp: %d", ret)) + return ret; +} +#endif /* DROPBEAR_CLI_LOCALTCPFWD */ + +#if DROPBEAR_CLI_REMOTETCPFWD +static void send_msg_global_request_remotetcp(const char *addr, int port) { + + TRACE(("enter send_msg_global_request_remotetcp")) + + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_GLOBAL_REQUEST); + buf_putstring(ses.writepayload, "tcpip-forward", 13); + buf_putbyte(ses.writepayload, 1); /* want_reply */ + buf_putstring(ses.writepayload, addr, strlen(addr)); + buf_putint(ses.writepayload, port); + + encrypt_packet(); + + TRACE(("leave send_msg_global_request_remotetcp")) +} + +/* The only global success/failure messages are for remotetcp. + * Since there isn't any identifier in these messages, we have to rely on them + * being in the same order as we sent the requests. This is the ordering + * of the cli_opts.remotefwds list. + * If the requested remote port is 0 the listen port will be + * dynamically allocated by the server and the port number will be returned + * to client and the port number reported to the user. */ +void cli_recv_msg_request_success() { + /* We just mark off that we have received the reply, + * so that we can report failure for later ones. */ + m_list_elem * iter = NULL; + for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) { + struct TCPFwdEntry *fwd = (struct TCPFwdEntry*)iter->item; + if (!fwd->have_reply) { + fwd->have_reply = 1; + if (fwd->listenport == 0) { + /* The server should let us know which port was allocated if we requested port 0 */ + int allocport = buf_getint(ses.payload); + if (allocport > 0) { + fwd->listenport = allocport; + dropbear_log(LOG_INFO, "Allocated port %d for remote forward to %s:%d", + allocport, fwd->connectaddr, fwd->connectport); + } + } + return; + } + } +} + +void cli_recv_msg_request_failure() { + m_list_elem *iter; + for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) { + struct TCPFwdEntry *fwd = (struct TCPFwdEntry*)iter->item; + if (!fwd->have_reply) { + fwd->have_reply = 1; + fwd_failed("Remote TCP forward request failed (port %d -> %s:%d)", + fwd->listenport, + fwd->connectaddr, + fwd->connectport); + return; + } + } +} + +void setup_remotetcp() { + m_list_elem *iter; + TRACE(("enter setup_remotetcp")) + + for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) { + struct TCPFwdEntry *fwd = (struct TCPFwdEntry*)iter->item; + if (!fwd->listenaddr) + { + /* we store the addresses so that we can compare them + when the server sends them back */ + if (opts.listen_fwd_all) { + fwd->listenaddr = m_strdup(""); + } else { + fwd->listenaddr = m_strdup("localhost"); + } + } + send_msg_global_request_remotetcp(fwd->listenaddr, fwd->listenport); + } + + TRACE(("leave setup_remotetcp")) +} + +static int newtcpforwarded(struct Channel * channel) { + + char *origaddr = NULL; + unsigned int origport; + m_list_elem * iter = NULL; + struct TCPFwdEntry *fwd = NULL; + char portstring[NI_MAXSERV]; + int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED; + + origaddr = buf_getstring(ses.payload, NULL); + origport = buf_getint(ses.payload); + + /* Find which port corresponds. First try and match address as well as port, + in case they want to forward different ports separately ... */ + for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) { + fwd = (struct TCPFwdEntry*)iter->item; + if (origport == fwd->listenport + && strcmp(origaddr, fwd->listenaddr) == 0) { + break; + } + } + + if (!iter) + { + /* ... otherwise try to generically match the only forwarded port + without address (also handles ::1 vs 127.0.0.1 vs localhost case). + rfc4254 is vague about the definition of "address that was connected" */ + for (iter = cli_opts.remotefwds->first; iter; iter = iter->next) { + fwd = (struct TCPFwdEntry*)iter->item; + if (origport == fwd->listenport) { + break; + } + } + } + + + if (iter == NULL || fwd == NULL) { + /* We didn't request forwarding on that port */ + cleantext(origaddr); + dropbear_log(LOG_INFO, "Server sent unrequested forward from \"%s:%d\"", + origaddr, origport); + goto out; + } + + snprintf(portstring, sizeof(portstring), "%u", fwd->connectport); + channel->conn_pending = connect_remote(fwd->connectaddr, portstring, channel_connect_done, + channel, NULL, NULL, DROPBEAR_PRIO_NORMAL); + + err = SSH_OPEN_IN_PROGRESS; + +out: + m_free(origaddr); + TRACE(("leave newtcpdirect: err %d", err)) + return err; +} +#endif /* DROPBEAR_CLI_REMOTETCPFWD */ diff --git a/src/common-algo.c b/src/common-algo.c new file mode 100644 index 0000000..378f0ca --- /dev/null +++ b/src/common-algo.c @@ -0,0 +1,579 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2004 by Mihnea Stoenescu + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "algo.h" +#include "session.h" +#include "dbutil.h" +#include "dh_groups.h" +#include "ltc_prng.h" +#include "ecc.h" +#include "gcm.h" +#include "chachapoly.h" +#include "ssh.h" + +/* This file (algo.c) organises the ciphers which can be used, and is used to + * decide which ciphers/hashes/compression/signing to use during key exchange*/ + +static int void_cipher(const unsigned char* in, unsigned char* out, + unsigned long len, void* UNUSED(cipher_state)) { + if (in != out) { + memmove(out, in, len); + } + return CRYPT_OK; +} + +static int void_start(int UNUSED(cipher), const unsigned char* UNUSED(IV), + const unsigned char* UNUSED(key), + int UNUSED(keylen), int UNUSED(num_rounds), void* UNUSED(cipher_state)) { + return CRYPT_OK; +} + +/* Mappings for ciphers, parameters are + {&cipher_desc, keysize, blocksize} */ + +/* Remember to add new ciphers/hashes to regciphers/reghashes too */ + +#if DROPBEAR_AES256 +static const struct dropbear_cipher dropbear_aes256 = + {&aes_desc, 32, 16}; +#endif +#if DROPBEAR_AES128 +static const struct dropbear_cipher dropbear_aes128 = + {&aes_desc, 16, 16}; +#endif +#if DROPBEAR_3DES +static const struct dropbear_cipher dropbear_3des = + {&des3_desc, 24, 8}; +#endif + +/* used to indicate no encryption, as defined in rfc2410 */ +const struct dropbear_cipher dropbear_nocipher = + {NULL, 16, 8}; + +/* A few void* s are required to silence warnings + * about the symmetric_CBC vs symmetric_CTR cipher_state pointer */ +#if DROPBEAR_ENABLE_CBC_MODE +const struct dropbear_cipher_mode dropbear_mode_cbc = + {(void*)cbc_start, (void*)cbc_encrypt, (void*)cbc_decrypt, NULL, NULL, NULL}; +#endif /* DROPBEAR_ENABLE_CBC_MODE */ + +const struct dropbear_cipher_mode dropbear_mode_none = + {void_start, void_cipher, void_cipher, NULL, NULL, NULL}; + +#if DROPBEAR_ENABLE_CTR_MODE +/* a wrapper to make ctr_start and cbc_start look the same */ +static int dropbear_big_endian_ctr_start(int cipher, + const unsigned char *IV, + const unsigned char *key, int keylen, + int num_rounds, symmetric_CTR *ctr) { + return ctr_start(cipher, IV, key, keylen, num_rounds, CTR_COUNTER_BIG_ENDIAN, ctr); +} +const struct dropbear_cipher_mode dropbear_mode_ctr = + {(void*)dropbear_big_endian_ctr_start, (void*)ctr_encrypt, (void*)ctr_decrypt, NULL, NULL, NULL}; +#endif /* DROPBEAR_ENABLE_CTR_MODE */ + +/* Mapping of ssh hashes to libtomcrypt hashes, including keysize etc. + {&hash_desc, keysize, hashsize} */ + +#if DROPBEAR_SHA1_HMAC +static const struct dropbear_hash dropbear_sha1 = + {&sha1_desc, 20, 20}; +#endif +#if DROPBEAR_SHA1_96_HMAC +static const struct dropbear_hash dropbear_sha1_96 = + {&sha1_desc, 20, 12}; +#endif +#if DROPBEAR_SHA2_256_HMAC +static const struct dropbear_hash dropbear_sha2_256 = + {&sha256_desc, 32, 32}; +#endif +#if DROPBEAR_SHA2_512_HMAC +static const struct dropbear_hash dropbear_sha2_512 = + {&sha512_desc, 64, 64}; +#endif + +const struct dropbear_hash dropbear_nohash = + {NULL, 16, 0}; /* used initially */ + + +/* The following map ssh names to internal values. + * The ordering here is important for the client - the first mode + * that is also supported by the server will get used. */ + +algo_type sshciphers[] = { +#if DROPBEAR_CHACHA20POLY1305 + {"chacha20-poly1305@openssh.com", 0, &dropbear_chachapoly, 1, &dropbear_mode_chachapoly}, +#endif + +#if DROPBEAR_ENABLE_GCM_MODE +#if DROPBEAR_AES128 + {"aes128-gcm@openssh.com", 0, &dropbear_aes128, 1, &dropbear_mode_gcm}, +#endif +#if DROPBEAR_AES256 + {"aes256-gcm@openssh.com", 0, &dropbear_aes256, 1, &dropbear_mode_gcm}, +#endif +#endif /* DROPBEAR_ENABLE_GCM_MODE */ + +#if DROPBEAR_ENABLE_CTR_MODE +#if DROPBEAR_AES128 + {"aes128-ctr", 0, &dropbear_aes128, 1, &dropbear_mode_ctr}, +#endif +#if DROPBEAR_AES256 + {"aes256-ctr", 0, &dropbear_aes256, 1, &dropbear_mode_ctr}, +#endif +#endif /* DROPBEAR_ENABLE_CTR_MODE */ + +#if DROPBEAR_ENABLE_CBC_MODE +#if DROPBEAR_AES128 + {"aes128-cbc", 0, &dropbear_aes128, 1, &dropbear_mode_cbc}, +#endif +#if DROPBEAR_AES256 + {"aes256-cbc", 0, &dropbear_aes256, 1, &dropbear_mode_cbc}, +#endif +#endif /* DROPBEAR_ENABLE_CBC_MODE */ + +#if DROPBEAR_3DES +#if DROPBEAR_ENABLE_CTR_MODE + {"3des-ctr", 0, &dropbear_3des, 1, &dropbear_mode_ctr}, +#endif +#if DROPBEAR_ENABLE_CBC_MODE + {"3des-cbc", 0, &dropbear_3des, 1, &dropbear_mode_cbc}, +#endif +#endif /* DROPBEAR_3DES */ + +#if DROPBEAR_ENABLE_CBC_MODE +#endif /* DROPBEAR_ENABLE_CBC_MODE */ + {NULL, 0, NULL, 0, NULL} +}; + +algo_type sshhashes[] = { +#if DROPBEAR_SHA1_96_HMAC + {"hmac-sha1-96", 0, &dropbear_sha1_96, 1, NULL}, +#endif +#if DROPBEAR_SHA1_HMAC + {"hmac-sha1", 0, &dropbear_sha1, 1, NULL}, +#endif +#if DROPBEAR_SHA2_256_HMAC + {"hmac-sha2-256", 0, &dropbear_sha2_256, 1, NULL}, +#endif +#if DROPBEAR_SHA2_512_HMAC + {"hmac-sha2-512", 0, &dropbear_sha2_512, 1, NULL}, +#endif + {NULL, 0, NULL, 0, NULL} +}; + +#ifndef DISABLE_ZLIB +algo_type ssh_compress[] = { + {"zlib@openssh.com", DROPBEAR_COMP_ZLIB_DELAY, NULL, 1, NULL}, + {"zlib", DROPBEAR_COMP_ZLIB, NULL, 1, NULL}, + {"none", DROPBEAR_COMP_NONE, NULL, 1, NULL}, + {NULL, 0, NULL, 0, NULL} +}; + +algo_type ssh_delaycompress[] = { + {"zlib@openssh.com", DROPBEAR_COMP_ZLIB_DELAY, NULL, 1, NULL}, + {"none", DROPBEAR_COMP_NONE, NULL, 1, NULL}, + {NULL, 0, NULL, 0, NULL} +}; +#endif + +algo_type ssh_nocompress[] = { + {"none", DROPBEAR_COMP_NONE, NULL, 1, NULL}, + {NULL, 0, NULL, 0, NULL} +}; + +algo_type sigalgs[] = { +#if DROPBEAR_ED25519 + {"ssh-ed25519", DROPBEAR_SIGNATURE_ED25519, NULL, 1, NULL}, +#if DROPBEAR_SK_ED25519 + {"sk-ssh-ed25519@openssh.com", DROPBEAR_SIGNATURE_SK_ED25519, NULL, 1, NULL}, +#endif +#endif +#if DROPBEAR_ECDSA +#if DROPBEAR_ECC_256 + {"ecdsa-sha2-nistp256", DROPBEAR_SIGNATURE_ECDSA_NISTP256, NULL, 1, NULL}, +#endif +#if DROPBEAR_ECC_384 + {"ecdsa-sha2-nistp384", DROPBEAR_SIGNATURE_ECDSA_NISTP384, NULL, 1, NULL}, +#endif +#if DROPBEAR_ECC_521 + {"ecdsa-sha2-nistp521", DROPBEAR_SIGNATURE_ECDSA_NISTP521, NULL, 1, NULL}, +#endif +#if DROPBEAR_SK_ECDSA + {"sk-ecdsa-sha2-nistp256@openssh.com", DROPBEAR_SIGNATURE_SK_ECDSA_NISTP256, NULL, 1, NULL}, +#endif +#endif +#if DROPBEAR_RSA +#if DROPBEAR_RSA_SHA256 + {"rsa-sha2-256", DROPBEAR_SIGNATURE_RSA_SHA256, NULL, 1, NULL}, +#endif +#if DROPBEAR_RSA_SHA1 + {"ssh-rsa", DROPBEAR_SIGNATURE_RSA_SHA1, NULL, 1, NULL}, +#endif +#endif +#if DROPBEAR_DSS + {"ssh-dss", DROPBEAR_SIGNATURE_DSS, NULL, 1, NULL}, +#endif + {NULL, 0, NULL, 0, NULL} +}; + +#if DROPBEAR_DH_GROUP1 +static const struct dropbear_kex kex_dh_group1 = {DROPBEAR_KEX_NORMAL_DH, dh_p_1, DH_P_1_LEN, NULL, &sha1_desc }; +#endif +#if DROPBEAR_DH_GROUP14_SHA1 +static const struct dropbear_kex kex_dh_group14_sha1 = {DROPBEAR_KEX_NORMAL_DH, dh_p_14, DH_P_14_LEN, NULL, &sha1_desc }; +#endif +#if DROPBEAR_DH_GROUP14_SHA256 +static const struct dropbear_kex kex_dh_group14_sha256 = {DROPBEAR_KEX_NORMAL_DH, dh_p_14, DH_P_14_LEN, NULL, &sha256_desc }; +#endif +#if DROPBEAR_DH_GROUP16 +static const struct dropbear_kex kex_dh_group16_sha512 = {DROPBEAR_KEX_NORMAL_DH, dh_p_16, DH_P_16_LEN, NULL, &sha512_desc }; +#endif + +#if DROPBEAR_ECDH +#if DROPBEAR_ECC_256 +static const struct dropbear_kex kex_ecdh_nistp256 = {DROPBEAR_KEX_ECDH, NULL, 0, &ecc_curve_nistp256, &sha256_desc }; +#endif +#if DROPBEAR_ECC_384 +static const struct dropbear_kex kex_ecdh_nistp384 = {DROPBEAR_KEX_ECDH, NULL, 0, &ecc_curve_nistp384, &sha384_desc }; +#endif +#if DROPBEAR_ECC_521 +static const struct dropbear_kex kex_ecdh_nistp521 = {DROPBEAR_KEX_ECDH, NULL, 0, &ecc_curve_nistp521, &sha512_desc }; +#endif +#endif /* DROPBEAR_ECDH */ + +#if DROPBEAR_CURVE25519 +/* Referred to directly */ +static const struct dropbear_kex kex_curve25519 = {DROPBEAR_KEX_CURVE25519, NULL, 0, NULL, &sha256_desc }; +#endif + +/* data == NULL for non-kex algorithm identifiers */ +algo_type sshkex[] = { +#if DROPBEAR_CURVE25519 + {"curve25519-sha256", 0, &kex_curve25519, 1, NULL}, + {"curve25519-sha256@libssh.org", 0, &kex_curve25519, 1, NULL}, +#endif +#if DROPBEAR_ECDH +#if DROPBEAR_ECC_521 + {"ecdh-sha2-nistp521", 0, &kex_ecdh_nistp521, 1, NULL}, +#endif +#if DROPBEAR_ECC_384 + {"ecdh-sha2-nistp384", 0, &kex_ecdh_nistp384, 1, NULL}, +#endif +#if DROPBEAR_ECC_256 + {"ecdh-sha2-nistp256", 0, &kex_ecdh_nistp256, 1, NULL}, +#endif +#endif +#if DROPBEAR_DH_GROUP14_SHA256 + {"diffie-hellman-group14-sha256", 0, &kex_dh_group14_sha256, 1, NULL}, +#endif +#if DROPBEAR_DH_GROUP14_SHA1 + {"diffie-hellman-group14-sha1", 0, &kex_dh_group14_sha1, 1, NULL}, +#endif +#if DROPBEAR_DH_GROUP1 + {"diffie-hellman-group1-sha1", 0, &kex_dh_group1, 1, NULL}, +#endif +#if DROPBEAR_DH_GROUP16 + {"diffie-hellman-group16-sha512", 0, &kex_dh_group16_sha512, 1, NULL}, +#endif +#if DROPBEAR_KEXGUESS2 + {KEXGUESS2_ALGO_NAME, 0, NULL, 1, NULL}, +#endif +#if DROPBEAR_EXT_INFO +#if DROPBEAR_CLIENT + /* Set unusable by svr_algos_initialise() */ + {SSH_EXT_INFO_C, 0, NULL, 1, NULL}, +#endif +#endif + {NULL, 0, NULL, 0, NULL} +}; + +/* Output a comma separated list of algorithms to a buffer */ +void buf_put_algolist_all(buffer * buf, const algo_type localalgos[], int useall) { + unsigned int i, len; + unsigned int donefirst = 0; + unsigned int startpos; + + startpos = buf->pos; + /* Placeholder for length */ + buf_putint(buf, 0); + for (i = 0; localalgos[i].name != NULL; i++) { + if (localalgos[i].usable || useall) { + if (donefirst) { + buf_putbyte(buf, ','); + } + donefirst = 1; + len = strlen(localalgos[i].name); + buf_putbytes(buf, (const unsigned char *) localalgos[i].name, len); + } + } + /* Fill out the length */ + len = buf->pos - startpos - 4; + buf_setpos(buf, startpos); + buf_putint(buf, len); + TRACE(("algolist add %d '%.*s'", len, len, buf_getptr(buf, len))) + buf_incrwritepos(buf, len); +} + +void buf_put_algolist(buffer * buf, const algo_type localalgos[]) { + buf_put_algolist_all(buf, localalgos, 0); +} + +/* returns a list of pointers into algolist, of null-terminated names. + ret_list should be passed in with space for *ret_count elements, + on return *ret_count has the number of names filled. + algolist is modified. */ +static void get_algolist(char* algolist, unsigned int algolist_len, + const char* *ret_list, unsigned int *ret_count) { + unsigned int max_count = *ret_count; + unsigned int i; + + if (*ret_count == 0) { + return; + } + if (algolist_len > MAX_PROPOSED_ALGO*(MAX_NAME_LEN+1)) { + *ret_count = 0; + } + + /* ret_list will contain a list of the strings parsed out. + We will have at least one string (even if it's just "") */ + ret_list[0] = algolist; + *ret_count = 1; + for (i = 0; i < algolist_len; i++) { + if (algolist[i] == '\0') { + /* someone is trying something strange */ + *ret_count = 0; + return; + } + + if (algolist[i] == ',') { + if (*ret_count >= max_count) { + dropbear_exit("Too many remote algorithms"); + *ret_count = 0; + return; + } + algolist[i] = '\0'; + ret_list[*ret_count] = &algolist[i+1]; + (*ret_count)++; + } + } +} + +/* Return DROPBEAR_SUCCESS if the namelist contains algo, +DROPBEAR_FAILURE otherwise. buf position is not incremented. */ +int buf_has_algo(buffer *buf, const char *algo) { + unsigned char* algolist = NULL; + unsigned int orig_pos = buf->pos; + unsigned int len, remotecount, i; + const char *remotenames[MAX_PROPOSED_ALGO]; + int ret = DROPBEAR_FAILURE; + + algolist = buf_getstring(buf, &len); + remotecount = MAX_PROPOSED_ALGO; + get_algolist(algolist, len, remotenames, &remotecount); + for (i = 0; i < remotecount; i++) + { + if (strcmp(remotenames[i], algo) == 0) { + ret = DROPBEAR_SUCCESS; + break; + } + } + if (algolist) { + m_free(algolist); + } + buf_setpos(buf, orig_pos); + return ret; +} + +algo_type * first_usable_algo(algo_type algos[]) { + int i; + for (i = 0; algos[i].name != NULL; i++) { + if (algos[i].usable) { + return &algos[i]; + } + } + return NULL; +} + +/* match the first algorithm in the comma-separated list in buf which is + * also in localalgos[], or return NULL on failure. + * (*goodguess) is set to 1 if the preferred client/server algos match, + * 0 otherwise. This is used for checking if the kexalgo/hostkeyalgos are + * guessed correctly */ +algo_type * buf_match_algo(buffer* buf, algo_type localalgos[], + int kexguess2, int *goodguess) { + char * algolist = NULL; + const char *remotenames[MAX_PROPOSED_ALGO], *localnames[MAX_PROPOSED_ALGO]; + unsigned int len; + unsigned int remotecount, localcount, clicount, servcount, i, j; + algo_type * ret = NULL; + const char **clinames, **servnames; + + if (goodguess) { + *goodguess = 0; + } + + /* get the comma-separated list from the buffer ie "algo1,algo2,algo3" */ + algolist = buf_getstring(buf, &len); + DEBUG3(("buf_match_algo: %s", algolist)) + remotecount = MAX_PROPOSED_ALGO; + get_algolist(algolist, len, remotenames, &remotecount); + + for (i = 0; localalgos[i].name != NULL; i++) { + if (localalgos[i].usable) { + localnames[i] = localalgos[i].name; + } else { + localnames[i] = NULL; + } + } + localcount = i; + + if (IS_DROPBEAR_SERVER) { + clinames = remotenames; + clicount = remotecount; + servnames = localnames; + servcount = localcount; + } else { + clinames = localnames; + clicount = localcount; + servnames = remotenames; + servcount = remotecount; + } + + /* iterate and find the first match */ + for (i = 0; i < clicount; i++) { + for (j = 0; j < servcount; j++) { + if (!(servnames[j] && clinames[i])) { + /* unusable algos are NULL */ + continue; + } + if (strcmp(servnames[j], clinames[i]) == 0) { + /* set if it was a good guess */ + if (goodguess != NULL) { + if (kexguess2) { + if (i == 0) { + *goodguess = 1; + } + } else { + if (i == 0 && j == 0) { + *goodguess = 1; + } + } + } + /* set the algo to return */ + if (IS_DROPBEAR_SERVER) { + ret = &localalgos[j]; + } else { + ret = &localalgos[i]; + } + goto out; + } + } + } + +out: + m_free(algolist); + return ret; +} + +#if DROPBEAR_USER_ALGO_LIST + +char * +algolist_string(const algo_type algos[]) +{ + char *ret_list; + buffer *b = buf_new(200); + buf_put_algolist(b, algos); + buf_setpos(b, b->len); + buf_putbyte(b, '\0'); + buf_setpos(b, 4); + ret_list = m_strdup((const char *) buf_getptr(b, b->len - b->pos)); + buf_free(b); + return ret_list; +} + +static algo_type* +check_algo(const char* algo_name, algo_type *algos) +{ + algo_type *a; + for (a = algos; a->name != NULL; a++) + { + if (strcmp(a->name, algo_name) == 0) + { + return a; + } + } + + return NULL; +} + +/* Checks a user provided comma-separated algorithm list for available + * options. Any that are not acceptable are removed in-place. Returns the + * number of valid algorithms. */ +int +check_user_algos(const char* user_algo_list, algo_type * algos, + const char *algo_desc) +{ + algo_type new_algos[MAX_PROPOSED_ALGO+1]; + char *work_list = m_strdup(user_algo_list); + char *start = work_list; + char *c; + int n; + /* So we can iterate and look for null terminator */ + memset(new_algos, 0x0, sizeof(new_algos)); + for (c = work_list, n = 0; ; c++) + { + char oc = *c; + if (n >= MAX_PROPOSED_ALGO) { + dropbear_exit("Too many algorithms '%s'", user_algo_list); + } + if (*c == ',' || *c == '\0') { + algo_type *match_algo = NULL; + *c = '\0'; + match_algo = check_algo(start, algos); + if (match_algo) { + if (check_algo(start, new_algos)) { + TRACE(("Skip repeated algorithm '%s'", start)) + } else { + new_algos[n] = *match_algo; + n++; + } + } else { + dropbear_log(LOG_WARNING, "This Dropbear program does not support '%s' %s algorithm", start, algo_desc); + } + c++; + start = c; + } + if (oc == '\0') { + break; + } + } + m_free(work_list); + /* n+1 to include a null terminator */ + memcpy(algos, new_algos, sizeof(*new_algos) * (n+1)); + return n; +} +#endif /* DROPBEAR_USER_ALGO_LIST */ diff --git a/src/common-channel.c b/src/common-channel.c new file mode 100644 index 0000000..be5b57f --- /dev/null +++ b/src/common-channel.c @@ -0,0 +1,1223 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002-2004 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* Handle the multiplexed channels, such as sessions, x11, agent connections */ + +#include "includes.h" +#include "session.h" +#include "packet.h" +#include "ssh.h" +#include "buffer.h" +#include "circbuffer.h" +#include "dbutil.h" +#include "channel.h" +#include "listener.h" +#include "runopts.h" +#include "netio.h" + +static void send_msg_channel_open_failure(unsigned int remotechan, int reason, + const char *text, const char *lang); +static void send_msg_channel_open_confirmation(const struct Channel* channel, + unsigned int recvwindow, + unsigned int recvmaxpacket); +static int writechannel(struct Channel* channel, int fd, circbuffer *cbuf, + const unsigned char *moredata, unsigned int *morelen); +static void send_msg_channel_window_adjust(const struct Channel *channel, + unsigned int incr); +static void send_msg_channel_data(struct Channel *channel, int isextended); +static void send_msg_channel_eof(struct Channel *channel); +static void send_msg_channel_close(struct Channel *channel); +static void remove_channel(struct Channel *channel); +static unsigned int write_pending(const struct Channel * channel); +static void check_close(struct Channel *channel); +static void close_chan_fd(struct Channel *channel, int fd, int how); + +#define FD_UNINIT (-2) +#define FD_CLOSED (-1) + +#define ERRFD_IS_READ(channel) ((channel)->extrabuf == NULL) +#define ERRFD_IS_WRITE(channel) (!ERRFD_IS_READ(channel)) + +/* allow space for: + * 1 byte byte SSH_MSG_CHANNEL_DATA + * 4 bytes uint32 recipient channel + * 4 bytes string data + */ +#define RECV_MAX_CHANNEL_DATA_LEN (RECV_MAX_PAYLOAD_LEN-(1+4+4)) + +/* Initialise all the channels */ +void chaninitialise(const struct ChanType *chantypes[]) { + + /* may as well create space for a single channel */ + ses.channels = (struct Channel**)m_malloc(sizeof(struct Channel*)); + ses.chansize = 1; + ses.channels[0] = NULL; + ses.chancount = 0; + + ses.chantypes = chantypes; + +#if DROPBEAR_LISTENERS + listeners_initialise(); +#endif + +} + +/* Clean up channels, freeing allocated memory */ +void chancleanup() { + + unsigned int i; + + TRACE(("enter chancleanup")) + for (i = 0; i < ses.chansize; i++) { + if (ses.channels[i] != NULL) { + TRACE(("channel %d closing", i)) + remove_channel(ses.channels[i]); + } + } + m_free(ses.channels); + TRACE(("leave chancleanup")) +} + +/* Create a new channel entry, send a reply confirm or failure */ +/* If remotechan, transwindow and transmaxpacket are not know (for a new + * outgoing connection, with them to be filled on confirmation), they should + * all be set to 0 */ +static struct Channel* newchannel(unsigned int remotechan, + const struct ChanType *type, + unsigned int transwindow, unsigned int transmaxpacket) { + + struct Channel * newchan; + unsigned int i, j; + + TRACE(("enter newchannel")) + + /* first see if we can use existing channels */ + for (i = 0; i < ses.chansize; i++) { + if (ses.channels[i] == NULL) { + break; + } + } + + /* otherwise extend the list */ + if (i == ses.chansize) { + if (ses.chansize >= MAX_CHANNELS) { + TRACE(("leave newchannel: max chans reached")) + return NULL; + } + + /* extend the channels */ + ses.channels = (struct Channel**)m_realloc(ses.channels, + (ses.chansize+CHAN_EXTEND_SIZE)*sizeof(struct Channel*)); + + ses.chansize += CHAN_EXTEND_SIZE; + + /* set the new channels to null */ + for (j = i; j < ses.chansize; j++) { + ses.channels[j] = NULL; + } + + } + + newchan = (struct Channel*)m_malloc(sizeof(struct Channel)); + newchan->type = type; + newchan->index = i; + newchan->sent_close = newchan->recv_close = 0; + newchan->sent_eof = newchan->recv_eof = 0; + + newchan->remotechan = remotechan; + newchan->transwindow = transwindow; + newchan->transmaxpacket = transmaxpacket; + + newchan->typedata = NULL; + newchan->writefd = FD_UNINIT; + newchan->readfd = FD_UNINIT; + newchan->errfd = FD_CLOSED; /* this isn't always set to start with */ + newchan->await_open = 0; + + newchan->writebuf = cbuf_new(opts.recv_window); + newchan->recvwindow = opts.recv_window; + + newchan->extrabuf = NULL; /* The user code can set it up */ + newchan->recvdonelen = 0; + newchan->recvmaxpacket = RECV_MAX_CHANNEL_DATA_LEN; + + newchan->prio = DROPBEAR_PRIO_NORMAL; + + ses.channels[i] = newchan; + ses.chancount++; + + TRACE(("leave newchannel")) + + return newchan; +} + +/* Returns the channel structure corresponding to the channel in the current + * data packet (ses.payload must be positioned appropriately). + * A valid channel is always returns, it will fail fatally with an unknown + * channel */ +static struct Channel* getchannel_msg(const char* kind) { + + unsigned int chan; + + chan = buf_getint(ses.payload); + if (chan >= ses.chansize || ses.channels[chan] == NULL) { + if (kind) { + dropbear_exit("%s for unknown channel %d", kind, chan); + } else { + dropbear_exit("Unknown channel %d", chan); + } + } + return ses.channels[chan]; +} + +struct Channel* getchannel() { + return getchannel_msg(NULL); +} + +/* Iterate through the channels, performing IO if available */ +void channelio(const fd_set *readfds, const fd_set *writefds) { + + /* Listeners such as TCP, X11, agent-auth */ + struct Channel *channel; + unsigned int i; + + /* foreach channel */ + for (i = 0; i < ses.chansize; i++) { + /* Close checking only needs to occur for channels that had IO events */ + int do_check_close = 0; + + channel = ses.channels[i]; + if (channel == NULL) { + /* only process in-use channels */ + continue; + } + + /* read data and send it over the wire */ + if (channel->readfd >= 0 && FD_ISSET(channel->readfd, readfds)) { + TRACE(("send normal readfd")) + send_msg_channel_data(channel, 0); + do_check_close = 1; + } + + /* read stderr data and send it over the wire */ + if (ERRFD_IS_READ(channel) && channel->errfd >= 0 + && FD_ISSET(channel->errfd, readfds)) { + TRACE(("send normal errfd")) + send_msg_channel_data(channel, 1); + do_check_close = 1; + } + + /* write to program/pipe stdin */ + if (channel->writefd >= 0 && FD_ISSET(channel->writefd, writefds)) { + writechannel(channel, channel->writefd, channel->writebuf, NULL, NULL); + do_check_close = 1; + } + + /* stderr for client mode */ + if (ERRFD_IS_WRITE(channel) + && channel->errfd >= 0 && FD_ISSET(channel->errfd, writefds)) { + writechannel(channel, channel->errfd, channel->extrabuf, NULL, NULL); + do_check_close = 1; + } + + if (ses.channel_signal_pending) { + /* SIGCHLD can change channel state for server sessions */ + do_check_close = 1; + } + + /* handle any channel closing etc */ + if (do_check_close) { + check_close(channel); + } + } + +#if DROPBEAR_LISTENERS + handle_listeners(readfds); +#endif +} + + +/* Returns true if there is data remaining to be written to stdin or + * stderr of a channel's endpoint. */ +static unsigned int write_pending(const struct Channel * channel) { + + if (channel->writefd >= 0 && cbuf_getused(channel->writebuf) > 0) { + return 1; + } else if (channel->errfd >= 0 && channel->extrabuf && + cbuf_getused(channel->extrabuf) > 0) { + return 1; + } + return 0; +} + + +/* EOF/close handling */ +static void check_close(struct Channel *channel) { + int close_allowed = 0; + + TRACE2(("check_close: writefd %d, readfd %d, errfd %d, sent_close %d, recv_close %d", + channel->writefd, channel->readfd, + channel->errfd, channel->sent_close, channel->recv_close)) + TRACE2(("writebuf size %d extrabuf size %d", + channel->writebuf ? cbuf_getused(channel->writebuf) : 0, + channel->extrabuf ? cbuf_getused(channel->extrabuf) : 0)) + + /* if a type-specific check_close is defined we will only exit + once that has been triggered. this is only used for a server "session" + channel, to ensure that the shell has exited (and the exit status + retrieved) before we close things up. */ + if (!channel->type->check_close + || channel->sent_close + || channel->type->check_close(channel)) { + close_allowed = 1; + } + + /* In flushing mode we close FDs as soon as pipes are empty. + This is used to drain out FDs when the process exits, in the case + where the FD doesn't have EOF - "sleep 10&echo hello" case */ + if (channel->flushing) { + if (channel->readfd >= 0 && !fd_read_pending(channel->readfd)) { + close_chan_fd(channel, channel->readfd, SHUT_RD); + } + if (ERRFD_IS_READ(channel) + && channel->errfd >= 0 && !fd_read_pending(channel->errfd)) { + close_chan_fd(channel, channel->errfd, SHUT_RD); + } + } + + if (channel->recv_close && !write_pending(channel) && close_allowed) { + if (!channel->sent_close) { + TRACE(("Sending MSG_CHANNEL_CLOSE in response to same.")) + send_msg_channel_close(channel); + } + remove_channel(channel); + return; + } + + if ((channel->recv_eof && !write_pending(channel)) + /* have a server "session" and child has exited */ + || (channel->type->check_close && close_allowed)) { + close_chan_fd(channel, channel->writefd, SHUT_WR); + } + + /* If we're not going to send any more data, send EOF */ + if (!channel->sent_eof + && channel->readfd == FD_CLOSED + && (ERRFD_IS_WRITE(channel) || channel->errfd == FD_CLOSED)) { + send_msg_channel_eof(channel); + } + + /* And if we can't receive any more data from them either, close up */ + if (channel->readfd == FD_CLOSED + && channel->writefd == FD_CLOSED + && (ERRFD_IS_WRITE(channel) || channel->errfd == FD_CLOSED) + && !channel->sent_close + && close_allowed + && !write_pending(channel)) { + TRACE(("sending close, readfd is closed")) + send_msg_channel_close(channel); + } +} + +/* Check whether a deferred (EINPROGRESS) connect() was successful, and + * if so, set up the channel properly. Otherwise, the channel is cleaned up, so + * it is important that the channel reference isn't used after a call to this + * function */ +void channel_connect_done(int result, int sock, void* user_data, const char* errstring) { + struct Channel *channel = user_data; + + TRACE(("enter channel_connect_done")) + + if (result == DROPBEAR_SUCCESS) + { + channel->readfd = channel->writefd = sock; + channel->bidir_fd = 1; + channel->conn_pending = NULL; + send_msg_channel_open_confirmation(channel, channel->recvwindow, + channel->recvmaxpacket); + TRACE(("leave channel_connect_done: success")) + } + else + { + send_msg_channel_open_failure(channel->remotechan, + SSH_OPEN_CONNECT_FAILED, errstring, ""); + remove_channel(channel); + TRACE(("leave check_in_progress: fail. internal errstring: %s", errstring)) + } +} + + +/* Send the close message and set the channel as closed */ +static void send_msg_channel_close(struct Channel *channel) { + + TRACE(("enter send_msg_channel_close %p", (void*)channel)) + if (channel->type->closehandler) { + channel->type->closehandler(channel); + } + + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_CLOSE); + buf_putint(ses.writepayload, channel->remotechan); + + encrypt_packet(); + + channel->sent_eof = 1; + channel->sent_close = 1; + close_chan_fd(channel, channel->readfd, SHUT_RD); + close_chan_fd(channel, channel->errfd, SHUT_RDWR); + close_chan_fd(channel, channel->writefd, SHUT_WR); + TRACE(("leave send_msg_channel_close")) +} + +/* call this when trans/eof channels are closed */ +static void send_msg_channel_eof(struct Channel *channel) { + + TRACE(("enter send_msg_channel_eof")) + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_EOF); + buf_putint(ses.writepayload, channel->remotechan); + + encrypt_packet(); + + channel->sent_eof = 1; + + TRACE(("leave send_msg_channel_eof")) +} + +#ifndef HAVE_WRITEV +static int writechannel_fallback(struct Channel* channel, int fd, circbuffer *cbuf, + const unsigned char *UNUSED(moredata), unsigned int *morelen) { + + unsigned char *circ_p1, *circ_p2; + unsigned int circ_len1, circ_len2; + ssize_t written; + + if (morelen) { + /* fallback doesn't consume moredata */ + *morelen = 0; + } + + /* Write the first portion of the circular buffer */ + cbuf_readptrs(cbuf, &circ_p1, &circ_len1, &circ_p2, &circ_len2); + written = write(fd, circ_p1, circ_len1); + if (written < 0) { + if (errno != EINTR && errno != EAGAIN) { + TRACE(("channel IO write error fd %d %s", fd, strerror(errno))) + close_chan_fd(channel, fd, SHUT_WR); + return DROPBEAR_FAILURE; + } + } else { + cbuf_incrread(cbuf, written); + channel->recvdonelen += written; + } + return DROPBEAR_SUCCESS; +} +#endif /* !HAVE_WRITEV */ + +#ifdef HAVE_WRITEV +static int writechannel_writev(struct Channel* channel, int fd, circbuffer *cbuf, + const unsigned char *moredata, unsigned int *morelen) { + + struct iovec iov[3]; + unsigned char *circ_p1, *circ_p2; + unsigned int circ_len1, circ_len2; + int io_count = 0; + + ssize_t written; + + cbuf_readptrs(cbuf, &circ_p1, &circ_len1, &circ_p2, &circ_len2); + + if (circ_len1 > 0) { + TRACE(("circ1 %d", circ_len1)) + iov[io_count].iov_base = circ_p1; + iov[io_count].iov_len = circ_len1; + io_count++; + } + + if (circ_len2 > 0) { + TRACE(("circ2 %d", circ_len2)) + iov[io_count].iov_base = circ_p2; + iov[io_count].iov_len = circ_len2; + io_count++; + } + + if (morelen) { + assert(moredata); + TRACE(("more %d", *morelen)) + iov[io_count].iov_base = (void*)moredata; + iov[io_count].iov_len = *morelen; + io_count++; + } + + if (io_count == 0) { + /* writechannel may sometimes be called twice in a main loop iteration. + From common_recv_msg_channel_data() then channelio(). + The second call may not have any data to write, so we just return. */ + TRACE(("leave writechannel, no data")) + return DROPBEAR_SUCCESS; + } + + if (morelen) { + /* Default return value, none consumed */ + *morelen = 0; + } + + written = writev(fd, iov, io_count); + + if (written < 0) { + if (errno != EINTR && errno != EAGAIN) { + TRACE(("channel IO write error fd %d %s", fd, strerror(errno))) + close_chan_fd(channel, fd, SHUT_WR); + return DROPBEAR_FAILURE; + } + } else { + int cbuf_written = MIN(circ_len1+circ_len2, (unsigned int)written); + cbuf_incrread(cbuf, cbuf_written); + if (morelen) { + *morelen = written - cbuf_written; + } + channel->recvdonelen += written; + } + return DROPBEAR_SUCCESS; +} +#endif /* HAVE_WRITEV */ + +/* Called to write data out to the local side of the channel. + Writes the circular buffer contents and also the "moredata" buffer + if not null. Will ignore EAGAIN. + Returns DROPBEAR_FAILURE if writing to fd had an error and the channel is being closed, DROPBEAR_SUCCESS otherwise */ +static int writechannel(struct Channel* channel, int fd, circbuffer *cbuf, + const unsigned char *moredata, unsigned int *morelen) { + int ret = DROPBEAR_SUCCESS; + TRACE(("enter writechannel fd %d", fd)) +#ifdef HAVE_WRITEV + ret = writechannel_writev(channel, fd, cbuf, moredata, morelen); +#else + ret = writechannel_fallback(channel, fd, cbuf, moredata, morelen); +#endif + + /* Window adjust handling */ + if (channel->recvdonelen >= RECV_WINDOWEXTEND) { + send_msg_channel_window_adjust(channel, channel->recvdonelen); + channel->recvwindow += channel->recvdonelen; + channel->recvdonelen = 0; + } + + dropbear_assert(channel->recvwindow <= opts.recv_window); + dropbear_assert(channel->recvwindow <= cbuf_getavail(channel->writebuf)); + dropbear_assert(channel->extrabuf == NULL || + channel->recvwindow <= cbuf_getavail(channel->extrabuf)); + + TRACE(("leave writechannel")) + return ret; +} + + +/* Set the file descriptors for the main select in session.c + * This avoid channels which don't have any window available, are closed, etc*/ +void setchannelfds(fd_set *readfds, fd_set *writefds, int allow_reads) { + + unsigned int i; + struct Channel * channel; + + for (i = 0; i < ses.chansize; i++) { + + channel = ses.channels[i]; + if (channel == NULL) { + continue; + } + + /* Stuff to put over the wire. + Avoid queueing data to send if we're in the middle of a + key re-exchange (!dataallowed), but still read from the + FD if there's the possibility of "~."" to kill an + interactive session (the read_mangler) */ + if (channel->transwindow > 0 + && ((ses.dataallowed && allow_reads) || channel->read_mangler)) { + + if (channel->readfd >= 0) { + FD_SET(channel->readfd, readfds); + } + + if (ERRFD_IS_READ(channel) && channel->errfd >= 0) { + FD_SET(channel->errfd, readfds); + } + } + + /* Stuff from the wire */ + if (channel->writefd >= 0 && cbuf_getused(channel->writebuf) > 0) { + FD_SET(channel->writefd, writefds); + } + + if (ERRFD_IS_WRITE(channel) && channel->errfd >= 0 + && cbuf_getused(channel->extrabuf) > 0) { + FD_SET(channel->errfd, writefds); + } + + } /* foreach channel */ + +#if DROPBEAR_LISTENERS + set_listener_fds(readfds); +#endif + +} + +/* handle the channel EOF event, by closing the channel filedescriptor. The + * channel isn't closed yet, it is left until the incoming (from the program + * etc) FD is also EOF */ +void recv_msg_channel_eof() { + + struct Channel * channel; + + TRACE(("enter recv_msg_channel_eof")) + + channel = getchannel_msg("EOF"); + + channel->recv_eof = 1; + + check_close(channel); + TRACE(("leave recv_msg_channel_eof")) +} + + +/* Handle channel closure(), respond in kind and close the channels */ +void recv_msg_channel_close() { + + struct Channel * channel; + + TRACE(("enter recv_msg_channel_close")) + + channel = getchannel_msg("Close"); + + channel->recv_eof = 1; + channel->recv_close = 1; + + check_close(channel); + TRACE(("leave recv_msg_channel_close")) +} + +/* Remove a channel entry, this is only executed after both sides have sent + * channel close */ +static void remove_channel(struct Channel * channel) { + + TRACE(("enter remove_channel")) + TRACE(("channel index is %d", channel->index)) + + cbuf_free(channel->writebuf); + channel->writebuf = NULL; + + if (channel->extrabuf) { + cbuf_free(channel->extrabuf); + channel->extrabuf = NULL; + } + + + if (IS_DROPBEAR_SERVER || (channel->writefd != STDOUT_FILENO)) { + /* close the FDs in case they haven't been done + * yet (they might have been shutdown etc) */ + TRACE(("CLOSE writefd %d", channel->writefd)) + m_close(channel->writefd); + TRACE(("CLOSE readfd %d", channel->readfd)) + m_close(channel->readfd); + TRACE(("CLOSE errfd %d", channel->errfd)) + m_close(channel->errfd); + } + + if (channel->type->cleanup) { + channel->type->cleanup(channel); + } + + if (channel->conn_pending) { + cancel_connect(channel->conn_pending); + } + + ses.channels[channel->index] = NULL; + m_free(channel); + ses.chancount--; + + update_channel_prio(); + + TRACE(("leave remove_channel")) +} + +/* Handle channel specific requests, passing off to corresponding handlers + * such as chansession or x11fwd */ +void recv_msg_channel_request() { + + struct Channel *channel; + + channel = getchannel(); + + TRACE(("enter recv_msg_channel_request %p", (void*)channel)) + + if (channel->type->reqhandler) { + channel->type->reqhandler(channel); + } else { + int wantreply; + buf_eatstring(ses.payload); + wantreply = buf_getbool(ses.payload); + if (wantreply) { + send_msg_channel_failure(channel); + } + } + + TRACE(("leave recv_msg_channel_request")) + +} + +/* Reads data from the server's program/shell/etc, and puts it in a + * channel_data packet to send. + * chan is the remote channel, isextended is 0 if it is normal data, 1 + * if it is extended data. if it is extended, then the type is in + * exttype */ +static void send_msg_channel_data(struct Channel *channel, int isextended) { + + int len; + size_t maxlen, size_pos; + int fd; + + CHECKCLEARTOWRITE(); + + TRACE(("enter send_msg_channel_data")) + dropbear_assert(!channel->sent_close); + + if (isextended) { + fd = channel->errfd; + } else { + fd = channel->readfd; + } + TRACE(("enter send_msg_channel_data isextended %d fd %d", isextended, fd)) + dropbear_assert(fd >= 0); + + maxlen = MIN(channel->transwindow, channel->transmaxpacket); + /* -(1+4+4) is SSH_MSG_CHANNEL_DATA, channel number, string length, and + * exttype if is extended */ + maxlen = MIN(maxlen, + ses.writepayload->size - 1 - 4 - 4 - (isextended ? 4 : 0)); + TRACE(("maxlen %zd", maxlen)) + if (maxlen == 0) { + TRACE(("leave send_msg_channel_data: no window")) + return; + } + + buf_putbyte(ses.writepayload, + isextended ? SSH_MSG_CHANNEL_EXTENDED_DATA : SSH_MSG_CHANNEL_DATA); + buf_putint(ses.writepayload, channel->remotechan); + if (isextended) { + buf_putint(ses.writepayload, SSH_EXTENDED_DATA_STDERR); + } + /* a dummy size first ...*/ + size_pos = ses.writepayload->pos; + buf_putint(ses.writepayload, 0); + + /* read the data */ + len = read(fd, buf_getwriteptr(ses.writepayload, maxlen), maxlen); + + if (len <= 0) { + if (len == 0 || errno != EINTR) { + /* This will also get hit in the case of EAGAIN. The only + time we expect to receive EAGAIN is when we're flushing a FD, + in which case it can be treated the same as EOF */ + close_chan_fd(channel, fd, SHUT_RD); + } + buf_setpos(ses.writepayload, 0); + buf_setlen(ses.writepayload, 0); + TRACE(("leave send_msg_channel_data: len %d read err %d or EOF for fd %d", + len, errno, fd)) + return; + } + + if (channel->read_mangler) { + channel->read_mangler(channel, buf_getwriteptr(ses.writepayload, len), &len); + if (len == 0) { + buf_setpos(ses.writepayload, 0); + buf_setlen(ses.writepayload, 0); + return; + } + } + + TRACE(("send_msg_channel_data: len %d fd %d", len, fd)) + buf_incrwritepos(ses.writepayload, len); + /* ... real size here */ + buf_setpos(ses.writepayload, size_pos); + buf_putint(ses.writepayload, len); + + channel->transwindow -= len; + + encrypt_packet(); + TRACE(("leave send_msg_channel_data")) +} + +/* We receive channel data */ +void recv_msg_channel_data() { + + struct Channel *channel; + + channel = getchannel(); + + common_recv_msg_channel_data(channel, channel->writefd, channel->writebuf); +} + +/* Shared for data and stderr data - when we receive data, put it in a buffer + * for writing to the local file descriptor */ +void common_recv_msg_channel_data(struct Channel *channel, int fd, + circbuffer * cbuf) { + + unsigned int datalen; + unsigned int maxdata; + unsigned int buflen; + unsigned int len; + unsigned int consumed; + int res; + + TRACE(("enter recv_msg_channel_data")) + + if (channel->recv_eof) { + dropbear_exit("Received data after eof"); + } + + if (fd < 0 || !cbuf) { + /* If we have encountered failed write, the far side might still + * be sending data without having yet received our close notification. + * We just drop the data. */ + return; + } + + datalen = buf_getint(ses.payload); + TRACE(("length %d", datalen)) + + maxdata = cbuf_getavail(cbuf); + + /* Whilst the spec says we "MAY ignore data past the end" this could + * lead to corrupted file transfers etc (chunks missed etc). It's better to + * just die horribly */ + if (datalen > maxdata) { + dropbear_exit("Oversized packet"); + } + + dropbear_assert(channel->recvwindow >= datalen); + channel->recvwindow -= datalen; + dropbear_assert(channel->recvwindow <= opts.recv_window); + + /* Attempt to write the data immediately without having to put it in the circular buffer */ + consumed = datalen; + res = writechannel(channel, fd, cbuf, buf_getptr(ses.payload, datalen), &consumed); + + datalen -= consumed; + buf_incrpos(ses.payload, consumed); + + + /* We may have to run throught twice, if the buffer wraps around. Can't + * just "leave it for next time" like with writechannel, since this + * is payload data. + * If the writechannel() failed then remaining data is discarded */ + if (res == DROPBEAR_SUCCESS) { + len = datalen; + while (len > 0) { + buflen = cbuf_writelen(cbuf); + buflen = MIN(buflen, len); + + memcpy(cbuf_writeptr(cbuf, buflen), + buf_getptr(ses.payload, buflen), buflen); + cbuf_incrwrite(cbuf, buflen); + buf_incrpos(ses.payload, buflen); + len -= buflen; + } + } + + TRACE(("leave recv_msg_channel_data")) +} + +/* Increment the outgoing data window for a channel - the remote end limits + * the amount of data which may be transmitted, this window is decremented + * as data is sent, and incremented upon receiving window-adjust messages */ +void recv_msg_channel_window_adjust() { + + struct Channel * channel; + unsigned int incr; + + channel = getchannel(); + + incr = buf_getint(ses.payload); + TRACE(("received window increment %d", incr)) + incr = MIN(incr, TRANS_MAX_WIN_INCR); + + channel->transwindow += incr; + channel->transwindow = MIN(channel->transwindow, TRANS_MAX_WINDOW); + +} + +/* Increment the incoming data window for a channel, and let the remote + * end know */ +static void send_msg_channel_window_adjust(const struct Channel* channel, + unsigned int incr) { + + TRACE(("sending window adjust %d", incr)) + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_WINDOW_ADJUST); + buf_putint(ses.writepayload, channel->remotechan); + buf_putint(ses.writepayload, incr); + + encrypt_packet(); +} + +/* Handle a new channel request, performing any channel-type-specific setup */ +void recv_msg_channel_open() { + + char *type; + unsigned int typelen; + unsigned int remotechan, transwindow, transmaxpacket; + struct Channel *channel; + const struct ChanType **cp; + const struct ChanType *chantype; + unsigned int errtype = SSH_OPEN_UNKNOWN_CHANNEL_TYPE; + int ret; + + + TRACE(("enter recv_msg_channel_open")) + + /* get the packet contents */ + type = buf_getstring(ses.payload, &typelen); + + remotechan = buf_getint(ses.payload); + transwindow = buf_getint(ses.payload); + transwindow = MIN(transwindow, TRANS_MAX_WINDOW); + transmaxpacket = buf_getint(ses.payload); + transmaxpacket = MIN(transmaxpacket, TRANS_MAX_PAYLOAD_LEN); + + /* figure what type of packet it is */ + if (typelen > MAX_NAME_LEN) { + goto failure; + } + + /* Get the channel type. Client and server style invokation will set up a + * different list for ses.chantypes at startup. We just iterate through + * this list and find the matching name */ + for (cp = &ses.chantypes[0], chantype = (*cp); + chantype != NULL; + cp++, chantype = (*cp)) { + if (strcmp(type, chantype->name) == 0) { + break; + } + } + + if (chantype == NULL) { + TRACE(("No matching type for '%s'", type)) + goto failure; + } + + TRACE(("matched type '%s'", type)) + + /* create the channel */ + channel = newchannel(remotechan, chantype, transwindow, transmaxpacket); + + if (channel == NULL) { + TRACE(("newchannel returned NULL")) + errtype = SSH_OPEN_RESOURCE_SHORTAGE; + goto failure; + } + + if (channel->type->inithandler) { + ret = channel->type->inithandler(channel); + if (ret == SSH_OPEN_IN_PROGRESS) { + /* We'll send the confirmation later */ + goto cleanup; + } + if (ret > 0) { + errtype = ret; + remove_channel(channel); + TRACE(("inithandler returned failure %d", ret)) + goto failure; + } + } + + update_channel_prio(); + + /* success */ + send_msg_channel_open_confirmation(channel, channel->recvwindow, + channel->recvmaxpacket); + goto cleanup; + +failure: + TRACE(("recv_msg_channel_open failure")) + send_msg_channel_open_failure(remotechan, errtype, "", ""); + +cleanup: + m_free(type); + + TRACE(("leave recv_msg_channel_open")) +} + +/* Send a failure message */ +void send_msg_channel_failure(const struct Channel *channel) { + + TRACE(("enter send_msg_channel_failure")) + + if (channel->sent_close) { + TRACE(("Skipping sending msg_channel_failure for closed channel")) + return; + } + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_FAILURE); + buf_putint(ses.writepayload, channel->remotechan); + + encrypt_packet(); + TRACE(("leave send_msg_channel_failure")) +} + +/* Send a success message */ +void send_msg_channel_success(const struct Channel *channel) { + + TRACE(("enter send_msg_channel_success")) + if (channel->sent_close) { + TRACE(("Skipping sending msg_channel_success for closed channel")) + return; + } + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_SUCCESS); + buf_putint(ses.writepayload, channel->remotechan); + + encrypt_packet(); + TRACE(("leave send_msg_channel_success")) +} + +/* Send a channel open failure message, with a corresponding reason + * code (usually resource shortage or unknown chan type) */ +static void send_msg_channel_open_failure(unsigned int remotechan, + int reason, const char *text, const char *lang) { + + TRACE(("enter send_msg_channel_open_failure")) + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_OPEN_FAILURE); + buf_putint(ses.writepayload, remotechan); + buf_putint(ses.writepayload, reason); + buf_putstring(ses.writepayload, text, strlen(text)); + buf_putstring(ses.writepayload, lang, strlen(lang)); + + encrypt_packet(); + TRACE(("leave send_msg_channel_open_failure")) +} + +/* Confirm a channel open, and let the remote end know what number we've + * allocated and the receive parameters */ +static void send_msg_channel_open_confirmation(const struct Channel* channel, + unsigned int recvwindow, + unsigned int recvmaxpacket) { + + TRACE(("enter send_msg_channel_open_confirmation")) + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + buf_putint(ses.writepayload, channel->remotechan); + buf_putint(ses.writepayload, channel->index); + buf_putint(ses.writepayload, recvwindow); + buf_putint(ses.writepayload, recvmaxpacket); + + encrypt_packet(); + TRACE(("leave send_msg_channel_open_confirmation")) +} + +/* close a fd, how is SHUT_RD or SHUT_WR */ +static void close_chan_fd(struct Channel *channel, int fd, int how) { + + int closein = 0, closeout = 0; + + if (channel->bidir_fd) { + TRACE(("SHUTDOWN(%d, %d)", fd, how)) + shutdown(fd, how); + if (how == 0) { + closeout = 1; + } else { + closein = 1; + } + } else { + TRACE(("CLOSE some fd %d", fd)) + m_close(fd); + closein = closeout = 1; + } + + if (closeout && (fd == channel->readfd)) { + channel->readfd = FD_CLOSED; + } + if (closeout && ERRFD_IS_READ(channel) && (fd == channel->errfd)) { + channel->errfd = FD_CLOSED; + } + + if (closein && fd == channel->writefd) { + channel->writefd = FD_CLOSED; + } + if (closein && ERRFD_IS_WRITE(channel) && (fd == channel->errfd)) { + channel->errfd = FD_CLOSED; + } + + /* if we called shutdown on it and all references are gone, then we + * need to close() it to stop it lingering */ + if (channel->bidir_fd && channel->readfd == FD_CLOSED + && channel->writefd == FD_CLOSED && channel->errfd == FD_CLOSED) { + TRACE(("CLOSE (finally) of %d", fd)) + m_close(fd); + } +} + + +#if (DROPBEAR_LISTENERS) || (DROPBEAR_CLIENT) +/* Create a new channel, and start the open request. This is intended + * for X11, agent, tcp forwarding, and should be filled with channel-specific + * options, with the calling function calling encrypt_packet() after + * completion. It is mandatory for the caller to encrypt_packet() if + * a channel is returned. NULL is returned on failure. */ +int send_msg_channel_open_init(int fd, const struct ChanType *type) { + + struct Channel* chan; + + TRACE(("enter send_msg_channel_open_init()")) + chan = newchannel(0, type, 0, 0); + if (!chan) { + TRACE(("leave send_msg_channel_open_init() - FAILED in newchannel()")) + return DROPBEAR_FAILURE; + } + + /* Outbound opened channels don't make use of in-progress connections, + * we can set it up straight away */ + + /* set fd non-blocking */ + setnonblocking(fd); + + chan->writefd = chan->readfd = fd; + ses.maxfd = MAX(ses.maxfd, fd); + chan->bidir_fd = 1; + + chan->await_open = 1; + + /* now open the channel connection */ + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_OPEN); + buf_putstring(ses.writepayload, type->name, strlen(type->name)); + buf_putint(ses.writepayload, chan->index); + buf_putint(ses.writepayload, opts.recv_window); + buf_putint(ses.writepayload, RECV_MAX_CHANNEL_DATA_LEN); + + TRACE(("leave send_msg_channel_open_init()")) + return DROPBEAR_SUCCESS; +} + +/* Confirmation that our channel open request was + * successful*/ +void recv_msg_channel_open_confirmation() { + + struct Channel * channel; + int ret; + + TRACE(("enter recv_msg_channel_open_confirmation")) + + channel = getchannel(); + + if (!channel->await_open) { + dropbear_exit("Unexpected channel reply"); + } + channel->await_open = 0; + + channel->remotechan = buf_getint(ses.payload); + channel->transwindow = buf_getint(ses.payload); + channel->transmaxpacket = buf_getint(ses.payload); + + TRACE(("new chan remote %d local %d", + channel->remotechan, channel->index)) + + /* Run the inithandler callback */ + if (channel->type->inithandler) { + ret = channel->type->inithandler(channel); + if (ret > 0) { + remove_channel(channel); + TRACE(("inithandler returned failure %d", ret)) + return; + } + } + + update_channel_prio(); + + TRACE(("leave recv_msg_channel_open_confirmation")) +} + +/* Notification that our channel open request failed */ +void recv_msg_channel_open_failure() { + + struct Channel * channel; + + channel = getchannel(); + + if (!channel->await_open) { + dropbear_exit("Unexpected channel reply"); + } + channel->await_open = 0; + + remove_channel(channel); +} +#endif /* DROPBEAR_LISTENERS */ + +void send_msg_request_success() { + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_REQUEST_SUCCESS); + encrypt_packet(); +} + +void send_msg_request_failure() { + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_REQUEST_FAILURE); + encrypt_packet(); +} + +struct Channel* get_any_ready_channel() { + size_t i; + if (ses.chancount == 0) { + return NULL; + } + for (i = 0; i < ses.chansize; i++) { + struct Channel *chan = ses.channels[i]; + if (chan + && !(chan->sent_eof || chan->recv_eof) + && !(chan->await_open)) { + return chan; + } + } + return NULL; +} + +void start_send_channel_request(const struct Channel *channel, + const char *type) { + + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST); + buf_putint(ses.writepayload, channel->remotechan); + + buf_putstring(ses.writepayload, type, strlen(type)); + +} diff --git a/src/common-chansession.c b/src/common-chansession.c new file mode 100644 index 0000000..b350c6c --- /dev/null +++ b/src/common-chansession.c @@ -0,0 +1,43 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "chansession.h" + +/* Mapping of signal values to ssh signal strings */ +const struct SigMap signames[] = { + {SIGABRT, "ABRT"}, + {SIGALRM, "ALRM"}, + {SIGFPE, "FPE"}, + {SIGHUP, "HUP"}, + {SIGILL, "ILL"}, + {SIGINT, "INT"}, + {SIGKILL, "KILL"}, + {SIGPIPE, "PIPE"}, + {SIGQUIT, "QUIT"}, + {SIGSEGV, "SEGV"}, + {SIGTERM, "TERM"}, + {SIGUSR1, "USR1"}, + {SIGUSR2, "USR2"}, + {0, NULL} +}; diff --git a/src/common-kex.c b/src/common-kex.c new file mode 100644 index 0000000..ac88442 --- /dev/null +++ b/src/common-kex.c @@ -0,0 +1,1021 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002-2004 Matt Johnston + * Portions Copyright (c) 2004 by Mihnea Stoenescu + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "algo.h" +#include "buffer.h" +#include "session.h" +#include "kex.h" +#include "dh_groups.h" +#include "ssh.h" +#include "packet.h" +#include "bignum.h" +#include "dbrandom.h" +#include "runopts.h" +#include "ecc.h" +#include "curve25519.h" +#include "crypto_desc.h" + +static void kexinitialise(void); +static void gen_new_keys(void); +#ifndef DISABLE_ZLIB +static void gen_new_zstream_recv(void); +static void gen_new_zstream_trans(void); +#endif +static void read_kex_algos(void); +/* helper function for gen_new_keys */ +static void hashkeys(unsigned char *out, unsigned int outlen, + const hash_state * hs, const unsigned char X); + + +/* Send our list of algorithms we can use */ +void send_msg_kexinit() { + + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_KEXINIT); + + /* cookie */ + genrandom(buf_getwriteptr(ses.writepayload, 16), 16); + buf_incrwritepos(ses.writepayload, 16); + + /* kex algos */ + buf_put_algolist(ses.writepayload, sshkex); + + /* server_host_key_algorithms */ + buf_put_algolist(ses.writepayload, sigalgs); + + /* encryption_algorithms_client_to_server */ + buf_put_algolist(ses.writepayload, sshciphers); + + /* encryption_algorithms_server_to_client */ + buf_put_algolist(ses.writepayload, sshciphers); + + /* mac_algorithms_client_to_server */ + buf_put_algolist(ses.writepayload, sshhashes); + + /* mac_algorithms_server_to_client */ + buf_put_algolist(ses.writepayload, sshhashes); + + + /* compression_algorithms_client_to_server */ + buf_put_algolist(ses.writepayload, ses.compress_algos); + + /* compression_algorithms_server_to_client */ + buf_put_algolist(ses.writepayload, ses.compress_algos); + + /* languages_client_to_server */ + buf_putstring(ses.writepayload, "", 0); + + /* languages_server_to_client */ + buf_putstring(ses.writepayload, "", 0); + + /* first_kex_packet_follows */ + buf_putbyte(ses.writepayload, (ses.send_kex_first_guess != NULL)); + + /* reserved unit32 */ + buf_putint(ses.writepayload, 0); + + /* set up transmitted kex packet buffer for hashing. + * This is freed after the end of the kex */ + ses.transkexinit = buf_newcopy(ses.writepayload); + + encrypt_packet(); + ses.dataallowed = 0; /* don't send other packets during kex */ + + ses.kexstate.sentkexinit = 1; + + ses.newkeys = (struct key_context*)m_malloc(sizeof(struct key_context)); + + if (ses.send_kex_first_guess) { + ses.newkeys->algo_kex = first_usable_algo(sshkex)->data; + ses.newkeys->algo_signature = first_usable_algo(sigalgs)->val; + ses.newkeys->algo_hostkey = signkey_type_from_signature(ses.newkeys->algo_signature); + ses.send_kex_first_guess(); + } + + TRACE(("DATAALLOWED=0")) + TRACE(("-> KEXINIT")) + +} + +static void switch_keys() { + TRACE2(("enter switch_keys")) + if (!(ses.kexstate.sentkexinit && ses.kexstate.recvkexinit)) { + dropbear_exit("Unexpected newkeys message"); + } + + if (!ses.keys) { + ses.keys = m_malloc(sizeof(*ses.newkeys)); + } + if (ses.kexstate.recvnewkeys && ses.newkeys->recv.valid) { + TRACE(("switch_keys recv")) +#ifndef DISABLE_ZLIB + gen_new_zstream_recv(); +#endif + ses.keys->recv = ses.newkeys->recv; + m_burn(&ses.newkeys->recv, sizeof(ses.newkeys->recv)); + ses.newkeys->recv.valid = 0; + } + if (ses.kexstate.sentnewkeys && ses.newkeys->trans.valid) { + TRACE(("switch_keys trans")) +#ifndef DISABLE_ZLIB + gen_new_zstream_trans(); +#endif + ses.keys->trans = ses.newkeys->trans; + m_burn(&ses.newkeys->trans, sizeof(ses.newkeys->trans)); + ses.newkeys->trans.valid = 0; + } + if (ses.kexstate.sentnewkeys && ses.kexstate.recvnewkeys) + { + TRACE(("switch_keys done")) + ses.keys->algo_kex = ses.newkeys->algo_kex; + ses.keys->algo_hostkey = ses.newkeys->algo_hostkey; + ses.keys->algo_signature = ses.newkeys->algo_signature; + ses.keys->allow_compress = 0; + m_free(ses.newkeys); + ses.newkeys = NULL; + kexinitialise(); + } + TRACE2(("leave switch_keys")) +} + +/* Bring new keys into use after a key exchange, and let the client know*/ +void send_msg_newkeys() { + + TRACE(("enter send_msg_newkeys")) + + /* generate the kexinit request */ + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_NEWKEYS); + encrypt_packet(); + + + /* set up our state */ + ses.kexstate.sentnewkeys = 1; + if (ses.kexstate.donefirstkex) { + ses.kexstate.donesecondkex = 1; + } + ses.kexstate.donefirstkex = 1; + ses.dataallowed = 1; /* we can send other packets again now */ + gen_new_keys(); + switch_keys(); + + TRACE(("leave send_msg_newkeys")) +} + +/* Bring the new keys into use after a key exchange */ +void recv_msg_newkeys() { + + TRACE(("enter recv_msg_newkeys")) + + ses.kexstate.recvnewkeys = 1; + switch_keys(); + + TRACE(("leave recv_msg_newkeys")) +} + + +/* Set up the kex for the first time */ +void kexfirstinitialise() { +#ifdef DISABLE_ZLIB + ses.compress_algos = ssh_nocompress; +#else + switch (opts.compress_mode) + { + case DROPBEAR_COMPRESS_DELAYED: + ses.compress_algos = ssh_delaycompress; + break; + + case DROPBEAR_COMPRESS_ON: + ses.compress_algos = ssh_compress; + break; + + case DROPBEAR_COMPRESS_OFF: + ses.compress_algos = ssh_nocompress; + break; + } +#endif + kexinitialise(); +} + +/* Reset the kex state, ready for a new negotiation */ +static void kexinitialise() { + + TRACE(("kexinitialise()")) + + /* sent/recv'd MSG_KEXINIT */ + ses.kexstate.sentkexinit = 0; + ses.kexstate.recvkexinit = 0; + + /* sent/recv'd MSG_NEWKEYS */ + ses.kexstate.recvnewkeys = 0; + ses.kexstate.sentnewkeys = 0; + + /* first_packet_follows */ + ses.kexstate.them_firstfollows = 0; + + ses.kexstate.datatrans = 0; + ses.kexstate.datarecv = 0; + + ses.kexstate.our_first_follows_matches = 0; + + ses.kexstate.lastkextime = monotonic_now(); + +} + +/* Helper function for gen_new_keys, creates a hash. It makes a copy of the + * already initialised hash_state hs, which should already have processed + * the dh_K and hash, since these are common. X is the letter 'A', 'B' etc. + * out must have at least min(hash_size, outlen) bytes allocated. + * + * See Section 7.2 of rfc4253 (ssh transport) for details */ +static void hashkeys(unsigned char *out, unsigned int outlen, + const hash_state * hs, const unsigned char X) { + + const struct ltc_hash_descriptor *hash_desc = ses.newkeys->algo_kex->hash_desc; + hash_state hs2; + unsigned int offset; + unsigned char tmpout[MAX_HASH_SIZE]; + + memcpy(&hs2, hs, sizeof(hash_state)); + hash_desc->process(&hs2, &X, 1); + hash_desc->process(&hs2, ses.session_id->data, ses.session_id->len); + hash_desc->done(&hs2, tmpout); + memcpy(out, tmpout, MIN(hash_desc->hashsize, outlen)); + for (offset = hash_desc->hashsize; + offset < outlen; + offset += hash_desc->hashsize) + { + /* need to extend */ + memcpy(&hs2, hs, sizeof(hash_state)); + hash_desc->process(&hs2, out, offset); + hash_desc->done(&hs2, tmpout); + memcpy(&out[offset], tmpout, MIN(outlen - offset, hash_desc->hashsize)); + } + m_burn(&hs2, sizeof(hash_state)); +} + +/* Generate the actual encryption/integrity keys, using the results of the + * key exchange, as specified in section 7.2 of the transport rfc 4253. + * This occurs after the DH key-exchange. + * + * ses.newkeys is the new set of keys which are generated, these are only + * taken into use after both sides have sent a newkeys message */ + +static void gen_new_keys() { + + unsigned char C2S_IV[MAX_IV_LEN]; + unsigned char C2S_key[MAX_KEY_LEN]; + unsigned char S2C_IV[MAX_IV_LEN]; + unsigned char S2C_key[MAX_KEY_LEN]; + /* unsigned char key[MAX_KEY_LEN]; */ + unsigned char *trans_IV, *trans_key, *recv_IV, *recv_key; + + hash_state hs; + const struct ltc_hash_descriptor *hash_desc = ses.newkeys->algo_kex->hash_desc; + char mactransletter, macrecvletter; /* Client or server specific */ + + TRACE(("enter gen_new_keys")) + /* the dh_K and hash are the start of all hashes, we make use of that */ + + hash_desc->init(&hs); + hash_process_mp(hash_desc, &hs, ses.dh_K); + mp_clear(ses.dh_K); + m_free(ses.dh_K); + hash_desc->process(&hs, ses.hash->data, ses.hash->len); + buf_burn_free(ses.hash); + ses.hash = NULL; + + if (IS_DROPBEAR_CLIENT) { + trans_IV = C2S_IV; + recv_IV = S2C_IV; + trans_key = C2S_key; + recv_key = S2C_key; + mactransletter = 'E'; + macrecvletter = 'F'; + } else { + trans_IV = S2C_IV; + recv_IV = C2S_IV; + trans_key = S2C_key; + recv_key = C2S_key; + mactransletter = 'F'; + macrecvletter = 'E'; + } + + hashkeys(C2S_IV, sizeof(C2S_IV), &hs, 'A'); + hashkeys(S2C_IV, sizeof(S2C_IV), &hs, 'B'); + hashkeys(C2S_key, sizeof(C2S_key), &hs, 'C'); + hashkeys(S2C_key, sizeof(S2C_key), &hs, 'D'); + + if (ses.newkeys->recv.algo_crypt->cipherdesc != NULL) { + int recv_cipher = -1; + if (ses.newkeys->recv.algo_crypt->cipherdesc->name != NULL) { + recv_cipher = find_cipher(ses.newkeys->recv.algo_crypt->cipherdesc->name); + if (recv_cipher < 0) { + dropbear_exit("Crypto error"); + } + } + if (ses.newkeys->recv.crypt_mode->start(recv_cipher, + recv_IV, recv_key, + ses.newkeys->recv.algo_crypt->keysize, 0, + &ses.newkeys->recv.cipher_state) != CRYPT_OK) { + dropbear_exit("Crypto error"); + } + } + + if (ses.newkeys->trans.algo_crypt->cipherdesc != NULL) { + int trans_cipher = -1; + if (ses.newkeys->trans.algo_crypt->cipherdesc->name != NULL) { + trans_cipher = find_cipher(ses.newkeys->trans.algo_crypt->cipherdesc->name); + if (trans_cipher < 0) { + dropbear_exit("Crypto error"); + } + } + if (ses.newkeys->trans.crypt_mode->start(trans_cipher, + trans_IV, trans_key, + ses.newkeys->trans.algo_crypt->keysize, 0, + &ses.newkeys->trans.cipher_state) != CRYPT_OK) { + dropbear_exit("Crypto error"); + } + } + + if (ses.newkeys->trans.algo_mac->hash_desc != NULL) { + hashkeys(ses.newkeys->trans.mackey, + ses.newkeys->trans.algo_mac->keysize, &hs, mactransletter); + ses.newkeys->trans.hash_index = find_hash(ses.newkeys->trans.algo_mac->hash_desc->name); + } + + if (ses.newkeys->recv.algo_mac->hash_desc != NULL) { + hashkeys(ses.newkeys->recv.mackey, + ses.newkeys->recv.algo_mac->keysize, &hs, macrecvletter); + ses.newkeys->recv.hash_index = find_hash(ses.newkeys->recv.algo_mac->hash_desc->name); + } + + /* Ready to switch over */ + ses.newkeys->trans.valid = 1; + ses.newkeys->recv.valid = 1; + + m_burn(C2S_IV, sizeof(C2S_IV)); + m_burn(C2S_key, sizeof(C2S_key)); + m_burn(S2C_IV, sizeof(S2C_IV)); + m_burn(S2C_key, sizeof(S2C_key)); + m_burn(&hs, sizeof(hash_state)); + + TRACE(("leave gen_new_keys")) +} + +#ifndef DISABLE_ZLIB + +int is_compress_trans() { + return ses.keys->trans.algo_comp == DROPBEAR_COMP_ZLIB + || (ses.authstate.authdone + && ses.keys->trans.algo_comp == DROPBEAR_COMP_ZLIB_DELAY); +} + +int is_compress_recv() { + return ses.keys->recv.algo_comp == DROPBEAR_COMP_ZLIB + || (ses.authstate.authdone + && ses.keys->recv.algo_comp == DROPBEAR_COMP_ZLIB_DELAY); +} + +static void* dropbear_zalloc(void* UNUSED(opaque), uInt items, uInt size) { + return m_calloc(items, size); +} + +static void dropbear_zfree(void* UNUSED(opaque), void* ptr) { + m_free(ptr); +} + +/* Set up new zlib compression streams, close the old ones. Only + * called from gen_new_keys() */ +static void gen_new_zstream_recv() { + + /* create new zstreams */ + if (ses.newkeys->recv.algo_comp == DROPBEAR_COMP_ZLIB + || ses.newkeys->recv.algo_comp == DROPBEAR_COMP_ZLIB_DELAY) { + ses.newkeys->recv.zstream = (z_streamp)m_malloc(sizeof(z_stream)); + ses.newkeys->recv.zstream->zalloc = dropbear_zalloc; + ses.newkeys->recv.zstream->zfree = dropbear_zfree; + + if (inflateInit(ses.newkeys->recv.zstream) != Z_OK) { + dropbear_exit("zlib error"); + } + } else { + ses.newkeys->recv.zstream = NULL; + } + /* clean up old keys */ + if (ses.keys->recv.zstream != NULL) { + if (inflateEnd(ses.keys->recv.zstream) == Z_STREAM_ERROR) { + /* Z_DATA_ERROR is ok, just means that stream isn't ended */ + dropbear_exit("Crypto error"); + } + m_free(ses.keys->recv.zstream); + } +} + +static void gen_new_zstream_trans() { + + if (ses.newkeys->trans.algo_comp == DROPBEAR_COMP_ZLIB + || ses.newkeys->trans.algo_comp == DROPBEAR_COMP_ZLIB_DELAY) { + ses.newkeys->trans.zstream = (z_streamp)m_malloc(sizeof(z_stream)); + ses.newkeys->trans.zstream->zalloc = dropbear_zalloc; + ses.newkeys->trans.zstream->zfree = dropbear_zfree; + + if (deflateInit2(ses.newkeys->trans.zstream, Z_DEFAULT_COMPRESSION, + Z_DEFLATED, DROPBEAR_ZLIB_WINDOW_BITS, + DROPBEAR_ZLIB_MEM_LEVEL, Z_DEFAULT_STRATEGY) + != Z_OK) { + dropbear_exit("zlib error"); + } + } else { + ses.newkeys->trans.zstream = NULL; + } + + if (ses.keys->trans.zstream != NULL) { + if (deflateEnd(ses.keys->trans.zstream) == Z_STREAM_ERROR) { + /* Z_DATA_ERROR is ok, just means that stream isn't ended */ + dropbear_exit("Crypto error"); + } + m_free(ses.keys->trans.zstream); + } +} +#endif /* DISABLE_ZLIB */ + + +/* Executed upon receiving a kexinit message from the client to initiate + * key exchange. If we haven't already done so, we send the list of our + * preferred algorithms. The client's requested algorithms are processed, + * and we calculate the first portion of the key-exchange-hash for used + * later in the key exchange. No response is sent, as the client should + * initiate the diffie-hellman key exchange */ +void recv_msg_kexinit() { + + unsigned int kexhashbuf_len = 0; + unsigned int remote_ident_len = 0; + unsigned int local_ident_len = 0; + + TRACE(("<- KEXINIT")) + TRACE(("enter recv_msg_kexinit")) + + if (!ses.kexstate.sentkexinit) { + /* we need to send a kex packet */ + send_msg_kexinit(); + TRACE(("continue recv_msg_kexinit: sent kexinit")) + } + + /* "Once a party has sent a SSH_MSG_KEXINIT message ... + further SSH_MSG_KEXINIT messages MUST NOT be sent" */ + if (ses.kexstate.recvkexinit) { + dropbear_exit("Unexpected KEXINIT"); + } + + /* start the kex hash */ + local_ident_len = strlen(LOCAL_IDENT); + remote_ident_len = strlen(ses.remoteident); + + kexhashbuf_len = local_ident_len + remote_ident_len + + ses.transkexinit->len + ses.payload->len + + KEXHASHBUF_MAX_INTS; + + ses.kexhashbuf = buf_new(kexhashbuf_len); + + if (IS_DROPBEAR_CLIENT) { + + /* read the peer's choice of algos */ + read_kex_algos(); + + /* V_C, the client's version string (CR and NL excluded) */ + buf_putstring(ses.kexhashbuf, LOCAL_IDENT, local_ident_len); + /* V_S, the server's version string (CR and NL excluded) */ + buf_putstring(ses.kexhashbuf, ses.remoteident, remote_ident_len); + + /* I_C, the payload of the client's SSH_MSG_KEXINIT */ + buf_putstring(ses.kexhashbuf, + (const char*)ses.transkexinit->data, ses.transkexinit->len); + /* I_S, the payload of the server's SSH_MSG_KEXINIT */ + buf_setpos(ses.payload, ses.payload_beginning); + buf_putstring(ses.kexhashbuf, + (const char*)buf_getptr(ses.payload, ses.payload->len-ses.payload->pos), + ses.payload->len-ses.payload->pos); + ses.requirenext = SSH_MSG_KEXDH_REPLY; + } else { + /* SERVER */ + + /* read the peer's choice of algos */ + read_kex_algos(); + /* V_C, the client's version string (CR and NL excluded) */ + buf_putstring(ses.kexhashbuf, ses.remoteident, remote_ident_len); + /* V_S, the server's version string (CR and NL excluded) */ + buf_putstring(ses.kexhashbuf, LOCAL_IDENT, local_ident_len); + + /* I_C, the payload of the client's SSH_MSG_KEXINIT */ + buf_setpos(ses.payload, ses.payload_beginning); + buf_putstring(ses.kexhashbuf, + (const char*)buf_getptr(ses.payload, ses.payload->len-ses.payload->pos), + ses.payload->len-ses.payload->pos); + + /* I_S, the payload of the server's SSH_MSG_KEXINIT */ + buf_putstring(ses.kexhashbuf, + (const char*)ses.transkexinit->data, ses.transkexinit->len); + + ses.requirenext = SSH_MSG_KEXDH_INIT; + } + + buf_free(ses.transkexinit); + ses.transkexinit = NULL; + /* the rest of ses.kexhashbuf will be done after DH exchange */ + + ses.kexstate.recvkexinit = 1; + + TRACE(("leave recv_msg_kexinit")) +} + +#if DROPBEAR_NORMAL_DH +static void load_dh_p(mp_int * dh_p) +{ + bytes_to_mp(dh_p, ses.newkeys->algo_kex->dh_p_bytes, + ses.newkeys->algo_kex->dh_p_len); +} + +/* Initialises and generate one side of the diffie-hellman key exchange values. + * See the transport rfc 4253 section 8 for details */ +/* dh_pub and dh_priv MUST be already initialised */ +struct kex_dh_param *gen_kexdh_param() { + struct kex_dh_param *param = NULL; + + DEF_MP_INT(dh_p); + DEF_MP_INT(dh_q); + DEF_MP_INT(dh_g); + + TRACE(("enter gen_kexdh_vals")) + + param = m_malloc(sizeof(*param)); + m_mp_init_multi(¶m->pub, ¶m->priv, &dh_g, &dh_p, &dh_q, NULL); + + /* read the prime and generator*/ + load_dh_p(&dh_p); + + mp_set_ul(&dh_g, DH_G_VAL); + + /* calculate q = (p-1)/2 */ + /* dh_priv is just a temp var here */ + if (mp_sub_d(&dh_p, 1, ¶m->priv) != MP_OKAY) { + dropbear_exit("Diffie-Hellman error"); + } + if (mp_div_2(¶m->priv, &dh_q) != MP_OKAY) { + dropbear_exit("Diffie-Hellman error"); + } + + /* Generate a private portion 0 < dh_priv < dh_q */ + gen_random_mpint(&dh_q, ¶m->priv); + + /* f = g^y mod p */ + if (mp_exptmod(&dh_g, ¶m->priv, &dh_p, ¶m->pub) != MP_OKAY) { + dropbear_exit("Diffie-Hellman error"); + } + mp_clear_multi(&dh_g, &dh_p, &dh_q, NULL); + return param; +} + +void free_kexdh_param(struct kex_dh_param *param) +{ + mp_clear_multi(¶m->pub, ¶m->priv, NULL); + m_free(param); +} + +/* This function is fairly common between client/server, with some substitution + * of dh_e/dh_f etc. Hence these arguments: + * dh_pub_us is 'e' for the client, 'f' for the server. dh_pub_them is + * vice-versa. dh_priv is the x/y value corresponding to dh_pub_us */ +void kexdh_comb_key(struct kex_dh_param *param, mp_int *dh_pub_them, + sign_key *hostkey) { + + DEF_MP_INT(dh_p); + DEF_MP_INT(dh_p_min1); + mp_int *dh_e = NULL, *dh_f = NULL; + + m_mp_init_multi(&dh_p, &dh_p_min1, NULL); + load_dh_p(&dh_p); + + if (mp_sub_d(&dh_p, 1, &dh_p_min1) != MP_OKAY) { + dropbear_exit("Diffie-Hellman error"); + } + + /* Check that dh_pub_them (dh_e or dh_f) is in the range [2, p-2] */ + if (mp_cmp(dh_pub_them, &dh_p_min1) != MP_LT + || mp_cmp_d(dh_pub_them, 1) != MP_GT) { + dropbear_exit("Diffie-Hellman error"); + } + + /* K = e^y mod p = f^x mod p */ + m_mp_alloc_init_multi(&ses.dh_K, NULL); + if (mp_exptmod(dh_pub_them, ¶m->priv, &dh_p, ses.dh_K) != MP_OKAY) { + dropbear_exit("Diffie-Hellman error"); + } + + /* clear no longer needed vars */ + mp_clear_multi(&dh_p, &dh_p_min1, NULL); + + /* From here on, the code needs to work with the _same_ vars on each side, + * not vice-versaing for client/server */ + if (IS_DROPBEAR_CLIENT) { + dh_e = ¶m->pub; + dh_f = dh_pub_them; + } else { + dh_e = dh_pub_them; + dh_f = ¶m->pub; + } + + /* Create the remainder of the hash buffer, to generate the exchange hash */ + /* K_S, the host key */ + buf_put_pub_key(ses.kexhashbuf, hostkey, ses.newkeys->algo_hostkey); + /* e, exchange value sent by the client */ + buf_putmpint(ses.kexhashbuf, dh_e); + /* f, exchange value sent by the server */ + buf_putmpint(ses.kexhashbuf, dh_f); + /* K, the shared secret */ + buf_putmpint(ses.kexhashbuf, ses.dh_K); + + /* calculate the hash H to sign */ + finish_kexhashbuf(); +} +#endif + +#if DROPBEAR_ECDH +struct kex_ecdh_param *gen_kexecdh_param() { + struct kex_ecdh_param *param = m_malloc(sizeof(*param)); + if (ecc_make_key_ex(NULL, dropbear_ltc_prng, + ¶m->key, ses.newkeys->algo_kex->ecc_curve->dp) != CRYPT_OK) { + dropbear_exit("ECC error"); + } + return param; +} + +void free_kexecdh_param(struct kex_ecdh_param *param) { + ecc_free(¶m->key); + m_free(param); + +} +void kexecdh_comb_key(struct kex_ecdh_param *param, buffer *pub_them, + sign_key *hostkey) { + const struct dropbear_kex *algo_kex = ses.newkeys->algo_kex; + /* public keys from client and server */ + ecc_key *Q_C, *Q_S, *Q_them; + + Q_them = buf_get_ecc_raw_pubkey(pub_them, algo_kex->ecc_curve); + if (Q_them == NULL) { + dropbear_exit("ECC error"); + } + + ses.dh_K = dropbear_ecc_shared_secret(Q_them, ¶m->key); + + /* Create the remainder of the hash buffer, to generate the exchange hash + See RFC5656 section 4 page 7 */ + if (IS_DROPBEAR_CLIENT) { + Q_C = ¶m->key; + Q_S = Q_them; + } else { + Q_C = Q_them; + Q_S = ¶m->key; + } + + /* K_S, the host key */ + buf_put_pub_key(ses.kexhashbuf, hostkey, ses.newkeys->algo_hostkey); + /* Q_C, client's ephemeral public key octet string */ + buf_put_ecc_raw_pubkey_string(ses.kexhashbuf, Q_C); + /* Q_S, server's ephemeral public key octet string */ + buf_put_ecc_raw_pubkey_string(ses.kexhashbuf, Q_S); + /* K, the shared secret */ + buf_putmpint(ses.kexhashbuf, ses.dh_K); + + ecc_free(Q_them); + m_free(Q_them); + + /* calculate the hash H to sign */ + finish_kexhashbuf(); +} +#endif /* DROPBEAR_ECDH */ + +#if DROPBEAR_CURVE25519 +struct kex_curve25519_param *gen_kexcurve25519_param() { + /* Per http://cr.yp.to/ecdh.html */ + struct kex_curve25519_param *param = m_malloc(sizeof(*param)); + const unsigned char basepoint[32] = {9}; + + genrandom(param->priv, CURVE25519_LEN); + dropbear_curve25519_scalarmult(param->pub, param->priv, basepoint); + + return param; +} + +void free_kexcurve25519_param(struct kex_curve25519_param *param) { + m_burn(param->priv, CURVE25519_LEN); + m_free(param); +} + +void kexcurve25519_comb_key(const struct kex_curve25519_param *param, const buffer *buf_pub_them, + sign_key *hostkey) { + unsigned char out[CURVE25519_LEN]; + const unsigned char* Q_C = NULL; + const unsigned char* Q_S = NULL; + char zeroes[CURVE25519_LEN] = {0}; + + if (buf_pub_them->len != CURVE25519_LEN) + { + dropbear_exit("Bad curve25519"); + } + + dropbear_curve25519_scalarmult(out, param->priv, buf_pub_them->data); + + if (constant_time_memcmp(zeroes, out, CURVE25519_LEN) == 0) { + dropbear_exit("Bad curve25519"); + } + + m_mp_alloc_init_multi(&ses.dh_K, NULL); + bytes_to_mp(ses.dh_K, out, CURVE25519_LEN); + m_burn(out, sizeof(out)); + + /* Create the remainder of the hash buffer, to generate the exchange hash. + See RFC5656 section 4 page 7 */ + if (IS_DROPBEAR_CLIENT) { + Q_C = param->pub; + Q_S = buf_pub_them->data; + } else { + Q_S = param->pub; + Q_C = buf_pub_them->data; + } + + /* K_S, the host key */ + buf_put_pub_key(ses.kexhashbuf, hostkey, ses.newkeys->algo_hostkey); + /* Q_C, client's ephemeral public key octet string */ + buf_putstring(ses.kexhashbuf, (const char*)Q_C, CURVE25519_LEN); + /* Q_S, server's ephemeral public key octet string */ + buf_putstring(ses.kexhashbuf, (const char*)Q_S, CURVE25519_LEN); + /* K, the shared secret */ + buf_putmpint(ses.kexhashbuf, ses.dh_K); + + /* calculate the hash H to sign */ + finish_kexhashbuf(); +} +#endif /* DROPBEAR_CURVE25519 */ + + +void finish_kexhashbuf(void) { + hash_state hs; + const struct ltc_hash_descriptor *hash_desc = ses.newkeys->algo_kex->hash_desc; + + hash_desc->init(&hs); + buf_setpos(ses.kexhashbuf, 0); + hash_desc->process(&hs, buf_getptr(ses.kexhashbuf, ses.kexhashbuf->len), + ses.kexhashbuf->len); + ses.hash = buf_new(hash_desc->hashsize); + hash_desc->done(&hs, buf_getwriteptr(ses.hash, hash_desc->hashsize)); + buf_setlen(ses.hash, hash_desc->hashsize); + +#if defined(DEBUG_KEXHASH) && DEBUG_TRACE + if (!debug_trace) { + printhex("kexhashbuf", ses.kexhashbuf->data, ses.kexhashbuf->len); + printhex("kexhash", ses.hash->data, ses.hash->len); + } +#endif + + buf_burn_free(ses.kexhashbuf); + m_burn(&hs, sizeof(hash_state)); + ses.kexhashbuf = NULL; + + /* first time around, we set the session_id to H */ + if (ses.session_id == NULL) { + /* create the session_id, this never needs freeing */ + ses.session_id = buf_newcopy(ses.hash); + } +} + +/* read the other side's algo list. buf_match_algo is a callback to match + * algos for the client or server. */ +static void read_kex_algos() { + + /* for asymmetry */ + algo_type * c2s_hash_algo = NULL; + algo_type * s2c_hash_algo = NULL; + algo_type * c2s_cipher_algo = NULL; + algo_type * s2c_cipher_algo = NULL; + algo_type * c2s_comp_algo = NULL; + algo_type * s2c_comp_algo = NULL; + /* the generic one */ + algo_type * algo = NULL; + + /* which algo couldn't match */ + char * erralgo = NULL; + + int goodguess = 0; + int allgood = 1; /* we AND this with each goodguess and see if its still + true after */ + int kexguess2 = 0; + + buf_incrpos(ses.payload, 16); /* start after the cookie */ + + memset(ses.newkeys, 0x0, sizeof(*ses.newkeys)); + + /* kex_algorithms */ +#if DROPBEAR_KEXGUESS2 + if (buf_has_algo(ses.payload, KEXGUESS2_ALGO_NAME) == DROPBEAR_SUCCESS) { + kexguess2 = 1; + } +#endif + +#if DROPBEAR_EXT_INFO + /* Determine if SSH_MSG_EXT_INFO messages should be sent. + Should be done for the first key exchange. Only required on server side + for server-sig-algs */ + if (IS_DROPBEAR_SERVER) { + if (!ses.kexstate.donefirstkex) { + if (buf_has_algo(ses.payload, SSH_EXT_INFO_C) == DROPBEAR_SUCCESS) { + ses.allow_ext_info = 1; + } + } + } +#endif + + algo = buf_match_algo(ses.payload, sshkex, kexguess2, &goodguess); + allgood &= goodguess; + if (algo == NULL || algo->data == NULL) { + /* kexguess2, ext-info-c, ext-info-s should not match negotiation */ + erralgo = "kex"; + goto error; + } + TRACE(("kexguess2 %d", kexguess2)) + DEBUG3(("kex algo %s", algo->name)) + ses.newkeys->algo_kex = algo->data; + + /* server_host_key_algorithms */ + algo = buf_match_algo(ses.payload, sigalgs, kexguess2, &goodguess); + allgood &= goodguess; + if (algo == NULL) { + erralgo = "hostkey"; + goto error; + } + DEBUG2(("hostkey algo %s", algo->name)) + ses.newkeys->algo_signature = algo->val; + ses.newkeys->algo_hostkey = signkey_type_from_signature(ses.newkeys->algo_signature); + + /* encryption_algorithms_client_to_server */ + c2s_cipher_algo = buf_match_algo(ses.payload, sshciphers, 0, NULL); + if (c2s_cipher_algo == NULL) { + erralgo = "enc c->s"; + goto error; + } + DEBUG2(("enc c2s is %s", c2s_cipher_algo->name)) + + /* encryption_algorithms_server_to_client */ + s2c_cipher_algo = buf_match_algo(ses.payload, sshciphers, 0, NULL); + if (s2c_cipher_algo == NULL) { + erralgo = "enc s->c"; + goto error; + } + DEBUG2(("enc s2c is %s", s2c_cipher_algo->name)) + + /* mac_algorithms_client_to_server */ + c2s_hash_algo = buf_match_algo(ses.payload, sshhashes, 0, NULL); +#if DROPBEAR_AEAD_MODE + if (((struct dropbear_cipher_mode*)c2s_cipher_algo->mode)->aead_crypt != NULL) { + c2s_hash_algo = NULL; + } else +#endif + if (c2s_hash_algo == NULL) { + erralgo = "mac c->s"; + goto error; + } + DEBUG2(("hmac c2s is %s", c2s_hash_algo ? c2s_hash_algo->name : "<implicit>")) + + /* mac_algorithms_server_to_client */ + s2c_hash_algo = buf_match_algo(ses.payload, sshhashes, 0, NULL); +#if DROPBEAR_AEAD_MODE + if (((struct dropbear_cipher_mode*)s2c_cipher_algo->mode)->aead_crypt != NULL) { + s2c_hash_algo = NULL; + } else +#endif + if (s2c_hash_algo == NULL) { + erralgo = "mac s->c"; + goto error; + } + DEBUG2(("hmac s2c is %s", s2c_hash_algo ? s2c_hash_algo->name : "<implicit>")) + + /* compression_algorithms_client_to_server */ + c2s_comp_algo = buf_match_algo(ses.payload, ses.compress_algos, 0, NULL); + if (c2s_comp_algo == NULL) { + erralgo = "comp c->s"; + goto error; + } + DEBUG2(("comp c2s is %s", c2s_comp_algo->name)) + + /* compression_algorithms_server_to_client */ + s2c_comp_algo = buf_match_algo(ses.payload, ses.compress_algos, 0, NULL); + if (s2c_comp_algo == NULL) { + erralgo = "comp s->c"; + goto error; + } + DEBUG2(("comp s2c is %s", s2c_comp_algo->name)) + + /* languages_client_to_server */ + buf_eatstring(ses.payload); + + /* languages_server_to_client */ + buf_eatstring(ses.payload); + + /* their first_kex_packet_follows */ + if (buf_getbool(ses.payload)) { + TRACE(("them kex firstfollows. allgood %d", allgood)) + ses.kexstate.them_firstfollows = 1; + /* if the guess wasn't good, we ignore the packet sent */ + if (!allgood) { + ses.ignorenext = 1; + } + } + + /* Handle the asymmetry */ + if (IS_DROPBEAR_CLIENT) { + ses.newkeys->recv.algo_crypt = + (struct dropbear_cipher*)s2c_cipher_algo->data; + ses.newkeys->trans.algo_crypt = + (struct dropbear_cipher*)c2s_cipher_algo->data; + ses.newkeys->recv.crypt_mode = + (struct dropbear_cipher_mode*)s2c_cipher_algo->mode; + ses.newkeys->trans.crypt_mode = + (struct dropbear_cipher_mode*)c2s_cipher_algo->mode; + ses.newkeys->recv.algo_mac = +#if DROPBEAR_AEAD_MODE + s2c_hash_algo == NULL ? ses.newkeys->recv.crypt_mode->aead_mac : +#endif + (struct dropbear_hash*)s2c_hash_algo->data; + ses.newkeys->trans.algo_mac = +#if DROPBEAR_AEAD_MODE + c2s_hash_algo == NULL ? ses.newkeys->trans.crypt_mode->aead_mac : +#endif + (struct dropbear_hash*)c2s_hash_algo->data; + ses.newkeys->recv.algo_comp = s2c_comp_algo->val; + ses.newkeys->trans.algo_comp = c2s_comp_algo->val; + } else { + /* SERVER */ + ses.newkeys->recv.algo_crypt = + (struct dropbear_cipher*)c2s_cipher_algo->data; + ses.newkeys->trans.algo_crypt = + (struct dropbear_cipher*)s2c_cipher_algo->data; + ses.newkeys->recv.crypt_mode = + (struct dropbear_cipher_mode*)c2s_cipher_algo->mode; + ses.newkeys->trans.crypt_mode = + (struct dropbear_cipher_mode*)s2c_cipher_algo->mode; + ses.newkeys->recv.algo_mac = +#if DROPBEAR_AEAD_MODE + c2s_hash_algo == NULL ? ses.newkeys->recv.crypt_mode->aead_mac : +#endif + (struct dropbear_hash*)c2s_hash_algo->data; + ses.newkeys->trans.algo_mac = +#if DROPBEAR_AEAD_MODE + s2c_hash_algo == NULL ? ses.newkeys->trans.crypt_mode->aead_mac : +#endif + (struct dropbear_hash*)s2c_hash_algo->data; + ses.newkeys->recv.algo_comp = c2s_comp_algo->val; + ses.newkeys->trans.algo_comp = s2c_comp_algo->val; + } + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + fuzz_kex_fakealgos(); + } +#endif + + /* reserved for future extensions */ + buf_getint(ses.payload); + + if (ses.send_kex_first_guess && allgood) { + TRACE(("our_first_follows_matches 1")) + ses.kexstate.our_first_follows_matches = 1; + } + return; + +error: + dropbear_exit("No matching algo %s", erralgo); +} diff --git a/src/common-runopts.c b/src/common-runopts.c new file mode 100644 index 0000000..e9ad314 --- /dev/null +++ b/src/common-runopts.c @@ -0,0 +1,173 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "runopts.h" +#include "signkey.h" +#include "buffer.h" +#include "dbutil.h" +#include "auth.h" +#include "algo.h" +#include "dbrandom.h" + +runopts opts; /* GLOBAL */ + +/* returns success or failure, and the keytype in *type. If we want + * to restrict the type, type can contain a type to return */ +int readhostkey(const char * filename, sign_key * hostkey, + enum signkey_type *type) { + + int ret = DROPBEAR_FAILURE; + buffer *buf; + + buf = buf_new(MAX_PRIVKEY_SIZE); + + if (buf_readfile(buf, filename) == DROPBEAR_FAILURE) { + goto out; + } + buf_setpos(buf, 0); + + addrandom(buf_getptr(buf, buf->len), buf->len); + + if (buf_get_priv_key(buf, hostkey, type) == DROPBEAR_FAILURE) { + goto out; + } + + ret = DROPBEAR_SUCCESS; +out: + + buf_burn_free(buf); + return ret; +} + +#if DROPBEAR_USER_ALGO_LIST +void +parse_ciphers_macs() { + int printed_help = 0; + if (opts.cipher_list) { + if (strcmp(opts.cipher_list, "help") == 0) { + char *ciphers = algolist_string(sshciphers); + dropbear_log(LOG_INFO, "Available ciphers: %s", ciphers); + m_free(ciphers); + printed_help = 1; + } else { + if (check_user_algos(opts.cipher_list, sshciphers, "cipher") == 0) { + dropbear_exit("No valid ciphers specified for '-c'"); + } + } + } + + if (opts.mac_list) { + if (strcmp(opts.mac_list, "help") == 0) { + char *macs = algolist_string(sshhashes); + dropbear_log(LOG_INFO, "Available MACs: %s", macs); + m_free(macs); + printed_help = 1; + } else { + if (check_user_algos(opts.mac_list, sshhashes, "MAC") == 0) { + dropbear_exit("No valid MACs specified for '-m'"); + } + } + } + if (printed_help) { + dropbear_exit("."); + } +} +#endif + +void print_version() { + fprintf(stderr, "Dropbear v%s\n", DROPBEAR_VERSION); +} + +void parse_recv_window(const char* recv_window_arg) { + int ret; + unsigned int rw; + + ret = m_str_to_uint(recv_window_arg, &rw); + if (ret == DROPBEAR_FAILURE || rw == 0 || rw > MAX_RECV_WINDOW) { + if (rw > MAX_RECV_WINDOW) { + opts.recv_window = MAX_RECV_WINDOW; + } + dropbear_log(LOG_WARNING, "Bad recv window '%s', using %d", + recv_window_arg, opts.recv_window); + } else { + opts.recv_window = rw; + } + +} + +/* Splits addr:port. Handles IPv6 [2001:0011::4]:port style format. + Returns first/second parts as malloced strings, second will + be NULL if no separator is found. + :port -> (NULL, "port") + port -> (port, NULL) + addr:port (addr, port) + addr: -> (addr, "") + Returns DROPBEAR_SUCCESS/DROPBEAR_FAILURE */ +int split_address_port(const char* spec, char **first, char ** second) { + char *spec_copy = NULL, *addr = NULL, *colon = NULL; + int ret = DROPBEAR_FAILURE; + + *first = NULL; + *second = NULL; + spec_copy = m_strdup(spec); + addr = spec_copy; + + if (*addr == '[') { + addr++; + colon = strchr(addr, ']'); + if (!colon) { + dropbear_log(LOG_WARNING, "Bad address '%s'", spec); + goto out; + } + *colon = '\0'; + colon++; + if (*colon == '\0') { + /* No port part */ + colon = NULL; + } else if (*colon != ':') { + dropbear_log(LOG_WARNING, "Bad address '%s'", spec); + goto out; + } + } else { + /* search for ':', that separates address and port */ + colon = strrchr(addr, ':'); + } + + /* colon points to ':' now, or is NULL */ + if (colon) { + /* Split the address/port */ + *colon = '\0'; + colon++; + *second = m_strdup(colon); + } + if (strlen(addr)) { + *first = m_strdup(addr); + } + ret = DROPBEAR_SUCCESS; + +out: + m_free(spec_copy); + return ret; +} diff --git a/src/common-session.c b/src/common-session.c new file mode 100644 index 0000000..6991f57 --- /dev/null +++ b/src/common-session.c @@ -0,0 +1,705 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "session.h" +#include "dbutil.h" +#include "packet.h" +#include "algo.h" +#include "buffer.h" +#include "dss.h" +#include "ssh.h" +#include "dbrandom.h" +#include "kex.h" +#include "channel.h" +#include "runopts.h" +#include "netio.h" + +static void checktimeouts(void); +static long select_timeout(void); +static int ident_readln(int fd, char* buf, int count); +static void read_session_identification(void); + +struct sshsession ses; /* GLOBAL */ + +/* called only at the start of a session, set up initial state */ +void common_session_init(int sock_in, int sock_out) { + time_t now; + +#if DEBUG_TRACE + debug_start_net(); +#endif + + TRACE(("enter session_init")) + + ses.sock_in = sock_in; + ses.sock_out = sock_out; + ses.maxfd = MAX(sock_in, sock_out); + + if (sock_in >= 0) { + setnonblocking(sock_in); + } + if (sock_out >= 0) { + setnonblocking(sock_out); + } + + ses.socket_prio = DROPBEAR_PRIO_NORMAL; + /* Sets it to lowdelay */ + update_channel_prio(); + +#if !DROPBEAR_SVR_MULTIUSER + /* A sanity check to prevent an accidental configuration option + leaving multiuser systems exposed */ + errno = 0; + getuid(); + if (errno != ENOSYS) { + dropbear_exit("Non-multiuser Dropbear requires a non-multiuser kernel"); + } +#endif + + now = monotonic_now(); + ses.connect_time = now; + ses.last_packet_time_keepalive_recv = now; + ses.last_packet_time_idle = now; + ses.last_packet_time_any_sent = 0; + ses.last_packet_time_keepalive_sent = 0; + +#if DROPBEAR_FUZZ + if (!fuzz.fuzzing) +#endif + { + if (pipe(ses.signal_pipe) < 0) { + dropbear_exit("Signal pipe failed"); + } + setnonblocking(ses.signal_pipe[0]); + setnonblocking(ses.signal_pipe[1]); + ses.maxfd = MAX(ses.maxfd, ses.signal_pipe[0]); + ses.maxfd = MAX(ses.maxfd, ses.signal_pipe[1]); + } + + ses.writepayload = buf_new(TRANS_MAX_PAYLOAD_LEN); + ses.transseq = 0; + + ses.readbuf = NULL; + ses.payload = NULL; + ses.recvseq = 0; + + initqueue(&ses.writequeue); + + ses.requirenext = SSH_MSG_KEXINIT; + ses.dataallowed = 1; /* we can send data until we actually + send the SSH_MSG_KEXINIT */ + ses.ignorenext = 0; + ses.lastpacket = 0; + ses.reply_queue_head = NULL; + ses.reply_queue_tail = NULL; + + /* set all the algos to none */ + ses.keys = (struct key_context*)m_malloc(sizeof(struct key_context)); + ses.newkeys = NULL; + ses.keys->recv.algo_crypt = &dropbear_nocipher; + ses.keys->trans.algo_crypt = &dropbear_nocipher; + ses.keys->recv.crypt_mode = &dropbear_mode_none; + ses.keys->trans.crypt_mode = &dropbear_mode_none; + + ses.keys->recv.algo_mac = &dropbear_nohash; + ses.keys->trans.algo_mac = &dropbear_nohash; + + ses.keys->algo_kex = NULL; + ses.keys->algo_hostkey = -1; + ses.keys->recv.algo_comp = DROPBEAR_COMP_NONE; + ses.keys->trans.algo_comp = DROPBEAR_COMP_NONE; + +#ifndef DISABLE_ZLIB + ses.keys->recv.zstream = NULL; + ses.keys->trans.zstream = NULL; +#endif + + /* key exchange buffers */ + ses.session_id = NULL; + ses.kexhashbuf = NULL; + ses.transkexinit = NULL; + ses.dh_K = NULL; + ses.remoteident = NULL; + + ses.chantypes = NULL; + + ses.allowprivport = 0; + +#if DROPBEAR_PLUGIN + ses.plugin_session = NULL; +#endif + + TRACE(("leave session_init")) +} + +void session_loop(void(*loophandler)(void)) { + + fd_set readfd, writefd; + struct timeval timeout; + int val; + + /* main loop, select()s for all sockets in use */ + for(;;) { + const int writequeue_has_space = (ses.writequeue_len <= 2*TRANS_MAX_PAYLOAD_LEN); + + timeout.tv_sec = select_timeout(); + timeout.tv_usec = 0; + DROPBEAR_FD_ZERO(&writefd); + DROPBEAR_FD_ZERO(&readfd); + + dropbear_assert(ses.payload == NULL); + + /* We get woken up when signal handlers write to this pipe. + SIGCHLD in svr-chansession is the only one currently. */ +#if DROPBEAR_FUZZ + if (!fuzz.fuzzing) +#endif + { + FD_SET(ses.signal_pipe[0], &readfd); + } + + /* set up for channels which can be read/written */ + setchannelfds(&readfd, &writefd, writequeue_has_space); + + /* Pending connections to test */ + set_connect_fds(&writefd); + + /* We delay reading from the input socket during initial setup until + after we have written out our initial KEXINIT packet (empty writequeue). + This means our initial packet can be in-flight while we're doing a blocking + read for the remote ident. + We also avoid reading from the socket if the writequeue is full, that avoids + replies backing up */ + if (ses.sock_in != -1 + && (ses.remoteident || isempty(&ses.writequeue)) + && writequeue_has_space) { + FD_SET(ses.sock_in, &readfd); + } + + /* Ordering is important, this test must occur after any other function + might have queued packets (such as connection handlers) */ + if (ses.sock_out != -1 && !isempty(&ses.writequeue)) { + FD_SET(ses.sock_out, &writefd); + } + + val = select(ses.maxfd+1, &readfd, &writefd, NULL, &timeout); + + if (ses.exitflag) { + dropbear_exit("Terminated by signal"); + } + + if (val < 0 && errno != EINTR) { + dropbear_exit("Error in select"); + } + + if (val <= 0) { + /* If we were interrupted or the select timed out, we still + * want to iterate over channels etc for reading, to handle + * server processes exiting etc. + * We don't want to read/write FDs. */ + DROPBEAR_FD_ZERO(&writefd); + DROPBEAR_FD_ZERO(&readfd); + } + + /* We'll just empty out the pipe if required. We don't do + any thing with the data, since the pipe's purpose is purely to + wake up the select() above. */ + ses.channel_signal_pending = 0; + if (FD_ISSET(ses.signal_pipe[0], &readfd)) { + char x; + TRACE(("signal pipe set")) + while (read(ses.signal_pipe[0], &x, 1) > 0) {} + ses.channel_signal_pending = 1; + } + + /* check for auth timeout, rekeying required etc */ + checktimeouts(); + + /* process session socket's incoming data */ + if (ses.sock_in != -1) { + if (FD_ISSET(ses.sock_in, &readfd)) { + if (!ses.remoteident) { + /* blocking read of the version string */ + read_session_identification(); + } else { + read_packet(); + } + } + + /* Process the decrypted packet. After this, the read buffer + * will be ready for a new packet */ + if (ses.payload != NULL) { + process_packet(); + } + } + + /* if required, flush out any queued reply packets that + were being held up during a KEX */ + maybe_flush_reply_queue(); + + handle_connect_fds(&writefd); + + /* loop handler prior to channelio, in case the server loophandler closes + channels on process exit */ + loophandler(); + + /* process pipes etc for the channels, ses.dataallowed == 0 + * during rekeying ) */ + channelio(&readfd, &writefd); + + /* process session socket's outgoing data */ + if (ses.sock_out != -1) { + if (!isempty(&ses.writequeue)) { + write_packet(); + } + } + + } /* for(;;) */ + + /* Not reached */ +} + +static void cleanup_buf(buffer **buf) { + if (!*buf) { + return; + } + buf_burn_free(*buf); + *buf = NULL; +} + +/* clean up a session on exit */ +void session_cleanup() { + + TRACE(("enter session_cleanup")) + + /* we can't cleanup if we don't know the session state */ + if (!ses.init_done) { + TRACE(("leave session_cleanup: !ses.init_done")) + return; + } + + /* BEWARE of changing order of functions here. */ + + /* Must be before extra_session_cleanup() */ + chancleanup(); + + if (ses.extra_session_cleanup) { + ses.extra_session_cleanup(); + } + + /* After these are freed most functions will fail */ +#if DROPBEAR_CLEANUP + /* listeners call cleanup functions, this should occur before + other session state is freed. */ + remove_all_listeners(); + + remove_connect_pending(); + + while (!isempty(&ses.writequeue)) { + buf_free(dequeue(&ses.writequeue)); + } + + m_free(ses.newkeys); +#ifndef DISABLE_ZLIB + if (ses.keys->recv.zstream != NULL) { + if (inflateEnd(ses.keys->recv.zstream) == Z_STREAM_ERROR) { + dropbear_exit("Crypto error"); + } + m_free(ses.keys->recv.zstream); + } +#endif + + m_free(ses.remoteident); + m_free(ses.authstate.pw_dir); + m_free(ses.authstate.pw_name); + m_free(ses.authstate.pw_shell); + m_free(ses.authstate.pw_passwd); + m_free(ses.authstate.username); +#endif + + cleanup_buf(&ses.session_id); + cleanup_buf(&ses.hash); + cleanup_buf(&ses.payload); + cleanup_buf(&ses.readbuf); + cleanup_buf(&ses.writepayload); + cleanup_buf(&ses.kexhashbuf); + cleanup_buf(&ses.transkexinit); + if (ses.dh_K) { + mp_clear(ses.dh_K); + } + m_free(ses.dh_K); + + m_burn(ses.keys, sizeof(struct key_context)); + m_free(ses.keys); + + TRACE(("leave session_cleanup")) +} + +void send_session_identification() { + buffer *writebuf = buf_new(strlen(LOCAL_IDENT "\r\n") + 1); + buf_putbytes(writebuf, (const unsigned char *) LOCAL_IDENT "\r\n", strlen(LOCAL_IDENT "\r\n")); + writebuf_enqueue(writebuf); +} + +static void read_session_identification() { + /* max length of 255 chars */ + char linebuf[256]; + int len = 0; + char done = 0; + int i; + + /* Servers may send other lines of data before sending the + * version string, client must be able to process such lines. + * If they send more than 50 lines, something is wrong */ + for (i = IS_DROPBEAR_CLIENT ? 50 : 1; i > 0; i--) { + len = ident_readln(ses.sock_in, linebuf, sizeof(linebuf)); + + if (len < 0 && errno != EINTR) { + /* It failed */ + break; + } + + if (len >= 4 && memcmp(linebuf, "SSH-", 4) == 0) { + /* start of line matches */ + done = 1; + break; + } + } + + if (!done) { + TRACE(("error reading remote ident: %s\n", strerror(errno))) + ses.remoteclosed(); + } else { + /* linebuf is already null terminated */ + ses.remoteident = m_malloc(len); + memcpy(ses.remoteident, linebuf, len); + } + + /* Shall assume that 2.x will be backwards compatible. */ + if (strncmp(ses.remoteident, "SSH-2.", 6) != 0 + && strncmp(ses.remoteident, "SSH-1.99-", 9) != 0) { + dropbear_exit("Incompatible remote version '%s'", ses.remoteident); + } + + DEBUG1(("remoteident: %s", ses.remoteident)) + +} + +/* returns the length including null-terminating zero on success, + * or -1 on failure */ +static int ident_readln(int fd, char* buf, int count) { + + char in; + int pos = 0; + int num = 0; + fd_set fds; + struct timeval timeout; + + TRACE(("enter ident_readln")) + + if (count < 1) { + return -1; + } + + DROPBEAR_FD_ZERO(&fds); + + /* select since it's a non-blocking fd */ + + /* leave space to null-terminate */ + while (pos < count-1) { + + FD_SET(fd, &fds); + + timeout.tv_sec = 1; + timeout.tv_usec = 0; + if (select(fd+1, &fds, NULL, NULL, &timeout) < 0) { + if (errno == EINTR) { + continue; + } + TRACE(("leave ident_readln: select error")) + return -1; + } + + checktimeouts(); + + /* Have to go one byte at a time, since we don't want to read past + * the end, and have to somehow shove bytes back into the normal + * packet reader */ + if (FD_ISSET(fd, &fds)) { + num = read(fd, &in, 1); + /* a "\n" is a newline, "\r" we want to read in and keep going + * so that it won't be read as part of the next line */ + if (num < 0) { + /* error */ + if (errno == EINTR) { + continue; /* not a real error */ + } + TRACE(("leave ident_readln: read error")) + return -1; + } + if (num == 0) { + /* EOF */ + TRACE(("leave ident_readln: EOF")) + return -1; + } + +#if DROPBEAR_FUZZ + fuzz_dump(&in, 1); +#endif + + if (in == '\n') { + /* end of ident string */ + break; + } + /* we don't want to include '\r's */ + if (in != '\r') { + buf[pos] = in; + pos++; + } + } + } + + buf[pos] = '\0'; + TRACE(("leave ident_readln: return %d", pos+1)) + return pos+1; +} + +void ignore_recv_response() { + /* Do nothing */ + TRACE(("Ignored msg_request_response")) +} + +static void send_msg_keepalive() { + time_t old_time_idle = ses.last_packet_time_idle; + struct Channel *chan = get_any_ready_channel(); + + CHECKCLEARTOWRITE(); + + if (chan) { + /* Channel requests are preferable, more implementations + handle them than SSH_MSG_GLOBAL_REQUEST */ + TRACE(("keepalive channel request %d", chan->index)) + start_send_channel_request(chan, DROPBEAR_KEEPALIVE_STRING); + } else { + TRACE(("keepalive global request")) + /* Some peers will reply with SSH_MSG_REQUEST_FAILURE, + some will reply with SSH_MSG_UNIMPLEMENTED, some will exit. */ + buf_putbyte(ses.writepayload, SSH_MSG_GLOBAL_REQUEST); + buf_putstring(ses.writepayload, DROPBEAR_KEEPALIVE_STRING, + strlen(DROPBEAR_KEEPALIVE_STRING)); + } + buf_putbyte(ses.writepayload, 1); /* want_reply */ + encrypt_packet(); + + ses.last_packet_time_keepalive_sent = monotonic_now(); + + /* keepalives shouldn't update idle timeout, reset it back */ + ses.last_packet_time_idle = old_time_idle; +} + +/* Returns the difference in seconds, clamped to LONG_MAX */ +static long elapsed(time_t now, time_t prev) { + time_t del = now - prev; + if (del > LONG_MAX) { + return LONG_MAX; + } + return (long)del; +} + +/* Check all timeouts which are required. Currently these are the time for + * user authentication, and the automatic rekeying. */ +static void checktimeouts() { + + time_t now; + now = monotonic_now(); + + if (IS_DROPBEAR_SERVER && ses.connect_time != 0 + && elapsed(now, ses.connect_time) >= AUTH_TIMEOUT) { + dropbear_close("Timeout before auth"); + } + + /* we can't rekey if we haven't done remote ident exchange yet */ + if (ses.remoteident == NULL) { + return; + } + + if (!ses.kexstate.sentkexinit + && (elapsed(now, ses.kexstate.lastkextime) >= KEX_REKEY_TIMEOUT + || ses.kexstate.datarecv+ses.kexstate.datatrans >= KEX_REKEY_DATA)) { + TRACE(("rekeying after timeout or max data reached")) + send_msg_kexinit(); + } + + if (opts.keepalive_secs > 0 && ses.authstate.authdone) { + /* Avoid sending keepalives prior to auth - those are + not valid pre-auth packet types */ + + /* Send keepalives if we've been idle */ + if (elapsed(now, ses.last_packet_time_any_sent) >= opts.keepalive_secs) { + send_msg_keepalive(); + } + + /* Also send an explicit keepalive message to trigger a response + if the remote end hasn't sent us anything */ + if (elapsed(now, ses.last_packet_time_keepalive_recv) >= opts.keepalive_secs + && elapsed(now, ses.last_packet_time_keepalive_sent) >= opts.keepalive_secs) { + send_msg_keepalive(); + } + + if (elapsed(now, ses.last_packet_time_keepalive_recv) + >= opts.keepalive_secs * DEFAULT_KEEPALIVE_LIMIT) { + dropbear_exit("Keepalive timeout"); + } + } + + if (opts.idle_timeout_secs > 0 + && elapsed(now, ses.last_packet_time_idle) >= opts.idle_timeout_secs) { + dropbear_close("Idle timeout"); + } +} + +static void update_timeout(long limit, time_t now, time_t last_event, long * timeout) { + TRACE2(("update_timeout limit %ld, now %llu, last %llu, timeout %ld", + limit, + (unsigned long long)now, + (unsigned long long)last_event, *timeout)) + if (last_event > 0 && limit > 0) { + *timeout = MIN(*timeout, elapsed(now, last_event) + limit); + TRACE2(("new timeout %ld", *timeout)) + } +} + +static long select_timeout() { + /* determine the minimum timeout that might be required, so + as to avoid waking when unneccessary */ + long timeout = KEX_REKEY_TIMEOUT; + time_t now = monotonic_now(); + + if (!ses.kexstate.sentkexinit) { + update_timeout(KEX_REKEY_TIMEOUT, now, ses.kexstate.lastkextime, &timeout); + } + + if (ses.authstate.authdone != 1 && IS_DROPBEAR_SERVER) { + /* AUTH_TIMEOUT is only relevant before authdone */ + update_timeout(AUTH_TIMEOUT, now, ses.connect_time, &timeout); + } + + if (ses.authstate.authdone) { + update_timeout(opts.keepalive_secs, now, + MAX(ses.last_packet_time_keepalive_recv, ses.last_packet_time_keepalive_sent), + &timeout); + } + + update_timeout(opts.idle_timeout_secs, now, ses.last_packet_time_idle, + &timeout); + + /* clamp negative timeouts to zero - event has already triggered */ + return MAX(timeout, 0); +} + +const char* get_user_shell() { + /* an empty shell should be interpreted as "/bin/sh" */ + if (ses.authstate.pw_shell[0] == '\0') { + return "/bin/sh"; + } else { + return ses.authstate.pw_shell; + } +} +void fill_passwd(const char* username) { + struct passwd *pw = NULL; + if (ses.authstate.pw_name) + m_free(ses.authstate.pw_name); + if (ses.authstate.pw_dir) + m_free(ses.authstate.pw_dir); + if (ses.authstate.pw_shell) + m_free(ses.authstate.pw_shell); + if (ses.authstate.pw_passwd) + m_free(ses.authstate.pw_passwd); + + pw = getpwnam(username); + if (!pw) { + return; + } + ses.authstate.pw_uid = pw->pw_uid; + ses.authstate.pw_gid = pw->pw_gid; + ses.authstate.pw_name = m_strdup(pw->pw_name); + ses.authstate.pw_dir = m_strdup(pw->pw_dir); + ses.authstate.pw_shell = m_strdup(pw->pw_shell); + { + char *passwd_crypt = pw->pw_passwd; +#ifdef HAVE_SHADOW_H + /* get the shadow password if possible */ + struct spwd *spasswd = getspnam(ses.authstate.pw_name); + if (spasswd && spasswd->sp_pwdp) { + passwd_crypt = spasswd->sp_pwdp; + } +#endif + if (!passwd_crypt) { + /* android supposedly returns NULL */ + passwd_crypt = "!!"; + } + ses.authstate.pw_passwd = m_strdup(passwd_crypt); + } +} + +/* Called when channels are modified */ +void update_channel_prio() { + enum dropbear_prio new_prio; + int any = 0; + unsigned int i; + + TRACE(("update_channel_prio")) + + if (ses.sock_out < 0) { + TRACE(("leave update_channel_prio: no socket")) + return; + } + + new_prio = DROPBEAR_PRIO_NORMAL; + for (i = 0; i < ses.chansize; i++) { + struct Channel *channel = ses.channels[i]; + if (!channel) { + continue; + } + any = 1; + if (channel->prio == DROPBEAR_PRIO_LOWDELAY) { + new_prio = DROPBEAR_PRIO_LOWDELAY; + break; + } + } + + if (any == 0) { + /* lowdelay during setup */ + TRACE(("update_channel_prio: not any")) + new_prio = DROPBEAR_PRIO_LOWDELAY; + } + + if (new_prio != ses.socket_prio) { + TRACE(("Dropbear priority transitioning %d -> %d", ses.socket_prio, new_prio)) + set_sock_priority(ses.sock_out, new_prio); + ses.socket_prio = new_prio; + } +} + diff --git a/src/compat.c b/src/compat.c new file mode 100644 index 0000000..8bd6add --- /dev/null +++ b/src/compat.c @@ -0,0 +1,281 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * strlcat() is copyright as follows: + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * 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. 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 ``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. + * + * daemon() and getusershell() is copyright as follows: + * + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* +* Modifications for Dropbear to getusershell() are by Paul Marinceu +*/ + +#include "includes.h" + +#ifndef HAVE_GETUSERSHELL +static char **curshell, **shells, *strings; +static char **initshells(); +#endif + +#ifndef HAVE_STRLCPY +/* Implemented by matt as specified in freebsd 4.7 manpage. + * We don't require great speed, is simply for use with sshpty code */ +size_t strlcpy(char *dst, const char *src, size_t size) { + + size_t i; + + /* this is undefined, though size==0 -> return 0 */ + if (size < 1) { + return 0; + } + + for (i = 0; i < size-1; i++) { + if (src[i] == '\0') { + break; + } else { + dst[i] = src[i]; + } + } + + dst[i] = '\0'; + return strlen(src); + +} +#endif /* HAVE_STRLCPY */ + +#ifndef HAVE_STRLCAT +/* taken from openbsd-compat for OpenSSH 7.2p2 */ +/* "$OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $" + * + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} +#endif /* HAVE_STRLCAT */ + +#ifndef HAVE_DAEMON +/* From NetBSD - daemonise a process */ + +int daemon(int nochdir, int noclose) { + + int fd; + + switch (fork()) { + case -1: + return (-1); + case 0: + break; + default: + _exit(0); + } + + if (setsid() == -1) + return -1; + + if (!nochdir) + (void)chdir("/"); + + if (!noclose && (fd = open(DROPBEAR_PATH_DEVNULL, O_RDWR, 0)) != -1) { + (void)dup2(fd, STDIN_FILENO); + (void)dup2(fd, STDOUT_FILENO); + (void)dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) + (void)close(fd); + } + return 0; +} +#endif /* HAVE_DAEMON */ + +#ifndef HAVE_BASENAME + +char *basename(const char *path) { + + char *foo = strrchr(path, '/'); + if (!foo) + { + return path; + } + return ++foo; +} + +#endif /* HAVE_BASENAME */ + +#ifndef HAVE_GETUSERSHELL + +/* + * Get a list of shells from /etc/shells, if it exists. + */ +char * getusershell() { + char *ret; + + if (curshell == NULL) + curshell = initshells(); + ret = *curshell; + if (ret != NULL) + curshell++; + return (ret); +} + +void endusershell() { + + if (shells != NULL) + free(shells); + shells = NULL; + if (strings != NULL) + free(strings); + strings = NULL; + curshell = NULL; +} + +void setusershell() { + curshell = initshells(); +} + +static char **initshells() { + static const char *okshells[] = { COMPAT_USER_SHELLS, NULL }; + register char **sp, *cp; + register FILE *fp; + struct stat statb; + int flen; + + if (shells != NULL) + free(shells); + shells = NULL; + if (strings != NULL) + free(strings); + strings = NULL; + if ((fp = fopen("/etc/shells", "rc")) == NULL) + return (char **) okshells; + if (fstat(fileno(fp), &statb) == -1) { + (void)fclose(fp); + return (char **) okshells; + } + if ((strings = malloc((u_int)statb.st_size + 1)) == NULL) { + (void)fclose(fp); + return (char **) okshells; + } + shells = calloc((unsigned)statb.st_size / 3, sizeof (char *)); + if (shells == NULL) { + (void)fclose(fp); + free(strings); + strings = NULL; + return (char **) okshells; + } + sp = shells; + cp = strings; + flen = statb.st_size; + while (fgets(cp, flen - (cp - strings), fp) != NULL) { + while (*cp != '#' && *cp != '/' && *cp != '\0') + cp++; + if (*cp == '#' || *cp == '\0') + continue; + *sp++ = cp; + while (!isspace(*cp) && *cp != '#' && *cp != '\0') + cp++; + *cp++ = '\0'; + } + *sp = NULL; + (void)fclose(fp); + return (shells); +} + +#endif /* HAVE_GETUSERSHELL */ diff --git a/src/compat.h b/src/compat.h new file mode 100644 index 0000000..58fd58e --- /dev/null +++ b/src/compat.h @@ -0,0 +1,56 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_COMPAT_H_ +#define DROPBEAR_COMPAT_H_ + +#include "includes.h" + +#ifndef HAVE_STRLCPY +size_t strlcpy(char *dst, const char *src, size_t size); +#endif + +#ifndef HAVE_STRLCAT +size_t strlcat(char *dst, const char *src, size_t siz); +#endif + +#ifndef HAVE_DAEMON +int daemon(int nochdir, int noclose); +#endif + +#ifndef HAVE_BASENAME +char *basename(const char* path); +#endif + +#ifndef HAVE_GETUSERSHELL +char *getusershell(void); +void setusershell(void); +void endusershell(void); +#endif + +#ifndef DROPBEAR_PATH_DEVNULL +#define DROPBEAR_PATH_DEVNULL "/dev/null" +#endif + +#endif /* DROPBEAR_COMPAT_H_ */ diff --git a/src/crypto_desc.c b/src/crypto_desc.c new file mode 100644 index 0000000..d0dcc82 --- /dev/null +++ b/src/crypto_desc.c @@ -0,0 +1,76 @@ +#include "includes.h" +#include "dbutil.h" +#include "crypto_desc.h" +#include "ltc_prng.h" +#include "ecc.h" +#include "dbrandom.h" + +#if DROPBEAR_LTC_PRNG + int dropbear_ltc_prng = -1; +#endif + +/* Wrapper for libtommath */ +static mp_err dropbear_rand_source(void* out, size_t size) { + genrandom((unsigned char*)out, (unsigned int)size); + return MP_OKAY; +} + + +/* Register the compiled in ciphers. + * This should be run before using any of the ciphers/hashes */ +void crypto_init() { + + const struct ltc_cipher_descriptor *regciphers[] = { +#if DROPBEAR_AES + &aes_desc, +#endif +#if DROPBEAR_3DES + &des3_desc, +#endif + NULL + }; + + const struct ltc_hash_descriptor *reghashes[] = { +#if DROPBEAR_SHA1_HMAC + &sha1_desc, +#endif +#if DROPBEAR_SHA256 + &sha256_desc, +#endif +#if DROPBEAR_SHA384 + &sha384_desc, +#endif +#if DROPBEAR_SHA512 + &sha512_desc, +#endif + NULL + }; + int i; + + for (i = 0; regciphers[i] != NULL; i++) { + if (register_cipher(regciphers[i]) == -1) { + dropbear_exit("Error registering crypto"); + } + } + + for (i = 0; reghashes[i] != NULL; i++) { + if (register_hash(reghashes[i]) == -1) { + dropbear_exit("Error registering crypto"); + } + } + +#if DROPBEAR_LTC_PRNG + dropbear_ltc_prng = register_prng(&dropbear_prng_desc); + if (dropbear_ltc_prng == -1) { + dropbear_exit("Error registering crypto"); + } +#endif + + mp_rand_source(dropbear_rand_source); + +#if DROPBEAR_ECC + ltc_mp = ltm_desc; + dropbear_ecc_fill_dp(); +#endif +} + diff --git a/src/crypto_desc.h b/src/crypto_desc.h new file mode 100644 index 0000000..08a75d9 --- /dev/null +++ b/src/crypto_desc.h @@ -0,0 +1,9 @@ +#ifndef DROPBEAR_CRYPTO_DESC_H +#define DROPBEAR_CRYPTO_DESC_H + +void crypto_init(void); + +extern int dropbear_ltc_prng; + +#endif /* DROPBEAR_CRYPTO_DESC_H */ + diff --git a/src/curve25519.c b/src/curve25519.c new file mode 100644 index 0000000..51e0e76 --- /dev/null +++ b/src/curve25519.c @@ -0,0 +1,497 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbrandom.h" +#include "curve25519.h" + +#if DROPBEAR_CURVE25519 || DROPBEAR_ED25519 + +/* Modified TweetNaCl version 20140427, a self-contained public-domain C library. + * https://tweetnacl.cr.yp.to/ */ + +#define FOR(i,n) for (i = 0;i < n;++i) +#define sv static void + +typedef unsigned char u8; +typedef unsigned long u32; +typedef unsigned long long u64; +typedef long long i64; +typedef i64 gf[16]; + +#if DROPBEAR_CURVE25519 +static const gf + _121665 = {0xDB41,1}; +#endif /* DROPBEAR_CURVE25519 */ +#if DROPBEAR_ED25519 +static const gf + gf0, + gf1 = {1}, + D2 = {0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406}, + X = {0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169}, + Y = {0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666}; +#if DROPBEAR_SIGNKEY_VERIFY +static const gf + D = {0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203}, + I = {0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83}; +#endif /* DROPBEAR_SIGNKEY_VERIFY */ +#endif /* DROPBEAR_ED25519 */ + +#if DROPBEAR_ED25519 +#if DROPBEAR_SIGNKEY_VERIFY +static int vn(const u8 *x,const u8 *y,u32 n) +{ + u32 i,d = 0; + FOR(i,n) d |= x[i]^y[i]; + return (1 & ((d - 1) >> 8)) - 1; +} + +static int crypto_verify_32(const u8 *x,const u8 *y) +{ + return vn(x,y,32); +} +#endif /* DROPBEAR_SIGNKEY_VERIFY */ + +sv set25519(gf r, const gf a) +{ + int i; + FOR(i,16) r[i]=a[i]; +} +#endif /* DROPBEAR_ED25519 */ + +sv car25519(gf o) +{ + int i; + i64 c; + FOR(i,16) { + o[i]+=(1LL<<16); + c=o[i]>>16; + o[(i+1)*(i<15)]+=c-1+37*(c-1)*(i==15); + o[i]-=c<<16; + } +} + +sv sel25519(gf p,gf q,int b) +{ + i64 t,i,c=~(b-1); + FOR(i,16) { + t= c&(p[i]^q[i]); + p[i]^=t; + q[i]^=t; + } +} + +sv pack25519(u8 *o,const gf n) +{ + int i,j,b; + gf m,t; + FOR(i,16) t[i]=n[i]; + car25519(t); + car25519(t); + car25519(t); + FOR(j,2) { + m[0]=t[0]-0xffed; + for(i=1;i<15;i++) { + m[i]=t[i]-0xffff-((m[i-1]>>16)&1); + m[i-1]&=0xffff; + } + m[15]=t[15]-0x7fff-((m[14]>>16)&1); + b=(m[15]>>16)&1; + m[14]&=0xffff; + sel25519(t,m,1-b); + } + FOR(i,16) { + o[2*i]=t[i]&0xff; + o[2*i+1]=t[i]>>8; + } +} + +#if DROPBEAR_ED25519 +#if DROPBEAR_SIGNKEY_VERIFY +static int neq25519(const gf a, const gf b) +{ + u8 c[32],d[32]; + pack25519(c,a); + pack25519(d,b); + return crypto_verify_32(c,d); +} +#endif /* DROPBEAR_SIGNKEY_VERIFY */ + +static u8 par25519(const gf a) +{ + u8 d[32]; + pack25519(d,a); + return d[0]&1; +} +#endif /* DROPBEAR_ED25519 */ + +sv unpack25519(gf o, const u8 *n) +{ + int i; + FOR(i,16) o[i]=n[2*i]+((i64)n[2*i+1]<<8); + o[15]&=0x7fff; +} + +sv A(gf o,const gf a,const gf b) +{ + int i; + FOR(i,16) o[i]=a[i]+b[i]; +} + +sv Z(gf o,const gf a,const gf b) +{ + int i; + FOR(i,16) o[i]=a[i]-b[i]; +} + +sv M(gf o,const gf a,const gf b) +{ + i64 i,j,t[31]; + FOR(i,31) t[i]=0; + FOR(i,16) FOR(j,16) t[i+j]+=a[i]*b[j]; + FOR(i,15) t[i]+=38*t[i+16]; + FOR(i,16) o[i]=t[i]; + car25519(o); + car25519(o); +} + +sv S(gf o,const gf a) +{ + M(o,a,a); +} + +sv inv25519(gf o,const gf i) +{ + gf c; + int a; + FOR(a,16) c[a]=i[a]; + for(a=253;a>=0;a--) { + S(c,c); + if(a!=2&&a!=4) M(c,c,i); + } + FOR(a,16) o[a]=c[a]; +} + +#if DROPBEAR_ED25519 && DROPBEAR_SIGNKEY_VERIFY +sv pow2523(gf o,const gf i) +{ + gf c; + int a; + FOR(a,16) c[a]=i[a]; + for(a=250;a>=0;a--) { + S(c,c); + if(a!=1) M(c,c,i); + } + FOR(a,16) o[a]=c[a]; +} +#endif /* DROPBEAR_ED25519 && DROPBEAR_SIGNKEY_VERIFY */ + +#if DROPBEAR_CURVE25519 +void dropbear_curve25519_scalarmult(u8 *q,const u8 *n,const u8 *p) +{ + u8 z[32]; + i64 x[80],r,i; + gf a,b,c,d,e,f; + FOR(i,31) z[i]=n[i]; + z[31]=(n[31]&127)|64; + z[0]&=248; + unpack25519(x,p); + FOR(i,16) { + b[i]=x[i]; + d[i]=a[i]=c[i]=0; + } + a[0]=d[0]=1; + for(i=254;i>=0;--i) { + r=(z[i>>3]>>(i&7))&1; + sel25519(a,b,r); + sel25519(c,d,r); + A(e,a,c); + Z(a,a,c); + A(c,b,d); + Z(b,b,d); + S(d,e); + S(f,a); + M(a,c,a); + M(c,b,e); + A(e,a,c); + Z(a,a,c); + S(b,a); + Z(c,d,f); + M(a,c,_121665); + A(a,a,d); + M(c,c,a); + M(a,d,f); + M(d,b,x); + S(b,e); + sel25519(a,b,r); + sel25519(c,d,r); + } + FOR(i,16) { + x[i+16]=a[i]; + x[i+32]=c[i]; + x[i+48]=b[i]; + x[i+64]=d[i]; + } + inv25519(x+32,x+32); + M(x+16,x+16,x+32); + pack25519(q,x+16); +} +#endif /* DROPBEAR_CURVE25519 */ + +#if DROPBEAR_ED25519 +static int crypto_hash(u8 *out,const u8 *m,u64 n) +{ + hash_state hs; + + sha512_init(&hs); + sha512_process(&hs, m, n); + return sha512_done(&hs, out); +} + +sv add(gf p[4],gf q[4]) +{ + gf a,b,c,d,t,e,f,g,h; + + Z(a, p[1], p[0]); + Z(t, q[1], q[0]); + M(a, a, t); + A(b, p[0], p[1]); + A(t, q[0], q[1]); + M(b, b, t); + M(c, p[3], q[3]); + M(c, c, D2); + M(d, p[2], q[2]); + A(d, d, d); + Z(e, b, a); + Z(f, d, c); + A(g, d, c); + A(h, b, a); + + M(p[0], e, f); + M(p[1], h, g); + M(p[2], g, f); + M(p[3], e, h); +} + +sv cswap(gf p[4],gf q[4],u8 b) +{ + int i; + FOR(i,4) + sel25519(p[i],q[i],b); +} + +sv pack(u8 *r,gf p[4]) +{ + gf tx, ty, zi; + inv25519(zi, p[2]); + M(tx, p[0], zi); + M(ty, p[1], zi); + pack25519(r, ty); + r[31] ^= par25519(tx) << 7; +} + +sv scalarmult(gf p[4],gf q[4],const u8 *s) +{ + int i; + set25519(p[0],gf0); + set25519(p[1],gf1); + set25519(p[2],gf1); + set25519(p[3],gf0); + for (i = 255;i >= 0;--i) { + u8 b = (s[i/8]>>(i&7))&1; + cswap(p,q,b); + add(q,p); + add(p,p); + cswap(p,q,b); + } +} + +sv scalarbase(gf p[4],const u8 *s) +{ + gf q[4]; + set25519(q[0],X); + set25519(q[1],Y); + set25519(q[2],gf1); + M(q[3],X,Y); + scalarmult(p,q,s); +} + +void dropbear_ed25519_make_key(u8 *pk,u8 *sk) +{ + u8 d[64]; + gf p[4]; + + genrandom(sk, 32); + + crypto_hash(d, sk, 32); + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + + scalarbase(p,d); + pack(pk,p); +} + +static const u64 L[32] = {0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10}; + +sv modL(u8 *r,i64 x[64]) +{ + i64 carry,i,j; + for (i = 63;i >= 32;--i) { + carry = 0; + for (j = i - 32;j < i - 12;++j) { + x[j] += carry - 16 * x[i] * L[j - (i - 32)]; + carry = (x[j] + 128) >> 8; + x[j] -= carry << 8; + } + x[j] += carry; + x[i] = 0; + } + carry = 0; + FOR(j,32) { + x[j] += carry - (x[31] >> 4) * L[j]; + carry = x[j] >> 8; + x[j] &= 255; + } + FOR(j,32) x[j] -= carry * L[j]; + FOR(i,32) { + x[i+1] += x[i] >> 8; + r[i] = x[i] & 255; + } +} + +sv reduce(u8 *r) +{ + i64 x[64],i; + FOR(i,64) x[i] = (u64) r[i]; + FOR(i,64) r[i] = 0; + modL(r,x); +} + +void dropbear_ed25519_sign(const u8 *m,u32 mlen,u8 *s,u32 *slen,const u8 *sk, const u8 *pk) +{ + hash_state hs; + u8 d[64],h[64],r[64]; + i64 x[64]; + gf p[4]; + u32 i,j; + + crypto_hash(d, sk, 32); + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + + *slen = 64; + + sha512_init(&hs); + sha512_process(&hs,d + 32,32); + sha512_process(&hs,m,mlen); + sha512_done(&hs,r); + reduce(r); + scalarbase(p,r); + pack(s,p); + + sha512_init(&hs); + sha512_process(&hs,s,32); + sha512_process(&hs,pk,32); + sha512_process(&hs,m,mlen); + sha512_done(&hs,h); + reduce(h); + + FOR(i,64) x[i] = 0; + FOR(i,32) x[i] = (u64) r[i]; + FOR(i,32) FOR(j,32) x[i+j] += h[i] * (u64) d[j]; + modL(s + 32,x); +} + +#if DROPBEAR_SIGNKEY_VERIFY +static int unpackneg(gf r[4],const u8 p[32]) +{ + gf t, chk, num, den, den2, den4, den6; + set25519(r[2],gf1); + unpack25519(r[1],p); + S(num,r[1]); + M(den,num,D); + Z(num,num,r[2]); + A(den,r[2],den); + + S(den2,den); + S(den4,den2); + M(den6,den4,den2); + M(t,den6,num); + M(t,t,den); + + pow2523(t,t); + M(t,t,num); + M(t,t,den); + M(t,t,den); + M(r[0],t,den); + + S(chk,r[0]); + M(chk,chk,den); + if (neq25519(chk, num)) M(r[0],r[0],I); + + S(chk,r[0]); + M(chk,chk,den); + if (neq25519(chk, num)) return -1; + + if (par25519(r[0]) == (p[31]>>7)) Z(r[0],gf0,r[0]); + + M(r[3],r[0],r[1]); + return 0; +} + +int dropbear_ed25519_verify(const u8 *m,u32 mlen,const u8 *s,u32 slen,const u8 *pk) +{ + hash_state hs; + u8 t[32],h[64]; + gf p[4],q[4]; + + if (slen < 64) return -1; + + if (unpackneg(q,pk)) return -1; + + sha512_init(&hs); + sha512_process(&hs,s,32); + sha512_process(&hs,pk,32); + sha512_process(&hs,m,mlen); + sha512_done(&hs,h); + + reduce(h); + scalarmult(p,q,h); + + scalarbase(q,s + 32); + add(p,q); + pack(t,p); + + if (crypto_verify_32(s, t)) + return -1; + + return 0; +} +#endif /* DROPBEAR_SIGNKEY_VERIFY */ + +#endif /* DROPBEAR_ED25519 */ + +#endif /* DROPBEAR_CURVE25519 || DROPBEAR_ED25519 */ diff --git a/src/curve25519.h b/src/curve25519.h new file mode 100644 index 0000000..55ef043 --- /dev/null +++ b/src/curve25519.h @@ -0,0 +1,37 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_CURVE25519_H +#define DROPBEAR_CURVE25519_H + +void dropbear_curve25519_scalarmult(unsigned char *q, const unsigned char *n, const unsigned char *p); +void dropbear_ed25519_make_key(unsigned char *pk, unsigned char *sk); +void dropbear_ed25519_sign(const unsigned char *m, unsigned long mlen, + unsigned char *s, unsigned long *slen, + const unsigned char *sk, const unsigned char *pk); +int dropbear_ed25519_verify(const unsigned char *m, unsigned long mlen, + const unsigned char *s, unsigned long slen, + const unsigned char *pk); + +#endif /* DROPBEAR_CURVE25519_H */ diff --git a/src/dbhelpers.c b/src/dbhelpers.c new file mode 100644 index 0000000..ce5c379 --- /dev/null +++ b/src/dbhelpers.c @@ -0,0 +1,18 @@ +#include "dbhelpers.h" +#include "includes.h" + +/* Erase data */ +void m_burn(void *data, unsigned int len) { + +#if defined(HAVE_MEMSET_S) + memset_s(data, len, 0x0, len); +#elif defined(HAVE_EXPLICIT_BZERO) + explicit_bzero(data, len); +#else + /* This must be volatile to avoid compiler optimisation */ + volatile void *p = data; + memset((void*)p, 0x0, len); +#endif +} + + diff --git a/src/dbhelpers.h b/src/dbhelpers.h new file mode 100644 index 0000000..551bcb4 --- /dev/null +++ b/src/dbhelpers.h @@ -0,0 +1,21 @@ +#ifndef DROPBEAR_DBHELPERS_H_ +#define DROPBEAR_DBHELPERS_H_ + +/* This header defines some things that are also used by libtomcrypt/math. + We avoid including normal include.h since that can result in conflicting + definitions - only include config.h */ +#include "config.h" + +#ifdef __GNUC__ +#define ATTRIB_PRINTF(fmt,args) __attribute__((format(printf, fmt, args))) +#define ATTRIB_NORETURN __attribute__((noreturn)) +#define ATTRIB_SENTINEL __attribute__((sentinel)) +#else +#define ATTRIB_PRINTF(fmt,args) +#define ATTRIB_NORETURN +#define ATTRIB_SENTINEL +#endif + +void m_burn(void* data, unsigned int len); + +#endif /* DROPBEAR_DBHELPERS_H_ */ diff --git a/src/dbmalloc.c b/src/dbmalloc.c new file mode 100644 index 0000000..e2cdc8f --- /dev/null +++ b/src/dbmalloc.c @@ -0,0 +1,192 @@ +#include "dbmalloc.h" +#include "dbutil.h" + + +void * m_calloc(size_t nmemb, size_t size) { + if (SIZE_T_MAX / nmemb < size) { + dropbear_exit("m_calloc failed"); + } + return m_malloc(nmemb*size); +} + +void * m_strdup(const char * str) { + char* ret; + unsigned int len; + len = strlen(str); + + ret = m_malloc(len+1); + if (ret == NULL) { + dropbear_exit("m_strdup failed"); + } + memcpy(ret, str, len+1); + return ret; +} + +#if !DROPBEAR_TRACKING_MALLOC + +/* Simple wrappers around malloc etc */ +void * m_malloc(size_t size) { + + void* ret; + + if (size == 0) { + dropbear_exit("m_malloc failed"); + } + ret = calloc(1, size); + if (ret == NULL) { + dropbear_exit("m_malloc failed"); + } + return ret; + +} + +void * m_realloc(void* ptr, size_t size) { + + void *ret; + + if (size == 0) { + dropbear_exit("m_realloc failed"); + } + ret = realloc(ptr, size); + if (ret == NULL) { + dropbear_exit("m_realloc failed"); + } + return ret; +} + + +#else + +/* For fuzzing */ + +struct dbmalloc_header { + unsigned int epoch; + struct dbmalloc_header *prev; + struct dbmalloc_header *next; +}; + +static void put_alloc(struct dbmalloc_header *header); +static void remove_alloc(struct dbmalloc_header *header); + +/* end of the linked list */ +static struct dbmalloc_header* staple; + +unsigned int current_epoch = 0; + +void m_malloc_set_epoch(unsigned int epoch) { + current_epoch = epoch; +} + +void m_malloc_free_epoch(unsigned int epoch, int dofree) { + struct dbmalloc_header* header; + struct dbmalloc_header* nextheader = NULL; + struct dbmalloc_header* oldstaple = staple; + staple = NULL; + /* free allocations from this epoch, create a new staple-anchored list from + the remainder */ + for (header = oldstaple; header; header = nextheader) + { + nextheader = header->next; + if (header->epoch == epoch) { + if (dofree) { + free(header); + } + } else { + header->prev = NULL; + header->next = NULL; + put_alloc(header); + } + } +} + +static void put_alloc(struct dbmalloc_header *header) { + assert(header->next == NULL); + assert(header->prev == NULL); + if (staple) { + staple->prev = header; + } + header->next = staple; + staple = header; +} + +static void remove_alloc(struct dbmalloc_header *header) { + if (header->prev) { + header->prev->next = header->next; + } + if (header->next) { + header->next->prev = header->prev; + } + if (staple == header) { + staple = header->next; + } + header->prev = NULL; + header->next = NULL; +} + +static struct dbmalloc_header* get_header(void* ptr) { + char* bptr = ptr; + return (struct dbmalloc_header*)&bptr[-sizeof(struct dbmalloc_header)]; +} + +void * m_malloc(size_t size) { + char* mem = NULL; + struct dbmalloc_header* header = NULL; + + if (size == 0 || size > 1e9) { + dropbear_exit("m_malloc failed"); + } + + size = size + sizeof(struct dbmalloc_header); + + mem = calloc(1, size); + if (mem == NULL) { + dropbear_exit("m_malloc failed"); + } + header = (struct dbmalloc_header*)mem; + put_alloc(header); + header->epoch = current_epoch; + return &mem[sizeof(struct dbmalloc_header)]; +} + +void * m_realloc(void* ptr, size_t size) { + char* mem = NULL; + struct dbmalloc_header* header = NULL; + if (size == 0 || size > 1e9) { + dropbear_exit("m_realloc failed"); + } + + header = get_header(ptr); + remove_alloc(header); + + size = size + sizeof(struct dbmalloc_header); + mem = realloc(header, size); + if (mem == NULL) { + dropbear_exit("m_realloc failed"); + } + + header = (struct dbmalloc_header*)mem; + put_alloc(header); + return &mem[sizeof(struct dbmalloc_header)]; +} + +void m_free_direct(void* ptr) { + struct dbmalloc_header* header = NULL; + if (!ptr) { + return; + } + header = get_header(ptr); + remove_alloc(header); + free(header); +} + +#endif /* DROPBEAR_TRACKING_MALLOC */ + +void * m_realloc_ltm(void* ptr, size_t oldsize, size_t newsize) { + (void)oldsize; + return m_realloc(ptr, newsize); +} + +void m_free_ltm(void *mem, size_t size) { + (void)size; + m_free_direct(mem); +} diff --git a/src/dbmalloc.h b/src/dbmalloc.h new file mode 100644 index 0000000..e5554e8 --- /dev/null +++ b/src/dbmalloc.h @@ -0,0 +1,27 @@ +#ifndef DBMALLOC_H_ +#define DBMALLOC_H_ + +#include "options.h" +#include <stdint.h> +#include <stdlib.h> + +void * m_malloc(size_t size); +void * m_calloc(size_t nmemb, size_t size); +void * m_strdup(const char * str); +void * m_realloc(void* ptr, size_t size); + +#if DROPBEAR_TRACKING_MALLOC +void m_free_direct(void* ptr); +void m_malloc_set_epoch(unsigned int epoch); +void m_malloc_free_epoch(unsigned int epoch, int dofree); + +#else +/* plain wrapper */ +#define m_free_direct free + +#endif + +#define m_free(X) do {m_free_direct(X); (X) = NULL;} while (0) + + +#endif /* DBMALLOC_H_ */ diff --git a/src/dbmulti.c b/src/dbmulti.c new file mode 100644 index 0000000..28ee959 --- /dev/null +++ b/src/dbmulti.c @@ -0,0 +1,103 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" + +static int runprog(const char *multipath, + const char *progname, int argc, char ** argv, int *match) { + *match = DROPBEAR_SUCCESS; + +#ifdef DBMULTI_dropbear + if (strcmp(progname, "dropbear") == 0) { + return dropbear_main(argc, argv, multipath); + } +#endif +#ifdef DBMULTI_dbclient + if (strcmp(progname, "dbclient") == 0 + || strcmp(progname, "ssh") == 0) { + return cli_main(argc, argv); + } +#endif +#ifdef DBMULTI_dropbearkey + if (strcmp(progname, "dropbearkey") == 0) { + return dropbearkey_main(argc, argv); + } +#endif +#ifdef DBMULTI_dropbearconvert + if (strcmp(progname, "dropbearconvert") == 0) { + return dropbearconvert_main(argc, argv); + } +#endif +#ifdef DBMULTI_scp + if (strcmp(progname, "scp") == 0) { + return scp_main(argc, argv); + } +#endif + *match = DROPBEAR_FAILURE; + return 1; +} + +int main(int argc, char ** argv) { + int i; + for (i = 0; i < 2; i++) { + const char* multipath = NULL; + if (i == 1) { + multipath = argv[0]; + } + /* Try symlink first, then try as an argument eg "dropbearmulti dbclient host ..." */ + if (argc > i) { + int match, res; + /* figure which form we're being called as */ + const char* progname = basename(argv[i]); + res = runprog(multipath, progname, argc-i, &argv[i], &match); + if (match == DROPBEAR_SUCCESS) { + return res; + } + } + } + + fprintf(stderr, "Dropbear SSH multi-purpose v%s\n" + "Make a symlink pointing at this binary with one of the\n" + "following names or run 'dropbearmulti <command>'.\n" +#ifdef DBMULTI_dropbear + "'dropbear' - the Dropbear server\n" +#endif +#ifdef DBMULTI_dbclient + "'dbclient' or 'ssh' - the Dropbear client\n" +#endif +#ifdef DBMULTI_dropbearkey + "'dropbearkey' - the key generator\n" +#endif +#ifdef DBMULTI_dropbearconvert + "'dropbearconvert' - the key converter\n" +#endif +#ifdef DBMULTI_scp + "'scp' - secure copy\n" +#endif + , + DROPBEAR_VERSION); + exit(1); + +} diff --git a/src/dbrandom.c b/src/dbrandom.c new file mode 100644 index 0000000..41aaa48 --- /dev/null +++ b/src/dbrandom.c @@ -0,0 +1,377 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "buffer.h" +#include "dbutil.h" +#include "bignum.h" +#include "dbrandom.h" +#include "runopts.h" + +/* this is used to generate unique output from the same hashpool */ +static uint32_t counter = 0; +/* the max value for the counter, so it won't integer overflow */ +#define MAX_COUNTER (1<<30) + +static unsigned char hashpool[SHA256_HASH_SIZE] = {0}; +static int donerandinit = 0; + +#define INIT_SEED_SIZE 32 /* 256 bits */ + +/* The basic setup is we read some data from /dev/(u)random or prngd and hash it + * into hashpool. To read data, we hash together current hashpool contents, + * and a counter. We feed more data in by hashing the current pool and new + * data into the pool. + * + * It is important to ensure that counter doesn't wrap around before we + * feed in new entropy. + * + */ + +/* Pass wantlen=0 to hash an entire file */ +static int +process_file(hash_state *hs, const char *filename, + unsigned int wantlen, int prngd) { + int readfd = -1; + unsigned int readcount; + int ret = DROPBEAR_FAILURE; + + if (prngd) { +#if DROPBEAR_USE_PRNGD + readfd = connect_unix(filename); +#endif + } else { + readfd = open(filename, O_RDONLY); + } + + if (readfd < 0) { + goto out; + } + + readcount = 0; + while (wantlen == 0 || readcount < wantlen) { + int readlen, wantread; + unsigned char readbuf[4096]; + if (wantlen == 0) { + wantread = sizeof(readbuf); + } else { + wantread = MIN(sizeof(readbuf), wantlen-readcount); + } + +#if DROPBEAR_USE_PRNGD + if (prngd) { + char egdcmd[2]; + egdcmd[0] = 0x02; /* blocking read */ + egdcmd[1] = (unsigned char)wantread; + if (write(readfd, egdcmd, 2) < 0) { + dropbear_exit("Can't send command to egd"); + } + } +#endif + readlen = read(readfd, readbuf, wantread); + if (readlen <= 0) { + if (readlen < 0 && errno == EINTR) { + continue; + } + if (readlen == 0 && wantlen == 0) { + /* whole file was read as requested */ + break; + } + goto out; + } + sha256_process(hs, readbuf, readlen); + readcount += readlen; + } + ret = DROPBEAR_SUCCESS; +out: + close(readfd); + return ret; +} + +void addrandom(const unsigned char * buf, unsigned int len) +{ + hash_state hs; + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + return; + } +#endif + + /* hash in the new seed data */ + sha256_init(&hs); + /* existing state (zeroes on startup) */ + sha256_process(&hs, (void*)hashpool, sizeof(hashpool)); + + /* new */ + sha256_process(&hs, buf, len); + sha256_done(&hs, hashpool); +} + +static void write_urandom() +{ +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + return; + } +#endif +#if !DROPBEAR_USE_PRNGD + /* This is opportunistic, don't worry about failure */ + unsigned char buf[INIT_SEED_SIZE]; + FILE *f = fopen(DROPBEAR_URANDOM_DEV, "w"); + if (!f) { + return; + } + genrandom(buf, sizeof(buf)); + fwrite(buf, sizeof(buf), 1, f); + fclose(f); +#endif +} + +#if DROPBEAR_FUZZ +void fuzz_seed(const unsigned char* dat, unsigned int len) { + hash_state hs; + sha256_init(&hs); + sha256_process(&hs, "fuzzfuzzfuzz", strlen("fuzzfuzzfuzz")); + sha256_process(&hs, dat, len); + sha256_done(&hs, hashpool); + counter = 0; + donerandinit = 1; +} +#endif + + +#ifdef HAVE_GETRANDOM +/* Reads entropy seed with getrandom(). + * May block if the kernel isn't ready. + * Return DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int process_getrandom(hash_state *hs) { + char buf[INIT_SEED_SIZE]; + ssize_t ret; + + /* First try non-blocking so that we can warn about waiting */ + ret = getrandom(buf, sizeof(buf), GRND_NONBLOCK); + if (ret == -1) { + if (errno == ENOSYS) { + /* Old kernel */ + return DROPBEAR_FAILURE; + } + /* Other errors fall through to blocking getrandom() */ + TRACE(("first getrandom() failed: %d %s", errno, strerror(errno))) + if (errno == EAGAIN) { + dropbear_log(LOG_WARNING, "Waiting for kernel randomness to be initialised..."); + } + } + + /* Wait blocking if needed. Loop in case we get EINTR */ + while (ret != sizeof(buf)) { + ret = getrandom(buf, sizeof(buf), 0); + + if (ret == sizeof(buf)) { + /* Success */ + break; + } + if (ret == -1 && errno == EINTR) { + /* Try again. */ + continue; + } + if (ret >= 0) { + TRACE(("Short read %zd from getrandom() shouldn't happen", ret)) + /* Try again? */ + continue; + } + + /* Unexpected problem, fall back to /dev/urandom */ + TRACE(("2nd getrandom() failed: %d %s", errno, strerror(errno))) + break; + } + + if (ret == sizeof(buf)) { + /* Success, stir in the entropy */ + sha256_process(hs, (void*)buf, sizeof(buf)); + return DROPBEAR_SUCCESS; + } + + return DROPBEAR_FAILURE; + +} +#endif /* HAVE_GETRANDOM */ + +/* Initialise the prng from /dev/urandom or prngd. This function can + * be called multiple times */ +void seedrandom() { + hash_state hs; + + pid_t pid; + struct timeval tv; + clock_t clockval; + int urandom_seeded = 0; + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + return; + } +#endif + + /* hash in the new seed data */ + sha256_init(&hs); + + /* existing state */ + sha256_process(&hs, (void*)hashpool, sizeof(hashpool)); + +#ifdef HAVE_GETRANDOM + if (process_getrandom(&hs) == DROPBEAR_SUCCESS) { + urandom_seeded = 1; + } +#endif + + if (!urandom_seeded) { +#if DROPBEAR_USE_PRNGD + if (process_file(&hs, DROPBEAR_PRNGD_SOCKET, INIT_SEED_SIZE, 1) + != DROPBEAR_SUCCESS) { + dropbear_exit("Failure reading random device %s", + DROPBEAR_PRNGD_SOCKET); + urandom_seeded = 1; + } +#else + /* non-blocking random source (probably /dev/urandom) */ + if (process_file(&hs, DROPBEAR_URANDOM_DEV, INIT_SEED_SIZE, 0) + != DROPBEAR_SUCCESS) { + dropbear_exit("Failure reading random device %s", + DROPBEAR_URANDOM_DEV); + urandom_seeded = 1; + } +#endif + } /* urandom_seeded */ + + /* A few other sources to fall back on. + * Add more here for other platforms */ +#ifdef __linux__ + /* Seems to be a reasonable source of entropy from timers. Possibly hard + * for even local attackers to reproduce */ + process_file(&hs, "/proc/timer_list", 0, 0); + /* Might help on systems with wireless */ + process_file(&hs, "/proc/interrupts", 0, 0); + + process_file(&hs, "/proc/loadavg", 0, 0); + process_file(&hs, "/proc/sys/kernel/random/entropy_avail", 0, 0); + + /* Mostly network visible but useful in some situations. + * Limit size to avoid slowdowns on systems with lots of routes */ + process_file(&hs, "/proc/net/netstat", 4096, 0); + process_file(&hs, "/proc/net/dev", 4096, 0); + process_file(&hs, "/proc/net/tcp", 4096, 0); + /* Also includes interface lo */ + process_file(&hs, "/proc/net/rt_cache", 4096, 0); + process_file(&hs, "/proc/vmstat", 0, 0); +#endif + + pid = getpid(); + sha256_process(&hs, (void*)&pid, sizeof(pid)); + + /* gettimeofday() doesn't completely fill out struct timeval on + OS X (10.8.3), avoid valgrind warnings by clearing it first */ + memset(&tv, 0x0, sizeof(tv)); + gettimeofday(&tv, NULL); + sha256_process(&hs, (void*)&tv, sizeof(tv)); + + clockval = clock(); + sha256_process(&hs, (void*)&clockval, sizeof(clockval)); + + /* When a private key is read by the client or server it will + * be added to the hashpool - see runopts.c */ + + sha256_done(&hs, hashpool); + + counter = 0; + donerandinit = 1; + + /* Feed it all back into /dev/urandom - this might help if Dropbear + * is running from inetd and gets new state each time */ + write_urandom(); +} + +/* return len bytes of pseudo-random data */ +void genrandom(unsigned char* buf, unsigned int len) { + + hash_state hs; + unsigned char hash[SHA256_HASH_SIZE]; + unsigned int copylen; + + if (!donerandinit) { + dropbear_exit("seedrandom not done"); + } + + while (len > 0) { + sha256_init(&hs); + sha256_process(&hs, (void*)hashpool, sizeof(hashpool)); + sha256_process(&hs, (void*)&counter, sizeof(counter)); + sha256_done(&hs, hash); + + counter++; + if (counter > MAX_COUNTER) { + seedrandom(); + } + + copylen = MIN(len, SHA256_HASH_SIZE); + memcpy(buf, hash, copylen); + len -= copylen; + buf += copylen; + } + m_burn(hash, sizeof(hash)); +} + +/* Generates a random mp_int. + * max is a *mp_int specifying an upper bound. + * rand must be an initialised *mp_int for the result. + * the result rand satisfies: 0 < rand < max + * */ +void gen_random_mpint(const mp_int *max, mp_int *rand) { + + unsigned char *randbuf = NULL; + unsigned int len = 0; + const unsigned char masks[] = {0xff, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f}; + + const int size_bits = mp_count_bits(max); + + len = size_bits / 8; + if ((size_bits % 8) != 0) { + len += 1; + } + + randbuf = (unsigned char*)m_malloc(len); + do { + genrandom(randbuf, len); + /* Mask out the unrequired bits - mp_read_unsigned_bin expects + * MSB first.*/ + randbuf[0] &= masks[size_bits % 8]; + + bytes_to_mp(rand, randbuf, len); + + /* keep regenerating until we get one satisfying + * 0 < rand < max */ + } while (!(mp_cmp(rand, max) == MP_LT && mp_cmp_d(rand, 0) == MP_GT)); + m_burn(randbuf, len); + m_free(randbuf); +} diff --git a/src/dbrandom.h b/src/dbrandom.h new file mode 100644 index 0000000..1db2c2f --- /dev/null +++ b/src/dbrandom.h @@ -0,0 +1,35 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_RANDOM_H_ +#define DROPBEAR_RANDOM_H_ + +#include "includes.h" + +void seedrandom(void); +void genrandom(unsigned char* buf, unsigned int len); +void addrandom(const unsigned char * buf, unsigned int len); +void gen_random_mpint(const mp_int *max, mp_int *rand); + +#endif /* DROPBEAR_RANDOM_H_ */ diff --git a/src/dbutil.c b/src/dbutil.c new file mode 100644 index 0000000..e8831c5 --- /dev/null +++ b/src/dbutil.c @@ -0,0 +1,786 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * strlcat() is copyright as follows: + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * 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. 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 ``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 "config.h" + +#ifdef __linux__ +#define _GNU_SOURCE +/* To call clock_gettime() directly */ +#include <sys/syscall.h> +#endif /* __linux */ + +#ifdef HAVE_MACH_MACH_TIME_H +#include <mach/mach_time.h> +#include <mach/mach.h> +#endif + +#include "includes.h" +#include "dbutil.h" +#include "buffer.h" +#include "session.h" +#include "atomicio.h" + +#define MAX_FMT 100 + +static void generic_dropbear_exit(int exitcode, const char* format, + va_list param) ATTRIB_NORETURN; +static void generic_dropbear_log(int priority, const char* format, + va_list param); + +void (*_dropbear_exit)(int exitcode, const char* format, va_list param) ATTRIB_NORETURN + = generic_dropbear_exit; +void (*_dropbear_log)(int priority, const char* format, va_list param) + = generic_dropbear_log; + +#if DEBUG_TRACE +int debug_trace = 0; +#endif + +#ifndef DISABLE_SYSLOG +void startsyslog(const char *ident) { + + openlog(ident, LOG_PID, LOG_AUTHPRIV); + +} +#endif /* DISABLE_SYSLOG */ + +/* the "format" string must be <= 100 characters */ +void dropbear_close(const char* format, ...) { + + va_list param; + + va_start(param, format); + _dropbear_exit(EXIT_SUCCESS, format, param); + va_end(param); + +} + +void dropbear_exit(const char* format, ...) { + + va_list param; + + va_start(param, format); + _dropbear_exit(EXIT_FAILURE, format, param); + va_end(param); +} + +static void generic_dropbear_exit(int exitcode, const char* format, + va_list param) { + + char fmtbuf[300]; + + snprintf(fmtbuf, sizeof(fmtbuf), "Exited: %s", format); + + _dropbear_log(LOG_INFO, fmtbuf, param); + +#if DROPBEAR_FUZZ + if (fuzz.do_jmp) { + longjmp(fuzz.jmp, 1); + } +#endif + + exit(exitcode); +} + +void fail_assert(const char* expr, const char* file, int line) { + dropbear_exit("Failed assertion (%s:%d): `%s'", file, line, expr); +} + +static void generic_dropbear_log(int UNUSED(priority), const char* format, + va_list param) { + + char printbuf[1024]; + + vsnprintf(printbuf, sizeof(printbuf), format, param); + + fprintf(stderr, "%s\n", printbuf); + +} + +/* this is what can be called to write arbitrary log messages */ +void dropbear_log(int priority, const char* format, ...) { + + va_list param; + + va_start(param, format); + _dropbear_log(priority, format, param); + va_end(param); +} + + +#if DEBUG_TRACE + +static double debug_start_time = -1; + +void debug_start_net() +{ + if (getenv("DROPBEAR_DEBUG_NET_TIMESTAMP")) + { + /* Timestamps start from first network activity */ + struct timeval tv; + gettimeofday(&tv, NULL); + debug_start_time = tv.tv_sec + (tv.tv_usec / 1000000.0); + TRACE(("Resetting Dropbear TRACE timestamps")) + } +} + +static double time_since_start() +{ + double nowf; + struct timeval tv; + gettimeofday(&tv, NULL); + nowf = tv.tv_sec + (tv.tv_usec / 1000000.0); + if (debug_start_time < 0) + { + debug_start_time = nowf; + return 0; + } + return nowf - debug_start_time; +} + +static void dropbear_tracelevel(int level, const char *format, va_list param) +{ + if (debug_trace == 0 || debug_trace < level) { + return; + } + + fprintf(stderr, "TRACE%d (%d) %f: ", level, getpid(), time_since_start()); + vfprintf(stderr, format, param); + fprintf(stderr, "\n"); +} +#if (DEBUG_TRACE>=1) +void dropbear_trace1(const char* format, ...) { + va_list param; + + va_start(param, format); + dropbear_tracelevel(1, format, param); + va_end(param); +} +#endif +#if (DEBUG_TRACE>=2) +void dropbear_trace2(const char* format, ...) { + va_list param; + + va_start(param, format); + dropbear_tracelevel(2, format, param); + va_end(param); +} +#endif +#if (DEBUG_TRACE>=3) +void dropbear_trace3(const char* format, ...) { + va_list param; + + va_start(param, format); + dropbear_tracelevel(3, format, param); + va_end(param); +} +#endif +#if (DEBUG_TRACE>=4) +void dropbear_trace4(const char* format, ...) { + va_list param; + + va_start(param, format); + dropbear_tracelevel(4, format, param); + va_end(param); +} +#endif +#if (DEBUG_TRACE>=5) +void dropbear_trace5(const char* format, ...) { + va_list param; + + va_start(param, format); + dropbear_tracelevel(5, format, param); + va_end(param); +} +#endif +#endif + + +/* Connect to a given unix socket. The socket is blocking */ +#if ENABLE_CONNECT_UNIX +int connect_unix(const char* path) { + struct sockaddr_un addr; + int fd = -1; + + memset((void*)&addr, 0x0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + TRACE(("Failed to open unix socket")) + return -1; + } + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + TRACE(("Failed to connect to '%s' socket", path)) + m_close(fd); + return -1; + } + return fd; +} +#endif + +/* Sets up a pipe for a, returning three non-blocking file descriptors + * and the pid. exec_fn is the function that will actually execute the child process, + * it will be run after the child has fork()ed, and is passed exec_data. + * If ret_errfd == NULL then stderr will not be captured. + * ret_pid can be passed as NULL to discard the pid. */ +int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data, + int *ret_writefd, int *ret_readfd, int *ret_errfd, pid_t *ret_pid) { + int infds[2]; + int outfds[2]; + int errfds[2]; + pid_t pid; + + const int FDIN = 0; + const int FDOUT = 1; + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + return fuzz_spawn_command(ret_writefd, ret_readfd, ret_errfd, ret_pid); + } +#endif + + /* redirect stdin/stdout/stderr */ + if (pipe(infds) != 0) { + return DROPBEAR_FAILURE; + } + if (pipe(outfds) != 0) { + return DROPBEAR_FAILURE; + } + if (ret_errfd && pipe(errfds) != 0) { + return DROPBEAR_FAILURE; + } + +#if DROPBEAR_VFORK + pid = vfork(); +#else + pid = fork(); +#endif + + if (pid < 0) { + return DROPBEAR_FAILURE; + } + + if (!pid) { + /* child */ + + TRACE(("back to normal sigchld")) + /* Revert to normal sigchld handling */ + if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) { + dropbear_exit("signal() error"); + } + + /* redirect stdin/stdout */ + + if ((dup2(infds[FDIN], STDIN_FILENO) < 0) || + (dup2(outfds[FDOUT], STDOUT_FILENO) < 0) || + (ret_errfd && dup2(errfds[FDOUT], STDERR_FILENO) < 0)) { + TRACE(("leave noptycommand: error redirecting FDs")) + dropbear_exit("Child dup2() failure"); + } + + close(infds[FDOUT]); + close(infds[FDIN]); + close(outfds[FDIN]); + close(outfds[FDOUT]); + if (ret_errfd) + { + close(errfds[FDIN]); + close(errfds[FDOUT]); + } + + exec_fn(exec_data); + /* not reached */ + return DROPBEAR_FAILURE; + } else { + /* parent */ + close(infds[FDIN]); + close(outfds[FDOUT]); + + setnonblocking(outfds[FDIN]); + setnonblocking(infds[FDOUT]); + + if (ret_errfd) { + close(errfds[FDOUT]); + setnonblocking(errfds[FDIN]); + } + + if (ret_pid) { + *ret_pid = pid; + } + + *ret_writefd = infds[FDOUT]; + *ret_readfd = outfds[FDIN]; + if (ret_errfd) { + *ret_errfd = errfds[FDIN]; + } + return DROPBEAR_SUCCESS; + } +} + +/* Runs a command with "sh -c". Will close FDs (except stdin/stdout/stderr) and + * re-enabled SIGPIPE. If cmd is NULL, will run a login shell. + */ +void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) { + char * argv[4]; + char * baseshell = NULL; + unsigned int i; + + baseshell = basename(usershell); + + if (cmd != NULL) { + argv[0] = baseshell; + } else { + /* a login shell should be "-bash" for "/bin/bash" etc */ + int len = strlen(baseshell) + 2; /* 2 for "-" */ + argv[0] = (char*)m_malloc(len); + snprintf(argv[0], len, "-%s", baseshell); + } + + if (cmd != NULL) { + argv[1] = "-c"; + argv[2] = (char*)cmd; + argv[3] = NULL; + } else { + /* construct a shell of the form "-bash" etc */ + argv[1] = NULL; + } + + /* Re-enable SIGPIPE for the executed process */ + if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) { + dropbear_exit("signal() error"); + } + + /* close file descriptors except stdin/stdout/stderr + * Need to be sure FDs are closed here to avoid reading files as root */ + for (i = 3; i <= maxfd; i++) { + m_close(i); + } + + execv(usershell, argv); +} + +#if DEBUG_TRACE +void printhex(const char * label, const unsigned char * buf, int len) { + int i, j; + + fprintf(stderr, "%s\n", label); + /* for each 16 byte line */ + for (j = 0; j < len; j += 16) { + const int linelen = MIN(16, len - j); + + /* print hex digits */ + for (i = 0; i < 16; i++) { + if (i < linelen) { + fprintf(stderr, "%02x", buf[j+i]); + } else { + fprintf(stderr, " "); + } + // separator between pairs + if (i % 2 ==1) { + fprintf(stderr, " "); + } + } + + /* print characters */ + fprintf(stderr, " "); + for (i = 0; i < linelen; i++) { + char c = buf[j+i]; + if (!isprint(c)) { + c = '.'; + } + fputc(c, stderr); + } + fprintf(stderr, "\n"); + } +} + +void printmpint(const char *label, const mp_int *mp) { + buffer *buf = buf_new(1000); + buf_putmpint(buf, mp); + fprintf(stderr, "%d bits ", mp_count_bits(mp)); + printhex(label, buf->data, buf->len); + buf_free(buf); + +} +#endif + +/* Strip all control characters from text (a null-terminated string), except + * for '\n', '\r' and '\t'. + * The result returned is a newly allocated string, this must be free()d after + * use */ +char * stripcontrol(const char * text) { + + char * ret; + int len, pos; + int i; + + len = strlen(text); + ret = m_malloc(len+1); + + pos = 0; + for (i = 0; i < len; i++) { + if ((text[i] <= '~' && text[i] >= ' ') /* normal printable range */ + || text[i] == '\n' || text[i] == '\r' || text[i] == '\t') { + ret[pos] = text[i]; + pos++; + } + } + ret[pos] = 0x0; + return ret; +} + + +/* reads the contents of filename into the buffer buf, from the current + * position, either to the end of the file, or the buffer being full. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int buf_readfile(buffer* buf, const char* filename) { + + int fd = -1; + int len; + int maxlen; + int ret = DROPBEAR_FAILURE; + + fd = open(filename, O_RDONLY); + + if (fd < 0) { + goto out; + } + + do { + maxlen = buf->size - buf->pos; + len = read(fd, buf_getwriteptr(buf, maxlen), maxlen); + if (len < 0) { + if (errno == EINTR || errno == EAGAIN) { + continue; + } + goto out; + } + buf_incrwritepos(buf, len); + } while (len < maxlen && len > 0); + + ret = DROPBEAR_SUCCESS; + +out: + if (fd >= 0) { + m_close(fd); + } + return ret; +} + +/* get a line from the file into buffer in the style expected for an + * authkeys file. + * Will return DROPBEAR_SUCCESS if data is read, or DROPBEAR_FAILURE on EOF.*/ +/* Only used for ~/.ssh/known_hosts and ~/.ssh/authorized_keys */ +#if DROPBEAR_CLIENT || DROPBEAR_SVR_PUBKEY_AUTH +int buf_getline(buffer * line, FILE * authfile) { + + int c = EOF; + + buf_setpos(line, 0); + buf_setlen(line, 0); + + while (line->pos < line->size) { + + c = fgetc(authfile); /*getc() is weird with some uClibc systems*/ + if (c == EOF || c == '\n' || c == '\r') { + goto out; + } + + buf_putbyte(line, (unsigned char)c); + } + + TRACE(("leave getauthline: line too long")) + /* We return success, but the line length will be zeroed - ie we just + * ignore that line */ + buf_setlen(line, 0); + +out: + + + /* if we didn't read anything before EOF or error, exit */ + if (c == EOF && line->pos == 0) { + return DROPBEAR_FAILURE; + } else { + buf_setpos(line, 0); + return DROPBEAR_SUCCESS; + } + +} +#endif + +/* make sure that the socket closes */ +void m_close(int fd) { + int val; + + if (fd < 0) { + return; + } + + do { + val = close(fd); + } while (val < 0 && errno == EINTR); + + if (val < 0 && errno != EBADF) { + /* Linux says EIO can happen */ + dropbear_exit("Error closing fd %d, %s", fd, strerror(errno)); + } +} + +void setnonblocking(int fd) { + + TRACE(("setnonblocking: %d", fd)) + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + return; + } +#endif + + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { + if (errno == ENODEV) { + /* Some devices (like /dev/null redirected in) + * can't be set to non-blocking */ + TRACE(("ignoring ENODEV for setnonblocking")) + } else { + { + dropbear_exit("Couldn't set nonblocking"); + } + } + } + TRACE(("leave setnonblocking")) +} + +void disallow_core() { + struct rlimit lim = {0}; + if (getrlimit(RLIMIT_CORE, &lim) < 0) { + TRACE(("getrlimit(RLIMIT_CORE) failed")); + } + lim.rlim_cur = 0; + if (setrlimit(RLIMIT_CORE, &lim) < 0) { + TRACE(("setrlimit(RLIMIT_CORE) failed")); + } +} + +/* Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE, with the result in *val */ +int m_str_to_uint(const char* str, unsigned int *val) { + unsigned long l; + char *endp; + + l = strtoul(str, &endp, 10); + + if (endp == str || *endp != '\0') { + /* parse error */ + return DROPBEAR_FAILURE; + } + + /* The c99 spec doesn't actually seem to define EINVAL, but most platforms + * I've looked at mention it in their manpage */ + if ((l == 0 && errno == EINVAL) + || (l == ULONG_MAX && errno == ERANGE) + || (l > UINT_MAX)) { + return DROPBEAR_FAILURE; + } else { + *val = l; + return DROPBEAR_SUCCESS; + } +} + +/* Returns malloced path. inpath beginning with '~/' expanded, + otherwise returned as-is */ +char * expand_homedir_path(const char *inpath) { + struct passwd *pw = NULL; + if (strncmp(inpath, "~/", 2) == 0) { + char *homedir = getenv("HOME"); + + if (!homedir) { + pw = getpwuid(getuid()); + if (pw) { + homedir = pw->pw_dir; + } + } + + if (homedir) { + int len = strlen(inpath)-2 + strlen(homedir) + 2; + char *buf = m_malloc(len); + snprintf(buf, len, "%s/%s", homedir, inpath+2); + return buf; + } + } + + /* Fallback */ + return m_strdup(inpath); +} + +int constant_time_memcmp(const void* a, const void *b, size_t n) +{ + const char *xa = a, *xb = b; + uint8_t c = 0; + size_t i; + for (i = 0; i < n; i++) + { + c |= (xa[i] ^ xb[i]); + } + return c; +} + +/* higher-resolution monotonic timestamp, falls back to gettimeofday */ +void gettime_wrapper(struct timespec *now) { + struct timeval tv; +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + /* time stands still when fuzzing */ + now->tv_sec = 5; + now->tv_nsec = 0; + } +#endif + +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + /* POSIX monotonic clock. Newer Linux, BSD, MacOSX >10.12 */ + if (clock_gettime(CLOCK_MONOTONIC, now) == 0) { + return; + } +#endif + +#if defined(__linux__) && defined(SYS_clock_gettime) + { + /* Old linux toolchain - kernel might support it but not the build headers */ + /* Also glibc <2.17 requires -lrt which we neglect to add */ + static int linux_monotonic_failed = 0; + if (!linux_monotonic_failed) { + /* CLOCK_MONOTONIC isn't in some headers */ + int clock_source_monotonic = 1; + if (syscall(SYS_clock_gettime, clock_source_monotonic, now) == 0) { + return; + } else { + /* Don't try again */ + linux_monotonic_failed = 1; + } + } + } +#endif /* linux fallback clock_gettime */ + +#if defined(HAVE_MACH_ABSOLUTE_TIME) + { + /* OS X pre 10.12, see https://developer.apple.com/library/mac/qa/qa1398/_index.html */ + static mach_timebase_info_data_t timebase_info; + uint64_t scaled_time; + if (timebase_info.denom == 0) { + mach_timebase_info(&timebase_info); + } + scaled_time = mach_absolute_time() * timebase_info.numer / timebase_info.denom; + now->tv_sec = scaled_time / 1000000000; + now->tv_nsec = scaled_time % 1000000000; + } +#endif /* osx mach_absolute_time */ + + /* Fallback for everything else - this will sometimes go backwards */ + gettimeofday(&tv, NULL); + now->tv_sec = tv.tv_sec; + now->tv_nsec = 1000*(long)tv.tv_usec; +} + +/* second-resolution monotonic timestamp */ +time_t monotonic_now() { + struct timespec ts; + gettime_wrapper(&ts); + return ts.tv_sec; +} + +void fsync_parent_dir(const char* fn) { +#ifdef HAVE_LIBGEN_H + char *fn_dir = m_strdup(fn); + char *dir = dirname(fn_dir); + int dirfd = open(dir, O_RDONLY); + + if (dirfd != -1) { + if (fsync(dirfd) != 0) { + TRACE(("fsync of directory %s failed: %s", dir, strerror(errno))) + } + m_close(dirfd); + } else { + TRACE(("error opening directory %s for fsync: %s", dir, strerror(errno))) + } + + m_free(fn_dir); +#endif +} + +int fd_read_pending(int fd) { + fd_set fds; + struct timeval timeout; + + DROPBEAR_FD_ZERO(&fds); + FD_SET(fd, &fds); + while (1) { + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select(fd+1, &fds, NULL, NULL, &timeout) < 0) { + if (errno == EINTR) { + continue; + } + return 0; + } + return FD_ISSET(fd, &fds); + } +} + +int m_snprintf(char *str, size_t size, const char *format, ...) { + va_list param; + int ret; + + va_start(param, format); + ret = vsnprintf(str, size, format, param); + va_end(param); + if (ret < 0) { + dropbear_exit("snprintf failed"); + } + return ret; +} diff --git a/src/dbutil.h b/src/dbutil.h new file mode 100644 index 0000000..df2f89b --- /dev/null +++ b/src/dbutil.h @@ -0,0 +1,115 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_DBUTIL_H_ + +#define DROPBEAR_DBUTIL_H_ + +#include "includes.h" +#include "buffer.h" +#include "queue.h" +#include "dbhelpers.h" +#include "dbmalloc.h" + +#ifndef DISABLE_SYSLOG +void startsyslog(const char *ident); +#endif + +extern void (*_dropbear_exit)(int exitcode, const char* format, va_list param) ATTRIB_NORETURN; +extern void (*_dropbear_log)(int priority, const char* format, va_list param); + +void dropbear_exit(const char* format, ...) ATTRIB_PRINTF(1,2) ATTRIB_NORETURN; + +void dropbear_close(const char* format, ...) ATTRIB_PRINTF(1,2) ; +void dropbear_log(int priority, const char* format, ...) ATTRIB_PRINTF(2,3) ; + +void fail_assert(const char* expr, const char* file, int line) ATTRIB_NORETURN; + +#if DEBUG_TRACE +void dropbear_trace1(const char* format, ...) ATTRIB_PRINTF(1,2); +void dropbear_trace2(const char* format, ...) ATTRIB_PRINTF(1,2); +void dropbear_trace3(const char* format, ...) ATTRIB_PRINTF(1,2); +void dropbear_trace4(const char* format, ...) ATTRIB_PRINTF(1,2); +void dropbear_trace5(const char* format, ...) ATTRIB_PRINTF(1,2); +void printhex(const char * label, const unsigned char * buf, int len); +void printmpint(const char *label, const mp_int *mp); +void debug_start_net(void); +extern int debug_trace; +#endif + +char * stripcontrol(const char * text); + +int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data, + int *writefd, int *readfd, int *errfd, pid_t *pid); +void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell); +#if ENABLE_CONNECT_UNIX +int connect_unix(const char* addr); +#endif +int buf_readfile(buffer* buf, const char* filename); +int buf_getline(buffer * line, FILE * authfile); + +void m_close(int fd); +void setnonblocking(int fd); +void disallow_core(void); +int m_str_to_uint(const char* str, unsigned int *val); +/* The same as snprintf() but exits rather than returning negative */ +int m_snprintf(char *str, size_t size, const char *format, ...); + +/* Used to force mp_ints to be initialised */ +#define DEF_MP_INT(X) mp_int X = {0, 0, 0, NULL} + +/* Dropbear assertion */ +#define dropbear_assert(X) do { if (!(X)) { fail_assert(#X, __FILE__, __LINE__); } } while (0) + +/* Returns 0 if a and b have the same contents */ +int constant_time_memcmp(const void* a, const void *b, size_t n); + +/* Returns a time in seconds that doesn't go backwards - does not correspond to +a real-world clock */ +time_t monotonic_now(void); +/* Higher resolution clock_gettime(CLOCK_MONOTONIC) wrapper */ +void gettime_wrapper(struct timespec *now); + +char * expand_homedir_path(const char *inpath); + +void fsync_parent_dir(const char* fn); + +int fd_read_pending(int fd); + +#if DROPBEAR_MSAN +/* FD_ZERO seems to leave some memory uninitialized. clear it to avoid false positives */ +#define DROPBEAR_FD_ZERO(fds) do { memset((fds), 0x0, sizeof(fd_set)); FD_ZERO(fds); } while(0) +#else +#define DROPBEAR_FD_ZERO(fds) FD_ZERO(fds) +#endif + +/* dropbearmulti entry points */ +int dropbear_main(int argc, char ** argv, const char * multipath); +int cli_main(int argc, char ** argv); +int dropbearkey_main(int argc, char ** argv); +int dropbearconvert_main(int argc, char ** argv); +int scp_main(int argc, char ** argv); + + +#endif /* DROPBEAR_DBUTIL_H_ */ diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..ab32fbd --- /dev/null +++ b/src/debug.h @@ -0,0 +1,102 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_DEBUG_H_ +#define DROPBEAR_DEBUG_H_ + +#include "includes.h" + +/* Debugging */ + +/* Work well for valgrind - don't clear environment, be nicer with signals + * etc. Don't use this normally, it might cause problems */ +/* #define DEBUG_VALGRIND */ + +/* All functions writing to the cleartext payload buffer call + * CHECKCLEARTOWRITE() before writing. This is only really useful if you're + * attempting to track down a problem */ +/*#define CHECKCLEARTOWRITE() assert(ses.writepayload->len == 0 && \ + ses.writepayload->pos == 0)*/ + +#ifndef CHECKCLEARTOWRITE +#define CHECKCLEARTOWRITE() +#endif + +/* A couple of flags, not usually useful, and mightn't do anything */ + +/*#define DEBUG_KEXHASH*/ +/*#define DEBUG_RSA*/ + +/* The level of TRACE() statements */ +#define DROPBEAR_VERBOSE_LEVEL 4 + +#if DEBUG_TRACE +extern int debug_trace; +#endif + +/* Enable debug trace levels. + We can't use __VA_ARGS_ here because Dropbear supports + old ~C89 compilers */ +/* Default is to discard output ... */ +#define DEBUG1(X) +#define DEBUG2(X) +#define DEBUG3(X) +#define TRACE(X) +#define TRACE2(X) +/* ... unless DEBUG_TRACE is high enough */ +#if (DEBUG_TRACE>=1) +#undef DEBUG1 +#define DEBUG1(X) dropbear_trace1 X; +#endif +#if (DEBUG_TRACE>=2) +#undef DEBUG2 +#define DEBUG2(X) dropbear_trace2 X; +#endif +#if (DEBUG_TRACE>=3) +#undef DEBUG3 +#define DEBUG3(X) dropbear_trace3 X; +#endif +#if (DEBUG_TRACE>=4) +#undef TRACE +#define TRACE(X) dropbear_trace4 X; +#endif +#if (DEBUG_TRACE>=5) +#undef TRACE2 +#define TRACE2(X) dropbear_trace5 X; +#endif + +/* To debug with GDB it is easier to run with no forking of child processes. + You will need to pass "-F" as well. */ +#ifndef DEBUG_NOFORK +#define DEBUG_NOFORK 0 +#endif + + +/* For testing as non-root on shadowed systems, include the crypt of a password + * here. You can then log in as any user with this password. Ensure that you + * make your own password, and are careful about using this. This will also + * disable some of the chown pty code etc*/ +/* #define DEBUG_HACKCRYPT "hL8nrFDt0aJ3E" */ /* this is crypt("password") */ + +#endif diff --git a/src/dh_groups.c b/src/dh_groups.c new file mode 100644 index 0000000..920f3f6 --- /dev/null +++ b/src/dh_groups.c @@ -0,0 +1,97 @@ +#include "options.h" +#include "dh_groups.h" + +#if DROPBEAR_NORMAL_DH + +#if DROPBEAR_DH_GROUP1 +/* diffie-hellman-group1-sha1 value for p */ +const unsigned char dh_p_1[DH_P_1_LEN] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +#endif /* DROPBEAR_DH_GROUP1 */ + +#if DROPBEAR_DH_GROUP14 +/* diffie-hellman-group14-sha1 value for p */ +const unsigned char dh_p_14[DH_P_14_LEN] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF}; +#endif /* DROPBEAR_DH_GROUP14 */ + +#if DROPBEAR_DH_GROUP16 +/* diffie-hellman-group16-256 value for p */ +const unsigned char dh_p_16[DH_P_16_LEN] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, + 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, + 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, + 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, + 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, 0x4F, + 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, + 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, + 0xB6, 0xF4, 0x06, 0xB7, 0xED, 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, + 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, + 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, + 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, + 0x5F, 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, + 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, + 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, 0xE3, 0x9E, 0x77, + 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, + 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, 0xDE, 0x2B, 0xCB, 0xF6, 0x95, + 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, + 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAA, 0xC4, + 0x2D, 0xAD, 0x33, 0x17, 0x0D, 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, + 0xDF, 0x1C, 0xBA, 0x64, 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, + 0xEA, 0x71, 0x57, 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, + 0xE4, 0xC7, 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, + 0xE0, 0x4A, 0x25, 0x61, 0x9D, 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, + 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02, 0x73, 0x3E, + 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, 0xBB, 0xE1, + 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, 0xBA, 0xD9, 0x46, + 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, 0x43, 0xDB, 0x5B, 0xFC, + 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, 0xA9, 0x21, 0x08, 0x01, 0x1A, + 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, + 0x5B, 0x26, 0x99, 0xC3, 0x27, 0x18, 0x6A, 0xF4, 0xE2, 0x3C, 0x1A, 0x94, 0x68, + 0x34, 0xB6, 0x15, 0x0B, 0xDA, 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, + 0xDB, 0xBB, 0xC2, 0xDB, 0x04, 0xDE, 0x8E, 0xF9, 0x2E, 0x8E, 0xFC, 0x14, 0x1F, + 0xBE, 0xCA, 0xA6, 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, 0x99, 0xB2, + 0x96, 0x4F, 0xA0, 0x90, 0xC3, 0xA2, 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, + 0xED, 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, 0xB8, 0x1B, 0xDD, 0x76, + 0x21, 0x70, 0x48, 0x1C, 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, 0x93, + 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, 0x86, 0xFF, 0xB7, 0xDC, 0x90, 0xA6, + 0xC0, 0x8F, 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x06, 0x31, 0x99, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +#endif /* DROPBEAR_DH_GROUP16 */ + +/* Same for all groups */ +const int DH_G_VAL = 2; + +#endif /* DROPBEAR_NORMAL_DH */ diff --git a/src/dh_groups.h b/src/dh_groups.h new file mode 100644 index 0000000..c995937 --- /dev/null +++ b/src/dh_groups.h @@ -0,0 +1,26 @@ +#ifndef DROPBEAR_DH_GROUPS_H +#define DROPBEAR_DH_GROUPS_H +#include "options.h" + +#if DROPBEAR_NORMAL_DH + +#if DROPBEAR_DH_GROUP1 +#define DH_P_1_LEN 128 +extern const unsigned char dh_p_1[DH_P_1_LEN]; +#endif + +#if DROPBEAR_DH_GROUP14 +#define DH_P_14_LEN 256 +extern const unsigned char dh_p_14[DH_P_14_LEN]; +#endif + +#if DROPBEAR_DH_GROUP16 +#define DH_P_16_LEN 512 +extern const unsigned char dh_p_16[DH_P_16_LEN]; +#endif + +extern const int DH_G_VAL; + +#endif /* DROPBEAR_NORMAL_DH */ + +#endif diff --git a/src/dropbear_lint.sh b/src/dropbear_lint.sh new file mode 100755 index 0000000..4e8d33b --- /dev/null +++ b/src/dropbear_lint.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +EXITCODE=0 + +# #ifdef instead of #if +grep '#ifdef DROPBEAR' -I -- *.c *.h && EXITCODE=1 + +exit $EXITCODE diff --git a/src/dropbearconvert.c b/src/dropbearconvert.c new file mode 100644 index 0000000..950608b --- /dev/null +++ b/src/dropbearconvert.c @@ -0,0 +1,145 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* This program converts to/from Dropbear and OpenSSH private-key formats */ +#include "includes.h" +#include "signkey.h" +#include "buffer.h" +#include "dbutil.h" +#include "keyimport.h" +#include "crypto_desc.h" +#include "dbrandom.h" + + +static int do_convert(int intype, const char* infile, int outtype, + const char* outfile); + +static void printhelp(char * progname); + +static void printhelp(char * progname) { + + fprintf(stderr, "Usage: %s <inputtype> <outputtype> <inputfile> <outputfile>\n\n" + "CAUTION: This program is for convenience only, and is not secure if used on\n" + "untrusted input files, ie it could allow arbitrary code execution.\n" + "All parameters must be specified in order.\n" + "\n" + "The input and output types are one of:\n" + "openssh\n" + "dropbear\n" + "\n" + "Example:\n" + "dropbearconvert openssh dropbear /etc/ssh/ssh_host_rsa_key /etc/dropbear_rsa_host_key\n", + progname); +} + +#if defined(DBMULTI_dropbearconvert) || !DROPBEAR_MULTI +#if defined(DBMULTI_dropbearconvert) && DROPBEAR_MULTI +int dropbearconvert_main(int argc, char ** argv) { +#else +int main(int argc, char ** argv) { +#endif + + int intype, outtype; + const char* infile; + const char* outfile; + + crypto_init(); + seedrandom(); + +#if DEBUG_TRACE + /* It's hard for it to get in the way _too_ much */ + debug_trace = DROPBEAR_VERBOSE_LEVEL; +#endif + + /* get the commandline options */ + if (argc != 5) { + fprintf(stderr, "All arguments must be specified\n"); + goto usage; + } + + /* input type */ + if (argv[1][0] == 'd') { + intype = KEYFILE_DROPBEAR; + } else if (argv[1][0] == 'o') { + intype = KEYFILE_OPENSSH; + } else { + fprintf(stderr, "Invalid input key type\n"); + goto usage; + } + + /* output type */ + if (argv[2][0] == 'd') { + outtype = KEYFILE_DROPBEAR; + } else if (argv[2][0] == 'o') { + outtype = KEYFILE_OPENSSH; + } else { + fprintf(stderr, "Invalid output key type\n"); + goto usage; + } + + /* we don't want output readable by others */ + umask(077); + + infile = argv[3]; + outfile = argv[4]; + + return do_convert(intype, infile, outtype, outfile); + +usage: + printhelp(argv[0]); + return 1; +} +#endif + +static int do_convert(int intype, const char* infile, int outtype, + const char* outfile) { + + sign_key * key = NULL; + const char * keytype = NULL; + int ret = 1; + + key = import_read(infile, NULL, intype); + if (!key) { + fprintf(stderr, "Error reading key from '%s'\n", + infile); + goto out; + } + + keytype = signkey_name_from_type(key->type, NULL); + + fprintf(stderr, "Key is a %s key\n", keytype); + + if (import_write(outfile, key, NULL, outtype) != 1) { + fprintf(stderr, "Error writing key to '%s'\n", outfile); + } else { + fprintf(stderr, "Wrote key to '%s'\n", outfile); + ret = 0; + } + +out: + if (key) { + sign_key_free(key); + } + return ret; +} diff --git a/src/dropbearkey.c b/src/dropbearkey.c new file mode 100644 index 0000000..bd9c6af --- /dev/null +++ b/src/dropbearkey.c @@ -0,0 +1,367 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* The format of the keyfiles is basically a raw dump of the buffer. Data types + * are specified in the transport rfc 4253 - string is a 32-bit len then the + * non-null-terminated string, mp_int is a 32-bit len then the bignum data. + * The actual functions are buf_put_rsa_priv_key() and buf_put_dss_priv_key() + + * RSA: + * string "ssh-rsa" + * mp_int e + * mp_int n + * mp_int d + * mp_int p (newer versions only) + * mp_int q (newer versions only) + * + * DSS: + * string "ssh-dss" + * mp_int p + * mp_int q + * mp_int g + * mp_int y + * mp_int x + * + * Ed25519: + * string "ssh-ed25519" + * string k (32 bytes) + A (32 bytes) + * + */ +#include "includes.h" +#include "signkey.h" +#include "buffer.h" +#include "dbutil.h" + +#include "genrsa.h" +#include "gendss.h" +#include "gened25519.h" +#include "ecdsa.h" +#include "crypto_desc.h" +#include "dbrandom.h" +#include "gensignkey.h" + +static void printhelp(char * progname); + + +static void printpubkey(sign_key * key, int keytype); +static int printpubfile(const char* filename); + +/* Print a help message */ +static void printhelp(char * progname) { + + fprintf(stderr, "Usage: %s -t <type> -f <filename> [-s bits]\n" + "-t type Type of key to generate. One of:\n" +#if DROPBEAR_RSA + " rsa\n" +#endif +#if DROPBEAR_DSS + " dss\n" +#endif +#if DROPBEAR_ECDSA + " ecdsa\n" +#endif +#if DROPBEAR_ED25519 + " ed25519\n" +#endif + "-f filename Use filename for the secret key.\n" + " ~/.ssh/id_dropbear is recommended for client keys.\n" + "-s bits Key size in bits, should be a multiple of 8 (optional)\n" +#if DROPBEAR_DSS + " DSS has a fixed size of 1024 bits\n" +#endif +#if DROPBEAR_ECDSA + " ECDSA has sizes " +#if DROPBEAR_ECC_256 + "256 " +#endif +#if DROPBEAR_ECC_384 + "384 " +#endif +#if DROPBEAR_ECC_521 + "521 " +#endif + "\n" +#endif +#if DROPBEAR_ED25519 + " Ed25519 has a fixed size of 256 bits\n" +#endif + "-y Just print the publickey and fingerprint for the\n private key in <filename>.\n" +#if DEBUG_TRACE + "-v verbose\n" +#endif + ,progname); +} + +/* fails fatally */ +static void check_signkey_bits(enum signkey_type type, int bits) +{ + switch (type) { +#if DROPBEAR_ED25519 + case DROPBEAR_SIGNKEY_ED25519: + if (bits != 256) { + dropbear_exit("Ed25519 keys have a fixed size of 256 bits\n"); + exit(EXIT_FAILURE); + } + break; +#endif +#if DROPBEAR_RSA + case DROPBEAR_SIGNKEY_RSA: + if (bits < 1024 || bits > 4096 || (bits % 8 != 0)) { + dropbear_exit("Bits must satisfy 1024 <= bits <= 4096, and be a" + " multiple of 8\n"); + } + break; +#endif +#if DROPBEAR_DSS + case DROPBEAR_SIGNKEY_DSS: + if (bits != 1024) { + dropbear_exit("DSS keys have a fixed size of 1024 bits\n"); + exit(EXIT_FAILURE); + } + break; +#endif + default: + (void)0; /* quiet, compiler. ecdsa handles checks itself */ + } +} + +#if defined(DBMULTI_dropbearkey) || !DROPBEAR_MULTI +#if defined(DBMULTI_dropbearkey) && DROPBEAR_MULTI +int dropbearkey_main(int argc, char ** argv) { +#else +int main(int argc, char ** argv) { +#endif + + int i; + char ** next = NULL; + char * filename = NULL; + enum signkey_type keytype = DROPBEAR_SIGNKEY_NONE; + char * typetext = NULL; + char * sizetext = NULL; + unsigned int bits = 0, genbits; + int printpub = 0; + + crypto_init(); + seedrandom(); + + /* get the commandline options */ + for (i = 1; i < argc; i++) { + if (argv[i] == NULL) { + continue; /* Whack */ + } + if (next) { + *next = argv[i]; + next = NULL; + continue; + } + + if (argv[i][0] == '-') { + switch (argv[i][1]) { + case 'f': + next = &filename; + break; + case 't': + next = &typetext; + break; + case 's': + next = &sizetext; + break; + case 'y': + printpub = 1; + break; + case 'h': + printhelp(argv[0]); + exit(EXIT_SUCCESS); + break; +#if DEBUG_TRACE + case 'v': + debug_trace = DROPBEAR_VERBOSE_LEVEL; + break; +#endif + default: + fprintf(stderr, "Unknown argument %s\n", argv[i]); + printhelp(argv[0]); + exit(EXIT_FAILURE); + break; + } + } + } + + if (!filename) { + fprintf(stderr, "Must specify a key filename\n"); + printhelp(argv[0]); + exit(EXIT_FAILURE); + } + + if (printpub) { + int ret = printpubfile(filename); + exit(ret); + } + + /* check/parse args */ + if (!typetext) { + fprintf(stderr, "Must specify key type\n"); + printhelp(argv[0]); + exit(EXIT_FAILURE); + } + +#if DROPBEAR_RSA + if (strcmp(typetext, "rsa") == 0) + { + keytype = DROPBEAR_SIGNKEY_RSA; + } +#endif +#if DROPBEAR_DSS + if (strcmp(typetext, "dss") == 0) + { + keytype = DROPBEAR_SIGNKEY_DSS; + } +#endif +#if DROPBEAR_ECDSA + if (strcmp(typetext, "ecdsa") == 0) + { + keytype = DROPBEAR_SIGNKEY_ECDSA_KEYGEN; + } +#endif +#if DROPBEAR_ED25519 + if (strcmp(typetext, "ed25519") == 0) + { + keytype = DROPBEAR_SIGNKEY_ED25519; + } +#endif + + if (keytype == DROPBEAR_SIGNKEY_NONE) { + fprintf(stderr, "Unknown key type '%s'\n", typetext); + printhelp(argv[0]); + exit(EXIT_FAILURE); + } + + if (sizetext) { + if (sscanf(sizetext, "%u", &bits) != 1) { + fprintf(stderr, "Bits must be an integer\n"); + exit(EXIT_FAILURE); + } + + check_signkey_bits(keytype, bits);; + } + + genbits = signkey_generate_get_bits(keytype, bits); + fprintf(stderr, "Generating %u bit %s key, this may take a while...\n", genbits, typetext); + if (signkey_generate(keytype, bits, filename, 0) == DROPBEAR_FAILURE) + { + dropbear_exit("Failed to generate key.\n"); + } + + printpubfile(filename); + + return EXIT_SUCCESS; +} +#endif + +static int printpubfile(const char* filename) { + + buffer *buf = NULL; + sign_key *key = NULL; + enum signkey_type keytype; + int ret; + int err = DROPBEAR_FAILURE; + + buf = buf_new(MAX_PRIVKEY_SIZE); + ret = buf_readfile(buf, filename); + + if (ret != DROPBEAR_SUCCESS) { + fprintf(stderr, "Failed reading '%s'\n", filename); + goto out; + } + + key = new_sign_key(); + keytype = DROPBEAR_SIGNKEY_ANY; + + buf_setpos(buf, 0); + ret = buf_get_priv_key(buf, key, &keytype); + if (ret == DROPBEAR_FAILURE) { + fprintf(stderr, "Bad key in '%s'\n", filename); + goto out; + } + + printpubkey(key, keytype); + + err = DROPBEAR_SUCCESS; + +out: + buf_burn_free(buf); + buf = NULL; + if (key) { + sign_key_free(key); + key = NULL; + } + return err; +} + +static void printpubkey(sign_key * key, int keytype) { + + buffer * buf = NULL; + unsigned char base64key[MAX_PUBKEY_SIZE*2]; + unsigned long base64len; + int err; + const char * typestring = NULL; + char *fp = NULL; + int len; + struct passwd * pw = NULL; + char * username = NULL; + char hostname[100]; + + buf = buf_new(MAX_PUBKEY_SIZE); + buf_put_pub_key(buf, key, keytype); + buf_setpos(buf, 4); + + len = buf->len - buf->pos; + + base64len = sizeof(base64key); + err = base64_encode(buf_getptr(buf, len), len, base64key, &base64len); + + if (err != CRYPT_OK) { + dropbear_exit("base64 failed"); + } + + typestring = signkey_name_from_type(keytype, NULL); + + fp = sign_key_fingerprint(buf_getptr(buf, len), len); + + /* a user@host comment is informative */ + username = ""; + pw = getpwuid(getuid()); + if (pw) { + username = pw->pw_name; + } + + gethostname(hostname, sizeof(hostname)); + hostname[sizeof(hostname)-1] = '\0'; + + printf("Public key portion is:\n%s %s %s@%s\nFingerprint: %s\n", + typestring, base64key, username, hostname, fp); + + m_free(fp); + buf_free(buf); +} diff --git a/src/dss.c b/src/dss.c new file mode 100644 index 0000000..012e72e --- /dev/null +++ b/src/dss.c @@ -0,0 +1,378 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "bignum.h" +#include "dss.h" +#include "buffer.h" +#include "ssh.h" +#include "dbrandom.h" + +/* Handle DSS (Digital Signature Standard), aka DSA (D.S. Algorithm), + * operations, such as key reading, signing, verification. Key generation + * is in gendss.c, since it isn't required in the server itself. + * + * See FIPS186 or the Handbook of Applied Cryptography for details of the + * algorithm */ + +#if DROPBEAR_DSS + +/* Load a dss key from a buffer, initialising the values. + * The key will have the same format as buf_put_dss_key. + * These should be freed with dss_key_free. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int buf_get_dss_pub_key(buffer* buf, dropbear_dss_key *key) { + int ret = DROPBEAR_FAILURE; + + TRACE(("enter buf_get_dss_pub_key")) + dropbear_assert(key != NULL); + m_mp_alloc_init_multi(&key->p, &key->q, &key->g, &key->y, NULL); + key->x = NULL; + + buf_incrpos(buf, 4+SSH_SIGNKEY_DSS_LEN); /* int + "ssh-dss" */ + if (buf_getmpint(buf, key->p) == DROPBEAR_FAILURE + || buf_getmpint(buf, key->q) == DROPBEAR_FAILURE + || buf_getmpint(buf, key->g) == DROPBEAR_FAILURE + || buf_getmpint(buf, key->y) == DROPBEAR_FAILURE) { + TRACE(("leave buf_get_dss_pub_key: failed reading mpints")) + ret = DROPBEAR_FAILURE; + goto out; + } + + if (mp_count_bits(key->p) != DSS_P_BITS) { + dropbear_log(LOG_WARNING, "Bad DSS p"); + ret = DROPBEAR_FAILURE; + goto out; + } + + if (mp_count_bits(key->q) != DSS_Q_BITS) { + dropbear_log(LOG_WARNING, "Bad DSS q"); + ret = DROPBEAR_FAILURE; + goto out; + } + + /* test 1 < g < p */ + if (mp_cmp_d(key->g, 1) != MP_GT) { + dropbear_log(LOG_WARNING, "Bad DSS g"); + ret = DROPBEAR_FAILURE; + goto out; + } + if (mp_cmp(key->g, key->p) != MP_LT) { + dropbear_log(LOG_WARNING, "Bad DSS g"); + ret = DROPBEAR_FAILURE; + goto out; + } + + ret = DROPBEAR_SUCCESS; + TRACE(("leave buf_get_dss_pub_key: success")) +out: + if (ret == DROPBEAR_FAILURE) { + m_mp_free_multi(&key->p, &key->q, &key->g, &key->y, NULL); + } + return ret; +} + +/* Same as buf_get_dss_pub_key, but reads a private "x" key at the end. + * Loads a private dss key from a buffer + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int buf_get_dss_priv_key(buffer* buf, dropbear_dss_key *key) { + + int ret = DROPBEAR_FAILURE; + + dropbear_assert(key != NULL); + + ret = buf_get_dss_pub_key(buf, key); + if (ret == DROPBEAR_FAILURE) { + return DROPBEAR_FAILURE; + } + + m_mp_alloc_init_multi(&key->x, NULL); + ret = buf_getmpint(buf, key->x); + if (ret == DROPBEAR_FAILURE) { + m_mp_free_multi(&key->x, NULL); + } + + return ret; +} + + +/* Clear and free the memory used by a public or private key */ +void dss_key_free(dropbear_dss_key *key) { + + TRACE2(("enter dsa_key_free")) + if (key == NULL) { + TRACE2(("enter dsa_key_free: key == NULL")) + return; + } + m_mp_free_multi(&key->p, &key->q, &key->g, &key->y, &key->x, NULL); + m_free(key); + TRACE2(("leave dsa_key_free")) +} + +/* put the dss public key into the buffer in the required format: + * + * string "ssh-dss" + * mpint p + * mpint q + * mpint g + * mpint y + */ +void buf_put_dss_pub_key(buffer* buf, const dropbear_dss_key *key) { + + dropbear_assert(key != NULL); + buf_putstring(buf, SSH_SIGNKEY_DSS, SSH_SIGNKEY_DSS_LEN); + buf_putmpint(buf, key->p); + buf_putmpint(buf, key->q); + buf_putmpint(buf, key->g); + buf_putmpint(buf, key->y); + +} + +/* Same as buf_put_dss_pub_key, but with the private "x" key appended */ +void buf_put_dss_priv_key(buffer* buf, const dropbear_dss_key *key) { + + dropbear_assert(key != NULL); + buf_put_dss_pub_key(buf, key); + buf_putmpint(buf, key->x); + +} + +#if DROPBEAR_SIGNKEY_VERIFY +/* Verify a DSS signature (in buf) made on data by the key given. + * returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int buf_dss_verify(buffer* buf, const dropbear_dss_key *key, const buffer *data_buf) { + unsigned char msghash[SHA1_HASH_SIZE]; + hash_state hs; + int ret = DROPBEAR_FAILURE; + DEF_MP_INT(val1); + DEF_MP_INT(val2); + DEF_MP_INT(val3); + DEF_MP_INT(val4); + char * string = NULL; + unsigned int stringlen; + + TRACE(("enter buf_dss_verify")) + dropbear_assert(key != NULL); + + m_mp_init_multi(&val1, &val2, &val3, &val4, NULL); + + /* get blob, check length */ + string = buf_getstring(buf, &stringlen); + if (stringlen != 2*SHA1_HASH_SIZE) { + goto out; + } + +#if DEBUG_DSS_VERIFY + printmpint("dss verify p", key->p); + printmpint("dss verify q", key->q); + printmpint("dss verify g", key->g); + printmpint("dss verify y", key->y); +#endif + + /* hash the data */ + sha1_init(&hs); + sha1_process(&hs, data_buf->data, data_buf->len); + sha1_done(&hs, msghash); + + /* create the signature - s' and r' are the received signatures in buf */ + /* w = (s')-1 mod q */ + /* let val1 = s' */ + bytes_to_mp(&val1, (const unsigned char*) &string[SHA1_HASH_SIZE], SHA1_HASH_SIZE); +#if DEBUG_DSS_VERIFY + printmpint("dss verify s'", &val1); +#endif + + if (mp_cmp(&val1, key->q) != MP_LT) { + TRACE(("verify failed, s' >= q")) + goto out; + } + if (mp_cmp_d(&val1, 0) != MP_GT) { + TRACE(("verify failed, s' <= 0")) + goto out; + } + /* let val2 = w = (s')^-1 mod q*/ + if (mp_invmod(&val1, key->q, &val2) != MP_OKAY) { + goto out; + } + + /* u1 = ((SHA(M')w) mod q */ + /* let val1 = SHA(M') = msghash */ + bytes_to_mp(&val1, msghash, SHA1_HASH_SIZE); +#if DEBUG_DSS_VERIFY + printmpint("dss verify r'", &val1); +#endif + + /* let val3 = u1 = ((SHA(M')w) mod q */ + if (mp_mulmod(&val1, &val2, key->q, &val3) != MP_OKAY) { + goto out; + } + + /* u2 = ((r')w) mod q */ + /* let val1 = r' */ + bytes_to_mp(&val1, (const unsigned char*) &string[0], SHA1_HASH_SIZE); + if (mp_cmp(&val1, key->q) != MP_LT) { + TRACE(("verify failed, r' >= q")) + goto out; + } + if (mp_cmp_d(&val1, 0) != MP_GT) { + TRACE(("verify failed, r' <= 0")) + goto out; + } + /* let val4 = u2 = ((r')w) mod q */ + if (mp_mulmod(&val1, &val2, key->q, &val4) != MP_OKAY) { + goto out; + } + + /* v = (((g)^u1 (y)^u2) mod p) mod q */ + /* val2 = g^u1 mod p */ + if (mp_exptmod(key->g, &val3, key->p, &val2) != MP_OKAY) { + goto out; + } + /* val3 = y^u2 mod p */ + if (mp_exptmod(key->y, &val4, key->p, &val3) != MP_OKAY) { + goto out; + } + /* val4 = ((g)^u1 (y)^u2) mod p */ + if (mp_mulmod(&val2, &val3, key->p, &val4) != MP_OKAY) { + goto out; + } + /* val2 = v = (((g)^u1 (y)^u2) mod p) mod q */ + if (mp_mod(&val4, key->q, &val2) != MP_OKAY) { + goto out; + } + + /* check whether signatures verify */ + if (mp_cmp(&val2, &val1) == MP_EQ) { + /* good sig */ + ret = DROPBEAR_SUCCESS; + } + +out: + mp_clear_multi(&val1, &val2, &val3, &val4, NULL); + m_free(string); + + return ret; + +} +#endif /* DROPBEAR_SIGNKEY_VERIFY */ + +/* Sign the data presented with key, writing the signature contents + * to the buffer */ +void buf_put_dss_sign(buffer* buf, const dropbear_dss_key *key, const buffer *data_buf) { + unsigned char msghash[SHA1_HASH_SIZE]; + unsigned int writelen; + unsigned int i; + size_t written; + DEF_MP_INT(dss_k); + DEF_MP_INT(dss_m); + DEF_MP_INT(dss_temp1); + DEF_MP_INT(dss_temp2); + DEF_MP_INT(dss_r); + DEF_MP_INT(dss_s); + hash_state hs; + + TRACE(("enter buf_put_dss_sign")) + dropbear_assert(key != NULL); + + /* hash the data */ + sha1_init(&hs); + sha1_process(&hs, data_buf->data, data_buf->len); + sha1_done(&hs, msghash); + + m_mp_init_multi(&dss_k, &dss_temp1, &dss_temp2, &dss_r, &dss_s, + &dss_m, NULL); + /* the random number generator's input has included the private key which + * avoids DSS's problem of private key exposure due to low entropy */ + gen_random_mpint(key->q, &dss_k); + + /* now generate the actual signature */ + bytes_to_mp(&dss_m, msghash, SHA1_HASH_SIZE); + + /* g^k mod p */ + if (mp_exptmod(key->g, &dss_k, key->p, &dss_temp1) != MP_OKAY) { + dropbear_exit("DSS error"); + } + /* r = (g^k mod p) mod q */ + if (mp_mod(&dss_temp1, key->q, &dss_r) != MP_OKAY) { + dropbear_exit("DSS error"); + } + + /* x*r mod q */ + if (mp_mulmod(&dss_r, key->x, key->q, &dss_temp1) != MP_OKAY) { + dropbear_exit("DSS error"); + } + /* (SHA1(M) + xr) mod q) */ + if (mp_addmod(&dss_m, &dss_temp1, key->q, &dss_temp2) != MP_OKAY) { + dropbear_exit("DSS error"); + } + + /* (k^-1) mod q */ + if (mp_invmod(&dss_k, key->q, &dss_temp1) != MP_OKAY) { + dropbear_exit("DSS error"); + } + + /* s = (k^-1(SHA1(M) + xr)) mod q */ + if (mp_mulmod(&dss_temp1, &dss_temp2, key->q, &dss_s) != MP_OKAY) { + dropbear_exit("DSS error"); + } + + buf_putstring(buf, SSH_SIGNKEY_DSS, SSH_SIGNKEY_DSS_LEN); + buf_putint(buf, 2*SHA1_HASH_SIZE); + + writelen = mp_ubin_size(&dss_r); + dropbear_assert(writelen <= SHA1_HASH_SIZE); + /* need to pad to 160 bits with leading zeros */ + for (i = 0; i < SHA1_HASH_SIZE - writelen; i++) { + buf_putbyte(buf, 0); + } + if (mp_to_ubin(&dss_r, buf_getwriteptr(buf, writelen), writelen, &written) + != MP_OKAY) { + dropbear_exit("DSS error"); + } + mp_clear(&dss_r); + buf_incrwritepos(buf, written); + + writelen = mp_ubin_size(&dss_s); + dropbear_assert(writelen <= SHA1_HASH_SIZE); + /* need to pad to 160 bits with leading zeros */ + for (i = 0; i < SHA1_HASH_SIZE - writelen; i++) { + buf_putbyte(buf, 0); + } + if (mp_to_ubin(&dss_s, buf_getwriteptr(buf, writelen), writelen, &written) + != MP_OKAY) { + dropbear_exit("DSS error"); + } + mp_clear(&dss_s); + buf_incrwritepos(buf, written); + + mp_clear_multi(&dss_k, &dss_temp1, &dss_temp2, &dss_r, &dss_s, + &dss_m, NULL); + + /* create the signature to return */ + + TRACE(("leave buf_put_dss_sign")) +} + +#endif /* DROPBEAR_DSS */ diff --git a/src/dss.h b/src/dss.h new file mode 100644 index 0000000..40806e5 --- /dev/null +++ b/src/dss.h @@ -0,0 +1,59 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_DSS_H_ +#define DROPBEAR_DSS_H_ + +#include "includes.h" +#include "buffer.h" + +#if DROPBEAR_DSS + +typedef struct dropbear_DSS_Key { + + mp_int* p; + mp_int* q; + mp_int* g; + mp_int* y; + /* x is the private part */ + mp_int* x; + +} dropbear_dss_key; + +#define DSS_P_BITS 1024 +#define DSS_Q_BITS 160 + +void buf_put_dss_sign(buffer* buf, const dropbear_dss_key *key, const buffer *data_buf); +#if DROPBEAR_SIGNKEY_VERIFY +int buf_dss_verify(buffer* buf, const dropbear_dss_key *key, const buffer *data_buf); +#endif +int buf_get_dss_pub_key(buffer* buf, dropbear_dss_key *key); +int buf_get_dss_priv_key(buffer* buf, dropbear_dss_key *key); +void buf_put_dss_pub_key(buffer* buf, const dropbear_dss_key *key); +void buf_put_dss_priv_key(buffer* buf, const dropbear_dss_key *key); +void dss_key_free(dropbear_dss_key *key); + +#endif /* DROPBEAR_DSS */ + +#endif /* DROPBEAR_DSS_H_ */ diff --git a/src/ecc.c b/src/ecc.c new file mode 100644 index 0000000..eaca65a --- /dev/null +++ b/src/ecc.c @@ -0,0 +1,264 @@ +#include "includes.h" +#include "ecc.h" +#include "dbutil.h" +#include "bignum.h" + +#if DROPBEAR_ECC + +/* .dp members are filled out by dropbear_ecc_fill_dp() at startup */ +#if DROPBEAR_ECC_256 +struct dropbear_ecc_curve ecc_curve_nistp256 = { + 32, /* .ltc_size */ + NULL, /* .dp */ + &sha256_desc, /* .hash_desc */ + "nistp256" /* .name */ +}; +#endif +#if DROPBEAR_ECC_384 +struct dropbear_ecc_curve ecc_curve_nistp384 = { + 48, /* .ltc_size */ + NULL, /* .dp */ + &sha384_desc, /* .hash_desc */ + "nistp384" /* .name */ +}; +#endif +#if DROPBEAR_ECC_521 +struct dropbear_ecc_curve ecc_curve_nistp521 = { + 66, /* .ltc_size */ + NULL, /* .dp */ + &sha512_desc, /* .hash_desc */ + "nistp521" /* .name */ +}; +#endif + +struct dropbear_ecc_curve *dropbear_ecc_curves[] = { +#if DROPBEAR_ECC_256 + &ecc_curve_nistp256, +#endif +#if DROPBEAR_ECC_384 + &ecc_curve_nistp384, +#endif +#if DROPBEAR_ECC_521 + &ecc_curve_nistp521, +#endif + NULL +}; + +void dropbear_ecc_fill_dp() { + struct dropbear_ecc_curve **curve; + /* libtomcrypt guarantees they're ordered by size */ + const ltc_ecc_set_type *dp = ltc_ecc_sets; + for (curve = dropbear_ecc_curves; *curve; curve++) { + for (;dp->size > 0; dp++) { + if (dp->size == (*curve)->ltc_size) { + (*curve)->dp = dp; + break; + } + } + if (!(*curve)->dp) { + dropbear_exit("Missing ECC params %s", (*curve)->name); + } + } +} + +struct dropbear_ecc_curve* curve_for_dp(const ltc_ecc_set_type *dp) { + struct dropbear_ecc_curve **curve = NULL; + for (curve = dropbear_ecc_curves; *curve; curve++) { + if ((*curve)->dp == dp) { + break; + } + } + assert(*curve); + return *curve; +} + +ecc_key * new_ecc_key(void) { + ecc_key *key = m_malloc(sizeof(*key)); + m_mp_alloc_init_multi((mp_int**)&key->pubkey.x, (mp_int**)&key->pubkey.y, + (mp_int**)&key->pubkey.z, (mp_int**)&key->k, NULL); + return key; +} + +/* Copied from libtomcrypt ecc_import.c (version there is static), modified + for different mp_int pointer without LTC_SOURCE */ +static int ecc_is_point(const ecc_key *key) +{ + mp_int *prime, *b, *t1, *t2; + int err; + + m_mp_alloc_init_multi(&prime, &b, &t1, &t2, NULL); + + /* load prime and b */ + if ((err = mp_read_radix(prime, key->dp->prime, 16)) != CRYPT_OK) { goto error; } + if ((err = mp_read_radix(b, key->dp->B, 16)) != CRYPT_OK) { goto error; } + + /* compute y^2 */ + if ((err = mp_sqr(key->pubkey.y, t1)) != CRYPT_OK) { goto error; } + + /* compute x^3 */ + if ((err = mp_sqr(key->pubkey.x, t2)) != CRYPT_OK) { goto error; } + if ((err = mp_mod(t2, prime, t2)) != CRYPT_OK) { goto error; } + if ((err = mp_mul(key->pubkey.x, t2, t2)) != CRYPT_OK) { goto error; } + + /* compute y^2 - x^3 */ + if ((err = mp_sub(t1, t2, t1)) != CRYPT_OK) { goto error; } + + /* compute y^2 - x^3 + 3x */ + if ((err = mp_add(t1, key->pubkey.x, t1)) != CRYPT_OK) { goto error; } + if ((err = mp_add(t1, key->pubkey.x, t1)) != CRYPT_OK) { goto error; } + if ((err = mp_add(t1, key->pubkey.x, t1)) != CRYPT_OK) { goto error; } + if ((err = mp_mod(t1, prime, t1)) != CRYPT_OK) { goto error; } + while (mp_cmp_d(t1, 0) == LTC_MP_LT) { + if ((err = mp_add(t1, prime, t1)) != CRYPT_OK) { goto error; } + } + while (mp_cmp(t1, prime) != LTC_MP_LT) { + if ((err = mp_sub(t1, prime, t1)) != CRYPT_OK) { goto error; } + } + + /* compare to b */ + if (mp_cmp(t1, b) != LTC_MP_EQ) { + err = CRYPT_INVALID_PACKET; + } else { + err = CRYPT_OK; + } + + error: + mp_clear_multi(prime, b, t1, t2, NULL); + m_free(prime); + m_free(b); + m_free(t1); + m_free(t2); + return err; +} + +/* For the "ephemeral public key octet string" in ECDH (rfc5656 section 4) */ +void buf_put_ecc_raw_pubkey_string(buffer *buf, ecc_key *key) { + unsigned long len = key->dp->size*2 + 1; + int err; + buf_putint(buf, len); + err = ecc_ansi_x963_export(key, buf_getwriteptr(buf, len), &len); + if (err != CRYPT_OK) { + dropbear_exit("ECC error"); + } + buf_incrwritepos(buf, len); +} + +/* For the "ephemeral public key octet string" in ECDH (rfc5656 section 4) */ +ecc_key * buf_get_ecc_raw_pubkey(buffer *buf, const struct dropbear_ecc_curve *curve) { + ecc_key *key = NULL; + int ret = DROPBEAR_FAILURE; + const unsigned int size = curve->dp->size; + unsigned char first; + + TRACE(("enter buf_get_ecc_raw_pubkey")) + + buf_setpos(buf, 0); + first = buf_getbyte(buf); + if (first == 2 || first == 3) { + dropbear_log(LOG_WARNING, "Dropbear doesn't support ECC point compression"); + return NULL; + } + if (first != 4 || buf->len != 1+2*size) { + TRACE(("leave, wrong size")) + return NULL; + } + + key = new_ecc_key(); + key->dp = curve->dp; + + if (mp_from_ubin(key->pubkey.x, buf_getptr(buf, size), size) != MP_OKAY) { + TRACE(("failed to read x")) + goto out; + } + buf_incrpos(buf, size); + + if (mp_from_ubin(key->pubkey.y, buf_getptr(buf, size), size) != MP_OKAY) { + TRACE(("failed to read y")) + goto out; + } + buf_incrpos(buf, size); + + mp_set(key->pubkey.z, 1); + + if (ecc_is_point(key) != CRYPT_OK) { + TRACE(("failed, not a point")) + goto out; + } + + /* SEC1 3.2.3.1 Check that Q != 0 */ + if (mp_cmp_d(key->pubkey.x, 0) == LTC_MP_EQ) { + TRACE(("failed, x == 0")) + goto out; + } + if (mp_cmp_d(key->pubkey.y, 0) == LTC_MP_EQ) { + TRACE(("failed, y == 0")) + goto out; + } + + ret = DROPBEAR_SUCCESS; + + out: + if (ret == DROPBEAR_FAILURE) { + if (key) { + ecc_free(key); + m_free(key); + key = NULL; + } + } + + return key; + +} + +/* a modified version of libtomcrypt's "ecc_shared_secret" to output + a mp_int instead. */ +mp_int * dropbear_ecc_shared_secret(ecc_key *public_key, const ecc_key *private_key) +{ + ecc_point *result = NULL; + mp_int *prime = NULL, *shared_secret = NULL; + int err = DROPBEAR_FAILURE; + + /* type valid? */ + if (private_key->type != PK_PRIVATE) { + goto out; + } + + if (private_key->dp != public_key->dp) { + goto out; + } + + /* make new point */ + result = ltc_ecc_new_point(); + if (result == NULL) { + goto out; + } + + prime = m_malloc(sizeof(*prime)); + m_mp_init(prime); + + if (mp_read_radix(prime, (char *)private_key->dp->prime, 16) != CRYPT_OK) { + goto out; + } + if (ltc_mp.ecc_ptmul(private_key->k, &public_key->pubkey, result, prime, 1) != CRYPT_OK) { + goto out; + } + + shared_secret = m_malloc(sizeof(*shared_secret)); + m_mp_init(shared_secret); + if (mp_copy(result->x, shared_secret) != CRYPT_OK) { + goto out; + } + + mp_clear(prime); + m_free(prime); + ltc_ecc_del_point(result); + + err = DROPBEAR_SUCCESS; + out: + if (err == DROPBEAR_FAILURE) { + dropbear_exit("ECC error"); + } + return shared_secret; +} + +#endif diff --git a/src/ecc.h b/src/ecc.h new file mode 100644 index 0000000..f4508f8 --- /dev/null +++ b/src/ecc.h @@ -0,0 +1,35 @@ +#ifndef DROPBEAR_DROPBEAR_ECC_H +#define DROPBEAR_DROPBEAR_ECC_H + +#include "includes.h" + +#include "buffer.h" + +#if DROPBEAR_ECC + +struct dropbear_ecc_curve { + int ltc_size; /* to match the byte sizes in ltc_ecc_sets[] */ + const ltc_ecc_set_type *dp; /* curve domain parameters */ + const struct ltc_hash_descriptor *hash_desc; + const char *name; +}; + +extern struct dropbear_ecc_curve ecc_curve_nistp256; +extern struct dropbear_ecc_curve ecc_curve_nistp384; +extern struct dropbear_ecc_curve ecc_curve_nistp521; +extern struct dropbear_ecc_curve *dropbear_ecc_curves[]; + +void dropbear_ecc_fill_dp(void); +struct dropbear_ecc_curve* curve_for_dp(const ltc_ecc_set_type *dp); + +/* "pubkey" refers to a point, but LTC uses ecc_key structure for both public + and private keys */ +void buf_put_ecc_raw_pubkey_string(buffer *buf, ecc_key *key); +ecc_key * buf_get_ecc_raw_pubkey(buffer *buf, const struct dropbear_ecc_curve *curve); +int buf_get_ecc_privkey_string(buffer *buf, ecc_key *key); + +mp_int * dropbear_ecc_shared_secret(ecc_key *pub_key, const ecc_key *priv_key); + +#endif + +#endif /* DROPBEAR_DROPBEAR_ECC_H */ diff --git a/src/ecdsa.c b/src/ecdsa.c new file mode 100644 index 0000000..5ac4e7b --- /dev/null +++ b/src/ecdsa.c @@ -0,0 +1,427 @@ +#include "includes.h" +#include "dbutil.h" +#include "crypto_desc.h" +#include "ecc.h" +#include "ecdsa.h" +#include "signkey.h" + +#if DROPBEAR_ECDSA + +int signkey_is_ecdsa(enum signkey_type type) +{ + return type == DROPBEAR_SIGNKEY_ECDSA_NISTP256 + || type == DROPBEAR_SIGNKEY_ECDSA_NISTP384 + || type == DROPBEAR_SIGNKEY_ECDSA_NISTP521; +} + +enum signkey_type ecdsa_signkey_type(const ecc_key * key) { +#if DROPBEAR_ECC_256 + if (key->dp == ecc_curve_nistp256.dp) { + return DROPBEAR_SIGNKEY_ECDSA_NISTP256; + } +#endif +#if DROPBEAR_ECC_384 + if (key->dp == ecc_curve_nistp384.dp) { + return DROPBEAR_SIGNKEY_ECDSA_NISTP384; + } +#endif +#if DROPBEAR_ECC_521 + if (key->dp == ecc_curve_nistp521.dp) { + return DROPBEAR_SIGNKEY_ECDSA_NISTP521; + } +#endif + return DROPBEAR_SIGNKEY_NONE; +} + +ecc_key *gen_ecdsa_priv_key(unsigned int bit_size) { + const ltc_ecc_set_type *dp = NULL; /* curve domain parameters */ + ecc_key *new_key = NULL; + switch (bit_size) { +#if DROPBEAR_ECC_256 + case 256: + dp = ecc_curve_nistp256.dp; + break; +#endif +#if DROPBEAR_ECC_384 + case 384: + dp = ecc_curve_nistp384.dp; + break; +#endif +#if DROPBEAR_ECC_521 + case 521: + dp = ecc_curve_nistp521.dp; + break; +#endif + } + if (!dp) { + dropbear_exit("Key size %d isn't valid. Try " +#if DROPBEAR_ECC_256 + "256 " +#endif +#if DROPBEAR_ECC_384 + "384 " +#endif +#if DROPBEAR_ECC_521 + "521 " +#endif + , bit_size); + } + + new_key = m_malloc(sizeof(*new_key)); + if (ecc_make_key_ex(NULL, dropbear_ltc_prng, new_key, dp) != CRYPT_OK) { + dropbear_exit("ECC error"); + } + return new_key; +} + +ecc_key *buf_get_ecdsa_pub_key(buffer* buf) { + unsigned char *key_ident = NULL, *identifier = NULL; + unsigned int key_ident_len, identifier_len; + buffer *q_buf = NULL; + struct dropbear_ecc_curve **curve; + ecc_key *new_key = NULL; + + /* string "ecdsa-sha2-[identifier]" or "sk-ecdsa-sha2-nistp256@openssh.com" */ + key_ident = (unsigned char*)buf_getstring(buf, &key_ident_len); + /* string "[identifier]" */ + identifier = (unsigned char*)buf_getstring(buf, &identifier_len); + + if (strcmp (key_ident, "sk-ecdsa-sha2-nistp256@openssh.com") == 0) { + if (strcmp (identifier, "nistp256") != 0) { + TRACE(("mismatching identifiers")) + goto out; + } + } else { + if (key_ident_len != identifier_len + strlen ("ecdsa-sha2-")) { + TRACE(("Bad identifier lengths")) + goto out; + } + if (memcmp(&key_ident[strlen ("ecdsa-sha2-")], identifier, identifier_len) != 0) { + TRACE(("mismatching identifiers")) + goto out; + } + } + + for (curve = dropbear_ecc_curves; *curve; curve++) { + if (memcmp(identifier, (char*)(*curve)->name, strlen((char*)(*curve)->name)) == 0) { + break; + } + } + if (!*curve) { + TRACE(("couldn't match ecc curve")) + goto out; + } + + /* string Q */ + q_buf = buf_getstringbuf(buf); + new_key = buf_get_ecc_raw_pubkey(q_buf, *curve); + +out: + m_free(key_ident); + m_free(identifier); + if (q_buf) { + buf_free(q_buf); + q_buf = NULL; + } + TRACE(("leave buf_get_ecdsa_pub_key")) + return new_key; +} + +ecc_key *buf_get_ecdsa_priv_key(buffer *buf) { + ecc_key *new_key = NULL; + TRACE(("enter buf_get_ecdsa_priv_key")) + new_key = buf_get_ecdsa_pub_key(buf); + if (!new_key) { + return NULL; + } + + if (buf_getmpint(buf, new_key->k) != DROPBEAR_SUCCESS) { + ecc_free(new_key); + m_free(new_key); + return NULL; + } + + return new_key; +} + +void buf_put_ecdsa_pub_key(buffer *buf, ecc_key *key) { + struct dropbear_ecc_curve *curve = NULL; + char key_ident[30]; + + curve = curve_for_dp(key->dp); + snprintf(key_ident, sizeof(key_ident), "ecdsa-sha2-%s", curve->name); + buf_putstring(buf, key_ident, strlen(key_ident)); + buf_putstring(buf, curve->name, strlen(curve->name)); + buf_put_ecc_raw_pubkey_string(buf, key); +} + +void buf_put_ecdsa_priv_key(buffer *buf, ecc_key *key) { + buf_put_ecdsa_pub_key(buf, key); + buf_putmpint(buf, key->k); +} + +void buf_put_ecdsa_sign(buffer *buf, const ecc_key *key, const buffer *data_buf) { + /* Based on libtomcrypt's ecc_sign_hash but without the asn1 */ + int err = DROPBEAR_FAILURE; + struct dropbear_ecc_curve *curve = NULL; + hash_state hs; + unsigned char hash[64]; + void *e = NULL, *p = NULL, *s = NULL, *r; + char key_ident[30]; + buffer *sigbuf = NULL; + + TRACE(("buf_put_ecdsa_sign")) + curve = curve_for_dp(key->dp); + + if (ltc_init_multi(&r, &s, &p, &e, NULL) != CRYPT_OK) { + goto out; + } + + curve->hash_desc->init(&hs); + curve->hash_desc->process(&hs, data_buf->data, data_buf->len); + curve->hash_desc->done(&hs, hash); + + if (ltc_mp.unsigned_read(e, hash, curve->hash_desc->hashsize) != CRYPT_OK) { + goto out; + } + + if (ltc_mp.read_radix(p, (char *)key->dp->order, 16) != CRYPT_OK) { + goto out; + } + + for (;;) { + ecc_key R_key; /* ephemeral key */ + if (ecc_make_key_ex(NULL, dropbear_ltc_prng, &R_key, key->dp) != CRYPT_OK) { + goto out; + } + if (ltc_mp.mpdiv(R_key.pubkey.x, p, NULL, r) != CRYPT_OK) { + goto out; + } + if (ltc_mp.compare_d(r, 0) == LTC_MP_EQ) { + /* try again */ + ecc_free(&R_key); + continue; + } + /* k = 1/k */ + if (ltc_mp.invmod(R_key.k, p, R_key.k) != CRYPT_OK) { + goto out; + } + /* s = xr */ + if (ltc_mp.mulmod(key->k, r, p, s) != CRYPT_OK) { + goto out; + } + /* s = e + xr */ + if (ltc_mp.add(e, s, s) != CRYPT_OK) { + goto out; + } + if (ltc_mp.mpdiv(s, p, NULL, s) != CRYPT_OK) { + goto out; + } + /* s = (e + xr)/k */ + if (ltc_mp.mulmod(s, R_key.k, p, s) != CRYPT_OK) { + goto out; + } + ecc_free(&R_key); + + if (ltc_mp.compare_d(s, 0) != LTC_MP_EQ) { + break; + } + } + + snprintf(key_ident, sizeof(key_ident), "ecdsa-sha2-%s", curve->name); + buf_putstring(buf, key_ident, strlen(key_ident)); + /* enough for nistp521 */ + sigbuf = buf_new(200); + buf_putmpint(sigbuf, (mp_int*)r); + buf_putmpint(sigbuf, (mp_int*)s); + buf_putbufstring(buf, sigbuf); + + err = DROPBEAR_SUCCESS; + +out: + if (r && s && p && e) { + ltc_deinit_multi(r, s, p, e, NULL); + } + + if (sigbuf) { + buf_free(sigbuf); + } + + if (err == DROPBEAR_FAILURE) { + dropbear_exit("ECC error"); + } +} + +/* returns values in s and r + returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int buf_get_ecdsa_verify_params(buffer *buf, + void *r, void* s) { + int ret = DROPBEAR_FAILURE; + unsigned int sig_len; + unsigned int sig_pos; + + sig_len = buf_getint(buf); + sig_pos = buf->pos; + if (buf_getmpint(buf, r) != DROPBEAR_SUCCESS) { + goto out; + } + if (buf_getmpint(buf, s) != DROPBEAR_SUCCESS) { + goto out; + } + if (buf->pos - sig_pos != sig_len) { + goto out; + } + ret = DROPBEAR_SUCCESS; + +out: + return ret; +} + + +int buf_ecdsa_verify(buffer *buf, const ecc_key *key, const buffer *data_buf) { + /* Based on libtomcrypt's ecc_verify_hash but without the asn1 */ + int ret = DROPBEAR_FAILURE; + hash_state hs; + struct dropbear_ecc_curve *curve = NULL; + unsigned char hash[64]; + ecc_point *mG = NULL, *mQ = NULL; + void *r = NULL, *s = NULL, *v = NULL, *w = NULL, *u1 = NULL, *u2 = NULL, + *e = NULL, *p = NULL, *m = NULL; + void *mp = NULL; + + /* verify + * + * w = s^-1 mod n + * u1 = xw + * u2 = rw + * X = u1*G + u2*Q + * v = X_x1 mod n + * accept if v == r + */ + + TRACE(("buf_ecdsa_verify")) + curve = curve_for_dp(key->dp); + + mG = ltc_ecc_new_point(); + mQ = ltc_ecc_new_point(); + if (ltc_init_multi(&r, &s, &v, &w, &u1, &u2, &p, &e, &m, NULL) != CRYPT_OK + || !mG + || !mQ) { + dropbear_exit("ECC error"); + } + + if (buf_get_ecdsa_verify_params(buf, r, s) != DROPBEAR_SUCCESS) { + goto out; + } + + curve->hash_desc->init(&hs); + curve->hash_desc->process(&hs, data_buf->data, data_buf->len); + curve->hash_desc->done(&hs, hash); + + if (ltc_mp.unsigned_read(e, hash, curve->hash_desc->hashsize) != CRYPT_OK) { + goto out; + } + + /* get the order */ + if (ltc_mp.read_radix(p, (char *)key->dp->order, 16) != CRYPT_OK) { + goto out; + } + + /* get the modulus */ + if (ltc_mp.read_radix(m, (char *)key->dp->prime, 16) != CRYPT_OK) { + goto out; + } + + /* check for zero */ + if (ltc_mp.compare_d(r, 0) == LTC_MP_EQ + || ltc_mp.compare_d(s, 0) == LTC_MP_EQ + || ltc_mp.compare(r, p) != LTC_MP_LT + || ltc_mp.compare(s, p) != LTC_MP_LT) { + goto out; + } + + /* w = s^-1 mod n */ + if (ltc_mp.invmod(s, p, w) != CRYPT_OK) { + goto out; + } + + /* u1 = ew */ + if (ltc_mp.mulmod(e, w, p, u1) != CRYPT_OK) { + goto out; + } + + /* u2 = rw */ + if (ltc_mp.mulmod(r, w, p, u2) != CRYPT_OK) { + goto out; + } + + /* find mG and mQ */ + if (ltc_mp.read_radix(mG->x, (char *)key->dp->Gx, 16) != CRYPT_OK) { + goto out; + } + if (ltc_mp.read_radix(mG->y, (char *)key->dp->Gy, 16) != CRYPT_OK) { + goto out; + } + if (ltc_mp.set_int(mG->z, 1) != CRYPT_OK) { + goto out; + } + + if (ltc_mp.copy(key->pubkey.x, mQ->x) != CRYPT_OK + || ltc_mp.copy(key->pubkey.y, mQ->y) != CRYPT_OK + || ltc_mp.copy(key->pubkey.z, mQ->z) != CRYPT_OK) { + goto out; + } + + /* compute u1*mG + u2*mQ = mG */ + if (ltc_mp.ecc_mul2add == NULL) { + if (ltc_mp.ecc_ptmul(u1, mG, mG, m, 0) != CRYPT_OK) { + goto out; + } + if (ltc_mp.ecc_ptmul(u2, mQ, mQ, m, 0) != CRYPT_OK) { + goto out; + } + + /* find the montgomery mp */ + if (ltc_mp.montgomery_setup(m, &mp) != CRYPT_OK) { + goto out; + } + + /* add them */ + if (ltc_mp.ecc_ptadd(mQ, mG, mG, m, mp) != CRYPT_OK) { + goto out; + } + + /* reduce */ + if (ltc_mp.ecc_map(mG, m, mp) != CRYPT_OK) { + goto out; + } + } else { + /* use Shamir's trick to compute u1*mG + u2*mQ using half of the doubles */ + if (ltc_mp.ecc_mul2add(mG, u1, mQ, u2, mG, m) != CRYPT_OK) { + goto out; + } + } + + /* v = X_x1 mod n */ + if (ltc_mp.mpdiv(mG->x, p, NULL, v) != CRYPT_OK) { + goto out; + } + + /* does v == r */ + if (ltc_mp.compare(v, r) == LTC_MP_EQ) { + ret = DROPBEAR_SUCCESS; + } + +out: + ltc_ecc_del_point(mG); + ltc_ecc_del_point(mQ); + ltc_deinit_multi(r, s, v, w, u1, u2, p, e, m, NULL); + if (mp != NULL) { + ltc_mp.montgomery_deinit(mp); + } + return ret; +} + + + +#endif /* DROPBEAR_ECDSA */ diff --git a/src/ecdsa.h b/src/ecdsa.h new file mode 100644 index 0000000..01cb134 --- /dev/null +++ b/src/ecdsa.h @@ -0,0 +1,36 @@ +#ifndef DROPBEAR_ECDSA_H_ +#define DROPBEAR_ECDSA_H_ + +#include "includes.h" +#include "buffer.h" +#include "signkey.h" + +#if DROPBEAR_ECDSA + +/* prefer 256 or 384 since those are SHOULD for + draft-ietf-curdle-ssh-kex-sha2.txt */ +#if DROPBEAR_ECC_256 +#define ECDSA_DEFAULT_SIZE 256 +#elif DROPBEAR_ECC_384 +#define ECDSA_DEFAULT_SIZE 384 +#elif DROPBEAR_ECC_521 +#define ECDSA_DEFAULT_SIZE 521 +#else +#error ECDSA cannot be enabled without enabling at least one size (256, 384, 521) +#endif + +ecc_key *gen_ecdsa_priv_key(unsigned int bit_size); +ecc_key *buf_get_ecdsa_pub_key(buffer* buf); +ecc_key *buf_get_ecdsa_priv_key(buffer *buf); +void buf_put_ecdsa_pub_key(buffer *buf, ecc_key *key); +void buf_put_ecdsa_priv_key(buffer *buf, ecc_key *key); +enum signkey_type ecdsa_signkey_type(const ecc_key * key); + +void buf_put_ecdsa_sign(buffer *buf, const ecc_key *key, const buffer *data_buf); +int buf_ecdsa_verify(buffer *buf, const ecc_key *key, const buffer *data_buf); +/* Returns 1 on success */ +int signkey_is_ecdsa(enum signkey_type type); + +#endif + +#endif /* DROPBEAR_ECDSA_H_ */ diff --git a/src/ed25519.c b/src/ed25519.c new file mode 100644 index 0000000..f200e13 --- /dev/null +++ b/src/ed25519.c @@ -0,0 +1,193 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* Perform Ed25519 operations on data, including reading keys, signing and + * verification. */ + +#include "includes.h" +#include "dbutil.h" +#include "buffer.h" +#include "ssh.h" +#include "curve25519.h" +#include "ed25519.h" + +#if DROPBEAR_ED25519 + +/* Load a public ed25519 key from a buffer, initialising the values. + * The key will have the same format as buf_put_ed25519_key. + * These should be freed with ed25519_key_free. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int buf_get_ed25519_pub_key(buffer *buf, dropbear_ed25519_key *key, + enum signkey_type expect_keytype) { + + + unsigned int len, typelen; + char *keytype = NULL; + enum signkey_type buf_keytype; + + TRACE(("enter buf_get_ed25519_pub_key")) + dropbear_assert(key != NULL); + + /* consume and check the key string */ + keytype = buf_getstring(buf, &typelen); + buf_keytype = signkey_type_from_name(keytype, typelen); + m_free(keytype); + if (buf_keytype != expect_keytype) { + TRACE(("leave buf_get_ed25519_pub_key: mismatch key type")) + return DROPBEAR_FAILURE; + } + + len = buf_getint(buf); + if (len != CURVE25519_LEN || buf->len - buf->pos < len) { + TRACE(("leave buf_get_ed25519_pub_key: failure")) + return DROPBEAR_FAILURE; + } + + m_burn(key->priv, CURVE25519_LEN); + memcpy(key->pub, buf_getptr(buf, CURVE25519_LEN), CURVE25519_LEN); + buf_incrpos(buf, CURVE25519_LEN); + + TRACE(("leave buf_get_ed25519_pub_key: success")) + return DROPBEAR_SUCCESS; +} + +/* Same as buf_get_ed25519_pub_key, but reads private key at the end. + * Loads a public and private ed25519 key from a buffer + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int buf_get_ed25519_priv_key(buffer *buf, dropbear_ed25519_key *key) { + + unsigned int len; + + TRACE(("enter buf_get_ed25519_priv_key")) + dropbear_assert(key != NULL); + + buf_incrpos(buf, 4+SSH_SIGNKEY_ED25519_LEN); /* int + "ssh-ed25519" */ + + len = buf_getint(buf); + if (len != CURVE25519_LEN*2 || buf->len - buf->pos < len) { + TRACE(("leave buf_get_ed25519_priv_key: failure")) + return DROPBEAR_FAILURE; + } + + memcpy(key->priv, buf_getptr(buf, CURVE25519_LEN), CURVE25519_LEN); + buf_incrpos(buf, CURVE25519_LEN); + memcpy(key->pub, buf_getptr(buf, CURVE25519_LEN), CURVE25519_LEN); + buf_incrpos(buf, CURVE25519_LEN); + + TRACE(("leave buf_get_ed25519_priv_key: success")) + return DROPBEAR_SUCCESS; +} + +/* Clear and free the memory used by a public or private key */ +void ed25519_key_free(dropbear_ed25519_key *key) { + + TRACE2(("enter ed25519_key_free")) + + if (key == NULL) { + TRACE2(("leave ed25519_key_free: key == NULL")) + return; + } + m_burn(key->priv, CURVE25519_LEN); + m_free(key); + + TRACE2(("leave ed25519_key_free")) +} + +/* Put the public ed25519 key into the buffer in the required format */ +void buf_put_ed25519_pub_key(buffer *buf, const dropbear_ed25519_key *key) { + + TRACE(("enter buf_put_ed25519_pub_key")) + dropbear_assert(key != NULL); + + buf_putstring(buf, SSH_SIGNKEY_ED25519, SSH_SIGNKEY_ED25519_LEN); + buf_putstring(buf, key->pub, CURVE25519_LEN); + + TRACE(("leave buf_put_ed25519_pub_key")) +} + +/* Put the public and private ed25519 key into the buffer in the required format */ +void buf_put_ed25519_priv_key(buffer *buf, const dropbear_ed25519_key *key) { + + TRACE(("enter buf_put_ed25519_priv_key")) + dropbear_assert(key != NULL); + + buf_putstring(buf, SSH_SIGNKEY_ED25519, SSH_SIGNKEY_ED25519_LEN); + buf_putint(buf, CURVE25519_LEN*2); + buf_putbytes(buf, key->priv, CURVE25519_LEN); + buf_putbytes(buf, key->pub, CURVE25519_LEN); + + TRACE(("leave buf_put_ed25519_priv_key")) +} + +/* Sign the data presented with key, writing the signature contents + * to the buffer */ +void buf_put_ed25519_sign(buffer* buf, const dropbear_ed25519_key *key, const buffer *data_buf) { + + unsigned char s[64]; + unsigned long slen = sizeof(s); + + TRACE(("enter buf_put_ed25519_sign")) + dropbear_assert(key != NULL); + + dropbear_ed25519_sign(data_buf->data, data_buf->len, s, &slen, key->priv, key->pub); + buf_putstring(buf, SSH_SIGNKEY_ED25519, SSH_SIGNKEY_ED25519_LEN); + buf_putstring(buf, s, slen); + + TRACE(("leave buf_put_ed25519_sign")) +} + +#if DROPBEAR_SIGNKEY_VERIFY +/* Verify a signature in buf, made on data by the key given. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int buf_ed25519_verify(buffer *buf, const dropbear_ed25519_key *key, const buffer *data_buf) { + + int ret = DROPBEAR_FAILURE; + unsigned char *s; + unsigned long slen; + + TRACE(("enter buf_ed25519_verify")) + dropbear_assert(key != NULL); + + slen = buf_getint(buf); + if (slen != 64 || buf->len - buf->pos < slen) { + TRACE(("leave buf_ed25519_verify: bad size")) + goto out; + } + s = buf_getptr(buf, slen); + + if (dropbear_ed25519_verify(data_buf->data, data_buf->len, + s, slen, key->pub) == 0) { + /* signature is valid */ + TRACE(("leave buf_ed25519_verify: success!")) + ret = DROPBEAR_SUCCESS; + } + +out: + TRACE(("leave buf_ed25519_verify: ret %d", ret)) + return ret; +} + +#endif /* DROPBEAR_SIGNKEY_VERIFY */ + +#endif /* DROPBEAR_ED25519 */ diff --git a/src/ed25519.h b/src/ed25519.h new file mode 100644 index 0000000..1da9fbd --- /dev/null +++ b/src/ed25519.h @@ -0,0 +1,56 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_ED25519_H_ +#define DROPBEAR_ED25519_H_ + +#include "includes.h" +#include "buffer.h" +#include "signkey.h" + +#if DROPBEAR_ED25519 + +#define CURVE25519_LEN 32 + +typedef struct dropbear_ED25519_Key { + + unsigned char priv[CURVE25519_LEN]; + unsigned char pub[CURVE25519_LEN]; + +} dropbear_ed25519_key; + +void buf_put_ed25519_sign(buffer* buf, const dropbear_ed25519_key *key, const buffer *data_buf); +#if DROPBEAR_SIGNKEY_VERIFY +int buf_ed25519_verify(buffer * buf, const dropbear_ed25519_key *key, const buffer *data_buf); +#endif +int buf_get_ed25519_pub_key(buffer *buf, dropbear_ed25519_key *key, + enum signkey_type expect_keytype); +int buf_get_ed25519_priv_key(buffer* buf, dropbear_ed25519_key *key); +void buf_put_ed25519_pub_key(buffer* buf, const dropbear_ed25519_key *key); +void buf_put_ed25519_priv_key(buffer* buf, const dropbear_ed25519_key *key); +void ed25519_key_free(dropbear_ed25519_key *key); + +#endif /* DROPBEAR_ED25519 */ + +#endif /* DROPBEAR_ED25519_H_ */ diff --git a/src/fake-rfc2553.c b/src/fake-rfc2553.c new file mode 100644 index 0000000..395cfcc --- /dev/null +++ b/src/fake-rfc2553.c @@ -0,0 +1,237 @@ +/* Taken for Dropbear from OpenSSH 5.5p1 */ + +/* + * Copyright (C) 2000-2003 Damien Miller. All rights reserved. + * Copyright (C) 1999 WIDE Project. 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Pseudo-implementation of RFC2553 name / address resolution functions + * + * But these functions are not implemented correctly. The minimum subset + * is implemented for ssh use only. For example, this routine assumes + * that ai_family is AF_INET. Don't use it for another purpose. + */ + +#include "includes.h" + +#include <stdlib.h> +#include <string.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#ifndef HAVE_GETNAMEINFO +int getnameinfo(const struct sockaddr *sa, size_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + struct hostent *hp; + char tmpserv[16]; + + if (sa->sa_family != AF_UNSPEC && sa->sa_family != AF_INET) + return (EAI_FAMILY); + if (serv != NULL) { + snprintf(tmpserv, sizeof(tmpserv), "%d", ntohs(sin->sin_port)); + if (strlcpy(serv, tmpserv, servlen) >= servlen) + return (EAI_MEMORY); + } + + if (host != NULL) { + if (flags & NI_NUMERICHOST) { + if (strlcpy(host, inet_ntoa(sin->sin_addr), + hostlen) >= hostlen) + return (EAI_MEMORY); + else + return (0); + } else { + hp = gethostbyaddr((char *)&sin->sin_addr, + sizeof(struct in_addr), AF_INET); + if (hp == NULL) + return (EAI_NODATA); + + if (strlcpy(host, hp->h_name, hostlen) >= hostlen) + return (EAI_MEMORY); + else + return (0); + } + } + return (0); +} +#endif /* !HAVE_GETNAMEINFO */ + +#ifndef HAVE_GAI_STRERROR +#ifdef HAVE_CONST_GAI_STRERROR_PROTO +const char * +#else +char * +#endif +gai_strerror(int err) +{ + switch (err) { + case EAI_NODATA: + return ("no address associated with name"); + case EAI_MEMORY: + return ("memory allocation failure."); + case EAI_NONAME: + return ("nodename nor servname provided, or not known"); + case EAI_FAMILY: + return ("ai_family not supported"); + default: + return ("unknown/invalid error."); + } +} +#endif /* !HAVE_GAI_STRERROR */ + +#ifndef HAVE_FREEADDRINFO +void +freeaddrinfo(struct addrinfo *ai) +{ + struct addrinfo *next; + + for(; ai != NULL;) { + next = ai->ai_next; + free(ai); + ai = next; + } +} +#endif /* !HAVE_FREEADDRINFO */ + +#ifndef HAVE_GETADDRINFO +static struct +addrinfo *malloc_ai(int port, u_long addr, const struct addrinfo *hints) +{ + struct addrinfo *ai; + + ai = malloc(sizeof(*ai) + sizeof(struct sockaddr_in)); + if (ai == NULL) + return (NULL); + + memset(ai, '\0', sizeof(*ai) + sizeof(struct sockaddr_in)); + + ai->ai_addr = (struct sockaddr *)(ai + 1); + /* XXX -- ssh doesn't use sa_len */ + ai->ai_addrlen = sizeof(struct sockaddr_in); + ai->ai_addr->sa_family = ai->ai_family = AF_INET; + + ((struct sockaddr_in *)(ai)->ai_addr)->sin_port = port; + ((struct sockaddr_in *)(ai)->ai_addr)->sin_addr.s_addr = addr; + + /* XXX: the following is not generally correct, but does what we want */ + if (hints->ai_socktype) + ai->ai_socktype = hints->ai_socktype; + else + ai->ai_socktype = SOCK_STREAM; + + if (hints->ai_protocol) + ai->ai_protocol = hints->ai_protocol; + + return (ai); +} + +int +getaddrinfo(const char *hostname, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) +{ + struct hostent *hp; + struct servent *sp; + struct in_addr in; + int i; + long int port; + u_long addr; + + port = 0; + if (hints && hints->ai_family != AF_UNSPEC && + hints->ai_family != AF_INET) + return (EAI_FAMILY); + if (servname != NULL) { + char *cp; + + port = strtol(servname, &cp, 10); + if (port > 0 && port <= 65535 && *cp == '\0') + port = htons(port); + else if ((sp = getservbyname(servname, NULL)) != NULL) + port = sp->s_port; + else + port = 0; + } + + if (hints && hints->ai_flags & AI_PASSIVE) { + addr = htonl(0x00000000); + if (hostname && inet_aton(hostname, &in) != 0) + addr = in.s_addr; + *res = malloc_ai(port, addr, hints); + if (*res == NULL) + return (EAI_MEMORY); + return (0); + } + + if (!hostname) { + *res = malloc_ai(port, htonl(0x7f000001), hints); + if (*res == NULL) + return (EAI_MEMORY); + return (0); + } + + if (inet_aton(hostname, &in)) { + *res = malloc_ai(port, in.s_addr, hints); + if (*res == NULL) + return (EAI_MEMORY); + return (0); + } + + /* Don't try DNS if AI_NUMERICHOST is set */ + if (hints && hints->ai_flags & AI_NUMERICHOST) + return (EAI_NONAME); + + hp = gethostbyname(hostname); + if (hp && hp->h_name && hp->h_name[0] && hp->h_addr_list[0]) { + struct addrinfo *cur, *prev; + + cur = prev = *res = NULL; + for (i = 0; hp->h_addr_list[i]; i++) { + struct in_addr *in = (struct in_addr *)hp->h_addr_list[i]; + + cur = malloc_ai(port, in->s_addr, hints); + if (cur == NULL) { + if (*res != NULL) + freeaddrinfo(*res); + return (EAI_MEMORY); + } + if (prev) + prev->ai_next = cur; + else + *res = cur; + + prev = cur; + } + return (0); + } + + return (EAI_NODATA); +} +#endif /* !HAVE_GETADDRINFO */ diff --git a/src/fake-rfc2553.h b/src/fake-rfc2553.h new file mode 100644 index 0000000..c64136c --- /dev/null +++ b/src/fake-rfc2553.h @@ -0,0 +1,177 @@ +/* Taken for Dropbear from OpenSSH 5.5p1 */ + +/* $Id: fake-rfc2553.h,v 1.16 2008/07/14 11:37:37 djm Exp $ */ + +/* + * Copyright (C) 2000-2003 Damien Miller. All rights reserved. + * Copyright (C) 1999 WIDE Project. 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Pseudo-implementation of RFC2553 name / address resolution functions + * + * But these functions are not implemented correctly. The minimum subset + * is implemented for ssh use only. For example, this routine assumes + * that ai_family is AF_INET. Don't use it for another purpose. + */ + +#ifndef DROPBEAR_FAKE_RFC2553_H +#define DROPBEAR_FAKE_RFC2553_H + +#include "includes.h" +#include <sys/types.h> +#if defined(HAVE_NETDB_H) +# include <netdb.h> +#endif + +/* + * First, socket and INET6 related definitions + */ +#ifndef HAVE_STRUCT_SOCKADDR_STORAGE +# define _SS_MAXSIZE 128 /* Implementation specific max size */ +# define _SS_PADSIZE (_SS_MAXSIZE - sizeof (struct sockaddr)) +struct sockaddr_storage { + struct sockaddr ss_sa; + char __ss_pad2[_SS_PADSIZE]; +}; +# define ss_family ss_sa.sa_family +#endif /* !HAVE_STRUCT_SOCKADDR_STORAGE */ + +#ifndef IN6_IS_ADDR_LOOPBACK +# define IN6_IS_ADDR_LOOPBACK(a) \ + (((u_int32_t *)(a))[0] == 0 && ((u_int32_t *)(a))[1] == 0 && \ + ((u_int32_t *)(a))[2] == 0 && ((u_int32_t *)(a))[3] == htonl(1)) +#endif /* !IN6_IS_ADDR_LOOPBACK */ + +#ifndef HAVE_STRUCT_IN6_ADDR +struct in6_addr { + u_int8_t s6_addr[16]; +}; +#endif /* !HAVE_STRUCT_IN6_ADDR */ + +#ifndef HAVE_STRUCT_SOCKADDR_IN6 +struct sockaddr_in6 { + unsigned short sin6_family; + u_int16_t sin6_port; + u_int32_t sin6_flowinfo; + struct in6_addr sin6_addr; + u_int32_t sin6_scope_id; +}; +#endif /* !HAVE_STRUCT_SOCKADDR_IN6 */ + +#ifndef AF_INET6 +/* Define it to something that should never appear */ +#define AF_INET6 AF_MAX +#endif + +/* + * Next, RFC2553 name / address resolution API + */ + +#ifndef NI_NUMERICHOST +# define NI_NUMERICHOST (1) +#endif +#ifndef NI_NAMEREQD +# define NI_NAMEREQD (1<<1) +#endif +#ifndef NI_NUMERICSERV +# define NI_NUMERICSERV (1<<2) +#endif + +#ifndef AI_PASSIVE +# define AI_PASSIVE (1) +#endif +#ifndef AI_CANONNAME +# define AI_CANONNAME (1<<1) +#endif +#ifndef AI_NUMERICHOST +# define AI_NUMERICHOST (1<<2) +#endif + +#ifndef NI_MAXSERV +# define NI_MAXSERV 32 +#endif /* !NI_MAXSERV */ +#ifndef NI_MAXHOST +# define NI_MAXHOST 1025 +#endif /* !NI_MAXHOST */ + +#ifndef EAI_NODATA +# define EAI_NODATA (INT_MAX - 1) +#endif +#ifndef EAI_MEMORY +# define EAI_MEMORY (INT_MAX - 2) +#endif +#ifndef EAI_NONAME +# define EAI_NONAME (INT_MAX - 3) +#endif +#ifndef EAI_SYSTEM +# define EAI_SYSTEM (INT_MAX - 4) +#endif +#ifndef EAI_FAMILY +# define EAI_FAMILY (INT_MAX - 5) +#endif + +#ifndef HAVE_STRUCT_ADDRINFO +struct addrinfo { + int ai_flags; /* AI_PASSIVE, AI_CANONNAME */ + int ai_family; /* PF_xxx */ + int ai_socktype; /* SOCK_xxx */ + int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */ + size_t ai_addrlen; /* length of ai_addr */ + char *ai_canonname; /* canonical name for hostname */ + struct sockaddr *ai_addr; /* binary address */ + struct addrinfo *ai_next; /* next structure in linked list */ +}; +#endif /* !HAVE_STRUCT_ADDRINFO */ + +#ifndef HAVE_GETADDRINFO +#ifdef getaddrinfo +# undef getaddrinfo +#endif +#define getaddrinfo(a,b,c,d) (ssh_getaddrinfo(a,b,c,d)) +int getaddrinfo(const char *, const char *, + const struct addrinfo *, struct addrinfo **); +#endif /* !HAVE_GETADDRINFO */ + +#if !defined(HAVE_GAI_STRERROR) && !defined(HAVE_CONST_GAI_STRERROR_PROTO) +#define gai_strerror(a) (_ssh_compat_gai_strerror(a)) +char *gai_strerror(int); +#endif /* !HAVE_GAI_STRERROR */ + +#ifndef HAVE_FREEADDRINFO +#define freeaddrinfo(a) (ssh_freeaddrinfo(a)) +void freeaddrinfo(struct addrinfo *); +#endif /* !HAVE_FREEADDRINFO */ + +#ifndef HAVE_GETNAMEINFO +#define getnameinfo(a,b,c,d,e,f,g) (ssh_getnameinfo(a,b,c,d,e,f,g)) +int getnameinfo(const struct sockaddr *, size_t, char *, size_t, + char *, size_t, int); +#endif /* !HAVE_GETNAMEINFO */ + +#endif /* !_FAKE_RFC2553_H */ + diff --git a/src/filelist.txt b/src/filelist.txt new file mode 100644 index 0000000..3b9bb67 --- /dev/null +++ b/src/filelist.txt @@ -0,0 +1,121 @@ +This file is out of date - it remains here in case it is still of use. +The basic naming convention is svr- and cli- for seperate parts, +then common- for common parts. Some files have no prefix. + +A brief rundown on which files do what, and their corresponding sections +in the IETF drafts. The .c files usually have corresponding .h files. + +Transport layer draft-ietf-secsh-transport-16.txt +=============== + +session.c Contains the main select() loop, and handles setting + up/closing down ssh connections + +algo.c Framework for handling various ciphers/hashes/algos, + and choosing between the lists of client/server + preferred ones + +kex.c Key exchange routines, used at startup to negotiate + which algorithms to use, and also to obtain session + keys. This also runs when rekeying during the + connection. + +packet.c Handles the basic packet encryption/decryption, + and switching to the appropriate packet handlers. + Called from session.c's main select loop. + +service.c Handles service requests (userauth or connection) + + +Authentication draft-ietf-secsh-userauth-17.txt +============== + +auth.c General auth handling, including user checking etc, + passes different auth types to auth{passwd,pubkey} + +authpasswd.c Handles /etc/passwd or /etc/shadow auth + +authpubkey.c Handles ~/.ssh/authorized_keys auth + + +Connection draft-ietf-secsh-connect-17.txt +========== + +channel.c Channel handling routines - each shell/tcp conn/agent + etc is a channel. + +chansession.c Handles shell/exec requests + +sshpty.c From OpenSSH, allocates PTYs etc + +termcodes.c Mapping of POSIX terminal codes to SSH terminal codes + +loginrec.c From OpenSSH, handles utmp/wtmp logging + +x11fwd.c Handles X11 forwarding + +agentfwd.c Handles auth-agent forwarding requests + +localtcpfwd.c Handles -L style tcp forwarding requests, setting + up the listening port and also handling connections + to that port (and subsequent channels) + + +Program-related +=============== + +dbmulti.c Combination binary chooser main() function + +dbutil.c Various utility functions, incl logging, memory etc + +dropbearconvert.c Conversion from dropbear<->openssh keys, uses + keyimport.c to do most of the work + +dropbearkey.c Generates keys, calling gen{dss,rsa} + +keyimport.c Modified from PuTTY, converts between key types + +main.c dropbear's main(), handles listening, forking for + new connections, child-process limits + +runopts.c Parses commandline options + +options.h Compile-time feature selection + +config.h Features selected from configure + +debug.h Compile-time selection of debug features + +includes.h Included system headers etc + + +Generic Routines +================ + +signkey.c A generic handler for pubkeys, switches to dss or rsa + depending on the key type + +rsa.c RSA asymmetric crypto routines + +dss.c DSS asymmetric crypto routines + +ed25519.c Ed25519 asymmetric crypto routines + +gened25519.c Ed25519 key generation + +gendss.c DSS key generation + +genrsa.c RSA key generation + +bignum.c Some bignum helper functions + +queue.c A queue, used to enqueue encrypted packets to send + +random.c PRNG, based on /dev/urandom or prngd + +atomicio.c From OpenSSH, does `blocking' IO on non-blocking fds + +buffer.c Buffer-usage routines, with size checking etc + + +vim:set ts=8: diff --git a/src/fuzz-wrapfd.h b/src/fuzz-wrapfd.h new file mode 100644 index 0000000..d0dea88 --- /dev/null +++ b/src/fuzz-wrapfd.h @@ -0,0 +1,27 @@ +#ifndef FUZZ_WRAPFD_H +#define FUZZ_WRAPFD_H + +#include "includes.h" +#include "buffer.h" + +enum wrapfd_mode { + UNUSED = 0, + COMMONBUF, // using the common buffer + DUMMY, // reads return fixed output, of random length +}; + +// buf is a common buffer read by all wrapped FDs. doesn't take ownership of buf +void wrapfd_setup(buffer *buf); +void wrapfd_setseed(uint32_t seed); +int wrapfd_new_fuzzinput(void); +int wrapfd_new_dummy(void); + +// called via #defines for read/write/select +int wrapfd_read(int fd, void *out, size_t count); +int wrapfd_write(int fd, const void* in, size_t count); +int wrapfd_select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout); +int wrapfd_close(int fd); +int fuzz_kill(pid_t pid, int sig); + +#endif // FUZZ_WRAPFD_H diff --git a/src/fuzz.h b/src/fuzz.h new file mode 100644 index 0000000..95cb4d8 --- /dev/null +++ b/src/fuzz.h @@ -0,0 +1,114 @@ +#ifndef DROPBEAR_FUZZ_H +#define DROPBEAR_FUZZ_H + +#include "config.h" + +#if DROPBEAR_FUZZ + +#include "includes.h" +#include "buffer.h" +#include "algo.h" +#include "netio.h" +#include "fuzz-wrapfd.h" + +// once per process +void fuzz_common_setup(void); +void fuzz_svr_setup(void); +void fuzz_cli_setup(void); + +// constructor attribute so it runs before main(), including +// in non-fuzzing mode. +void fuzz_early_setup(void) __attribute__((constructor)); + +// must be called once per fuzz iteration. +// returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE +int fuzz_set_input(const uint8_t *Data, size_t Size); + +int fuzz_run_server(const uint8_t *Data, size_t Size, int skip_kexmaths, int postauth); +int fuzz_run_client(const uint8_t *Data, size_t Size, int skip_kexmaths); +const void* fuzz_get_algo(const algo_type *algos, const char* name); + +// fuzzer functions that intrude into general code +void fuzz_kex_fakealgos(void); +int fuzz_checkpubkey_line(buffer* line, int line_num, char* filename, + const char* algo, unsigned int algolen, + const unsigned char* keyblob, unsigned int keybloblen); +extern const char * const * fuzz_signkey_names; +void fuzz_seed(const unsigned char* dat, unsigned int len); +void fuzz_svr_hook_preloop(void); + +int fuzz_dropbear_listen(const char* address, const char* port, + int *socks, unsigned int sockcount, char **errstring, int *maxfd); + +// helpers +void fuzz_get_socket_address(int fd, char **local_host, char **local_port, + char **remote_host, char **remote_port, int host_lookup); +void fuzz_fake_send_kexdh_reply(void); +int fuzz_spawn_command(int *ret_writefd, int *ret_readfd, int *ret_errfd, pid_t *ret_pid); +void fuzz_dump(const unsigned char* data, size_t len); + +// fake IO wrappers +#ifndef FUZZ_SKIP_WRAP +#define select(nfds, readfds, writefds, exceptfds, timeout) \ + wrapfd_select(nfds, readfds, writefds, exceptfds, timeout) +#define write(fd, buf, count) wrapfd_write(fd, buf, count) +#define read(fd, buf, count) wrapfd_read(fd, buf, count) +#define close(fd) wrapfd_close(fd) +#define kill(pid, sig) fuzz_kill(pid, sig) +#endif // FUZZ_SKIP_WRAP + +struct dropbear_fuzz_options { + int fuzzing; + + // fuzzing input + buffer *input; + struct dropbear_cipher recv_cipher; + struct dropbear_hash recv_mac; + int wrapfds; + + // whether to skip slow bignum maths + int skip_kexmaths; + // whether is svr_postauth mode + int svr_postauth; + + // dropbear_exit() jumps back + int do_jmp; + sigjmp_buf jmp; + + // write out decrypted session data to this FD if it is set + // flag - this needs to be set manually in cli-main.c etc + int dumping; + // the file descriptor + int recv_dumpfd; + + // avoid filling fuzzing logs, this points to /dev/null + FILE *fake_stderr; +}; + +extern struct dropbear_fuzz_options fuzz; + +/* guard for when fuzz.h is included by fuzz-common.c */ +#ifndef FUZZ_NO_REPLACE_STDERR + +/* This is a bodge but seems to work. + glibc stdio.h has the comment + "C89/C99 say they're macros. Make them happy." */ +/* OS X has it as a macro */ +#ifdef stderr +#undef stderr +#endif +#define stderr (fuzz.fake_stderr) + +#endif /* FUZZ_NO_REPLACE_STDERR */ + +struct passwd* fuzz_getpwuid(uid_t uid); +struct passwd* fuzz_getpwnam(const char *login); +/* guard for when fuzz.h is included by fuzz-common.c */ +#ifndef FUZZ_NO_REPLACE_GETPW +#define getpwnam(x) fuzz_getpwnam(x) +#define getpwuid(x) fuzz_getpwuid(x) +#endif // FUZZ_NO_REPLACE_GETPW + +#endif /* DROPBEAR_FUZZ */ + +#endif /* DROPBEAR_FUZZ_H */ diff --git a/src/gcm.c b/src/gcm.c new file mode 100644 index 0000000..2ceced1 --- /dev/null +++ b/src/gcm.c @@ -0,0 +1,120 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2020 by Vladislav Grishenko + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "algo.h" +#include "dbutil.h" +#include "gcm.h" + +#if DROPBEAR_ENABLE_GCM_MODE + +#define GHASH_LEN 16 + +static const struct dropbear_hash dropbear_ghash = + {NULL, 0, GHASH_LEN}; + +static int dropbear_gcm_start(int cipher, const unsigned char *IV, + const unsigned char *key, int keylen, + int UNUSED(num_rounds), dropbear_gcm_state *state) { + int err; + + TRACE2(("enter dropbear_gcm_start")) + + if ((err = gcm_init(&state->gcm, cipher, key, keylen)) != CRYPT_OK) { + return err; + } + memcpy(state->iv, IV, GCM_NONCE_LEN); + + TRACE2(("leave dropbear_gcm_start")) + return CRYPT_OK; +} + +static int dropbear_gcm_crypt(unsigned int UNUSED(seq), + const unsigned char *in, unsigned char *out, + unsigned long len, unsigned long taglen, + dropbear_gcm_state *state, int direction) { + unsigned char *iv, tag[GHASH_LEN]; + int i, err; + + TRACE2(("enter dropbear_gcm_crypt")) + + if (len < 4 || taglen != GHASH_LEN) { + return CRYPT_ERROR; + } + + gcm_reset(&state->gcm); + + if ((err = gcm_add_iv(&state->gcm, + state->iv, GCM_NONCE_LEN)) != CRYPT_OK) { + return err; + } + + if ((err = gcm_add_aad(&state->gcm, in, 4)) != CRYPT_OK) { + return err; + } + + if ((err = gcm_process(&state->gcm, (unsigned char *) in + 4, + len - 4, out + 4, direction)) != CRYPT_OK) { + return err; + } + + if (direction == LTC_ENCRYPT) { + gcm_done(&state->gcm, out + len, &taglen); + } else { + gcm_done(&state->gcm, tag, &taglen); + if (constant_time_memcmp(in + len, tag, taglen) != 0) { + return CRYPT_ERROR; + } + } + + /* increment invocation counter */ + iv = state->iv + GCM_IVFIX_LEN; + for (i = GCM_IVCTR_LEN - 1; i >= 0 && ++iv[i] == 0; i--); + + TRACE2(("leave dropbear_gcm_crypt")) + return CRYPT_OK; +} + +static int dropbear_gcm_getlength(unsigned int UNUSED(seq), + const unsigned char *in, unsigned int *outlen, + unsigned long len, dropbear_gcm_state* UNUSED(state)) { + TRACE2(("enter dropbear_gcm_getlength")) + + if (len < 4) { + return CRYPT_ERROR; + } + + LOAD32H(*outlen, in); + + TRACE2(("leave dropbear_gcm_getlength")) + return CRYPT_OK; +} + +const struct dropbear_cipher_mode dropbear_mode_gcm = + {(void *)dropbear_gcm_start, NULL, NULL, + (void *)dropbear_gcm_crypt, + (void *)dropbear_gcm_getlength, &dropbear_ghash}; + +#endif /* DROPBEAR_ENABLE_GCM_MODE */ diff --git a/src/gcm.h b/src/gcm.h new file mode 100644 index 0000000..58c530a --- /dev/null +++ b/src/gcm.h @@ -0,0 +1,47 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2020 by Vladislav Grishenko + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_DROPBEAR_GCM_H_ +#define DROPBEAR_DROPBEAR_GCM_H_ + +#include "includes.h" +#include "algo.h" + +#if DROPBEAR_ENABLE_GCM_MODE + +#define GCM_IVFIX_LEN 4 +#define GCM_IVCTR_LEN 8 +#define GCM_NONCE_LEN (GCM_IVFIX_LEN + GCM_IVCTR_LEN) + +typedef struct { + gcm_state gcm; + unsigned char iv[GCM_NONCE_LEN]; +} dropbear_gcm_state; + +extern const struct dropbear_cipher_mode dropbear_mode_gcm; + +#endif /* DROPBEAR_ENABLE_GCM_MODE */ + +#endif /* DROPBEAR_DROPBEAR_GCM_H_ */ diff --git a/src/gendss.c b/src/gendss.c new file mode 100644 index 0000000..46d161e --- /dev/null +++ b/src/gendss.c @@ -0,0 +1,198 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "signkey.h" +#include "bignum.h" +#include "dbrandom.h" +#include "buffer.h" +#include "gendss.h" +#include "dss.h" + +#define QSIZE 20 /* 160 bit */ + +/* This is just a test */ + +#if DROPBEAR_DSS + +static void getq(const dropbear_dss_key *key); +static void getp(const dropbear_dss_key *key, unsigned int size); +static void getg(const dropbear_dss_key *key); +static void getx(const dropbear_dss_key *key); +static void gety(const dropbear_dss_key *key); + +dropbear_dss_key * gen_dss_priv_key(unsigned int size) { + + dropbear_dss_key *key; + + if (size != 1024) { + dropbear_exit("DSS keys have a fixed size of 1024 bits"); + } + + key = m_malloc(sizeof(*key)); + + m_mp_alloc_init_multi(&key->p, &key->q, &key->g, &key->y, &key->x, NULL); + + getq(key); + getp(key, size/8); + getg(key); + getx(key); + gety(key); + + return key; + +} + +static void getq(const dropbear_dss_key *key) { + + unsigned char buf[QSIZE]; + int trials; + + /* 160 bit prime */ + genrandom(buf, QSIZE); + buf[0] |= 0x80; /* top bit high */ + buf[QSIZE-1] |= 0x01; /* bottom bit high */ + + bytes_to_mp(key->q, buf, QSIZE); + + /* ask FIPS 186.4 how many Rabin-Miller trials are required */ + trials = mp_prime_rabin_miller_trials(mp_count_bits(key->q)); + if (mp_prime_next_prime(key->q, trials, 0) != MP_OKAY) { + fprintf(stderr, "DSS key generation failed\n"); + exit(1); + } +} + +static void getp(const dropbear_dss_key *key, unsigned int size) { + + DEF_MP_INT(tempX); + DEF_MP_INT(tempC); + DEF_MP_INT(tempP); + DEF_MP_INT(temp2q); + int result, trials; + unsigned char *buf; + + m_mp_init_multi(&tempX, &tempC, &tempP, &temp2q, NULL); + + + /* 2*q */ + if (mp_mul_d(key->q, 2, &temp2q) != MP_OKAY) { + fprintf(stderr, "DSS key generation failed\n"); + exit(1); + } + + buf = (unsigned char*)m_malloc(size); + + result = 0; + do { + + genrandom(buf, size); + buf[0] |= 0x80; /* set the top bit high */ + + /* X is a random mp_int */ + bytes_to_mp(&tempX, buf, size); + + /* C = X mod 2q */ + if (mp_mod(&tempX, &temp2q, &tempC) != MP_OKAY) { + fprintf(stderr, "DSS key generation failed\n"); + exit(1); + } + + /* P = X - (C - 1) = X - C + 1*/ + if (mp_sub(&tempX, &tempC, &tempP) != MP_OKAY) { + fprintf(stderr, "DSS key generation failed\n"); + exit(1); + } + + if (mp_add_d(&tempP, 1, key->p) != MP_OKAY) { + fprintf(stderr, "DSS key generation failed\n"); + exit(1); + } + + /* ask FIPS 186.4 how many Rabin-Miller trials are required */ + trials = mp_prime_rabin_miller_trials(mp_count_bits(key->p)); + /* result == 1 => p is prime */ + if (mp_prime_is_prime(key->p, trials, &result) != MP_OKAY) { + fprintf(stderr, "DSS key generation failed\n"); + exit(1); + } + } while (!result); + + mp_clear_multi(&tempX, &tempC, &tempP, &temp2q, NULL); + m_burn(buf, size); + m_free(buf); +} + +static void getg(const dropbear_dss_key * key) { + + DEF_MP_INT(div); + DEF_MP_INT(h); + DEF_MP_INT(val); + + m_mp_init_multi(&div, &h, &val, NULL); + + /* get div=(p-1)/q */ + if (mp_sub_d(key->p, 1, &val) != MP_OKAY) { + fprintf(stderr, "DSS key generation failed\n"); + exit(1); + } + if (mp_div(&val, key->q, &div, NULL) != MP_OKAY) { + fprintf(stderr, "DSS key generation failed\n"); + exit(1); + } + + /* initialise h=1 */ + mp_set(&h, 1); + do { + /* now keep going with g=h^div mod p, until g > 1 */ + if (mp_exptmod(&h, &div, key->p, key->g) != MP_OKAY) { + fprintf(stderr, "DSS key generation failed\n"); + exit(1); + } + + if (mp_add_d(&h, 1, &h) != MP_OKAY) { + fprintf(stderr, "DSS key generation failed\n"); + exit(1); + } + + } while (mp_cmp_d(key->g, 1) != MP_GT); + + mp_clear_multi(&div, &h, &val, NULL); +} + +static void getx(const dropbear_dss_key *key) { + + gen_random_mpint(key->q, key->x); +} + +static void gety(const dropbear_dss_key *key) { + + if (mp_exptmod(key->g, key->x, key->p, key->y) != MP_OKAY) { + fprintf(stderr, "DSS key generation failed\n"); + exit(1); + } +} + +#endif /* DROPBEAR_DSS */ diff --git a/src/gendss.h b/src/gendss.h new file mode 100644 index 0000000..33858f2 --- /dev/null +++ b/src/gendss.h @@ -0,0 +1,36 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_GENDSS_H_ +#define DROPBEAR_GENDSS_H_ + +#include "dss.h" + +#if DROPBEAR_DSS + +dropbear_dss_key * gen_dss_priv_key(unsigned int size); + +#endif /* DROPBEAR_DSS */ + +#endif /* DROPBEAR_GENDSS_H_ */ diff --git a/src/gened25519.c b/src/gened25519.c new file mode 100644 index 0000000..a027914 --- /dev/null +++ b/src/gened25519.c @@ -0,0 +1,47 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "dbrandom.h" +#include "curve25519.h" +#include "gened25519.h" + +#if DROPBEAR_ED25519 + +dropbear_ed25519_key * gen_ed25519_priv_key(unsigned int size) { + + dropbear_ed25519_key *key; + + if (size != 256) { + dropbear_exit("Ed25519 keys have a fixed size of 256 bits"); + } + + key = m_malloc(sizeof(*key)); + dropbear_ed25519_make_key(key->pub, key->priv); + + return key; +} + +#endif /* DROPBEAR_ED25519 */ diff --git a/src/gened25519.h b/src/gened25519.h new file mode 100644 index 0000000..8058310 --- /dev/null +++ b/src/gened25519.h @@ -0,0 +1,36 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_GENED25519_H_ +#define DROPBEAR_GENED25519_H_ + +#include "ed25519.h" + +#if DROPBEAR_ED25519 + +dropbear_ed25519_key * gen_ed25519_priv_key(unsigned int size); + +#endif /* DROPBEAR_ED25519 */ + +#endif /* DROPBEAR_GENED25519_H_ */ diff --git a/src/genrsa.c b/src/genrsa.c new file mode 100644 index 0000000..e249d6e --- /dev/null +++ b/src/genrsa.c @@ -0,0 +1,134 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "bignum.h" +#include "dbrandom.h" +#include "rsa.h" +#include "genrsa.h" + +#define RSA_E 65537 + +#if DROPBEAR_RSA + +static void getrsaprime(mp_int* prime, mp_int *primeminus, + const mp_int* rsa_e, unsigned int size_bytes); + +/* mostly taken from libtomcrypt's rsa key generation routine */ +dropbear_rsa_key * gen_rsa_priv_key(unsigned int size) { + + dropbear_rsa_key * key; + DEF_MP_INT(pminus); + DEF_MP_INT(qminus); + DEF_MP_INT(lcm); + + if (size < 512 || size > 4096 || (size % 8 != 0)) { + dropbear_exit("Bits must satisfy 512 <= bits <= 4096, and be a" + " multiple of 8"); + } + + key = m_malloc(sizeof(*key)); + m_mp_alloc_init_multi(&key->e, &key->n, &key->d, &key->p, &key->q, NULL); + m_mp_init_multi(&pminus, &lcm, &qminus, NULL); + + mp_set_ul(key->e, RSA_E); + + while (1) { + getrsaprime(key->p, &pminus, key->e, size/16); + getrsaprime(key->q, &qminus, key->e, size/16); + + if (mp_mul(key->p, key->q, key->n) != MP_OKAY) { + fprintf(stderr, "RSA generation failed\n"); + exit(1); + } + + if ((unsigned int)mp_count_bits(key->n) == size) { + break; + } + } + + /* lcm(p-1, q-1) */ + if (mp_lcm(&pminus, &qminus, &lcm) != MP_OKAY) { + fprintf(stderr, "RSA generation failed\n"); + exit(1); + } + + /* de = 1 mod lcm(p-1,q-1) */ + /* therefore d = (e^-1) mod lcm(p-1,q-1) */ + if (mp_invmod(key->e, &lcm, key->d) != MP_OKAY) { + fprintf(stderr, "RSA generation failed\n"); + exit(1); + } + + mp_clear_multi(&pminus, &qminus, &lcm, NULL); + + return key; +} + +/* return a prime suitable for p or q */ +static void getrsaprime(mp_int* prime, mp_int *primeminus, + const mp_int* rsa_e, unsigned int size_bytes) { + + unsigned char *buf; + int trials; + DEF_MP_INT(temp_gcd); + + buf = (unsigned char*)m_malloc(size_bytes); + + m_mp_init(&temp_gcd); + do { + /* generate a random odd number with MSB set, then find the + the next prime above it */ + genrandom(buf, size_bytes); + buf[0] |= 0x80; + + bytes_to_mp(prime, buf, size_bytes); + + /* find the next integer which is prime */ + trials = mp_prime_rabin_miller_trials(mp_count_bits(prime)); + if (mp_prime_next_prime(prime, trials, 0) != MP_OKAY) { + fprintf(stderr, "RSA generation failed\n"); + exit(1); + } + + /* subtract one to get p-1 */ + if (mp_sub_d(prime, 1, primeminus) != MP_OKAY) { + fprintf(stderr, "RSA generation failed\n"); + exit(1); + } + /* check relative primality to e */ + if (mp_gcd(primeminus, rsa_e, &temp_gcd) != MP_OKAY) { + fprintf(stderr, "RSA generation failed\n"); + exit(1); + } + } while (mp_cmp_d(&temp_gcd, 1) != MP_EQ); /* while gcd(p-1, e) != 1 */ + + /* now we have a good value for result */ + mp_clear(&temp_gcd); + m_burn(buf, size_bytes); + m_free(buf); +} + +#endif /* DROPBEAR_RSA */ diff --git a/src/genrsa.h b/src/genrsa.h new file mode 100644 index 0000000..641d5a5 --- /dev/null +++ b/src/genrsa.h @@ -0,0 +1,36 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_GENRSA_H_ +#define DROPBEAR_GENRSA_H_ + +#include "rsa.h" + +#if DROPBEAR_RSA + +dropbear_rsa_key * gen_rsa_priv_key(unsigned int size); + +#endif /* DROPBEAR_RSA */ + +#endif /* DROPBEAR_GENRSA_H_ */ diff --git a/src/gensignkey.c b/src/gensignkey.c new file mode 100644 index 0000000..cfe0a80 --- /dev/null +++ b/src/gensignkey.c @@ -0,0 +1,193 @@ +#include "includes.h" +#include "dbutil.h" +#include "buffer.h" +#include "ecdsa.h" +#include "genrsa.h" +#include "gendss.h" +#include "gened25519.h" +#include "signkey.h" +#include "dbrandom.h" + +/* Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int buf_writefile(buffer * buf, const char * filename, int skip_exist) { + int ret = DROPBEAR_FAILURE; + int fd = -1; + + fd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + if (fd < 0) { + /* If generating keys on connection (skip_exist) it's OK to get EEXIST + - we probably just lost a race with another connection to generate the key */ + if (skip_exist && errno == EEXIST) { + ret = DROPBEAR_SUCCESS; + } else { + dropbear_log(LOG_ERR, "Couldn't create new file %s: %s", + filename, strerror(errno)); + } + + goto out; + } + + /* write the file now */ + while (buf->pos != buf->len) { + int len = write(fd, buf_getptr(buf, buf->len - buf->pos), + buf->len - buf->pos); + if (len == -1 && errno == EINTR) { + continue; + } + if (len <= 0) { + dropbear_log(LOG_ERR, "Failed writing file %s: %s", + filename, strerror(errno)); + goto out; + } + buf_incrpos(buf, len); + } + + ret = DROPBEAR_SUCCESS; + +out: + if (fd >= 0) { + if (fsync(fd) != 0) { + dropbear_log(LOG_ERR, "fsync of %s failed: %s", filename, strerror(errno)); + } + m_close(fd); + } + return ret; +} + +/* returns 0 on failure */ +static int get_default_bits(enum signkey_type keytype) +{ + switch (keytype) { +#if DROPBEAR_RSA + case DROPBEAR_SIGNKEY_RSA: + return DROPBEAR_DEFAULT_RSA_SIZE; +#endif +#if DROPBEAR_DSS + case DROPBEAR_SIGNKEY_DSS: + /* DSS for SSH only defines 1024 bits */ + return 1024; +#endif +#if DROPBEAR_ECDSA + case DROPBEAR_SIGNKEY_ECDSA_KEYGEN: + return ECDSA_DEFAULT_SIZE; + case DROPBEAR_SIGNKEY_ECDSA_NISTP521: + return 521; + case DROPBEAR_SIGNKEY_ECDSA_NISTP384: + return 384; + case DROPBEAR_SIGNKEY_ECDSA_NISTP256: + return 256; +#endif +#if DROPBEAR_ED25519 + case DROPBEAR_SIGNKEY_ED25519: + return 256; +#endif + default: + return 0; + } +} + +int signkey_generate_get_bits(enum signkey_type keytype, int bits) { + if (bits == 0) + { + bits = get_default_bits(keytype); + } + return bits; +} + +/* if skip_exist is set it will silently return if the key file exists */ +int signkey_generate(enum signkey_type keytype, int bits, const char* filename, int skip_exist) +{ + sign_key * key = NULL; + buffer *buf = NULL; + char *fn_temp = NULL; + int ret = DROPBEAR_FAILURE; + bits = signkey_generate_get_bits(keytype, bits); + + /* now we can generate the key */ + key = new_sign_key(); + + seedrandom(); + + switch(keytype) { +#if DROPBEAR_RSA + case DROPBEAR_SIGNKEY_RSA: + key->rsakey = gen_rsa_priv_key(bits); + break; +#endif +#if DROPBEAR_DSS + case DROPBEAR_SIGNKEY_DSS: + key->dsskey = gen_dss_priv_key(bits); + break; +#endif +#if DROPBEAR_ECDSA + case DROPBEAR_SIGNKEY_ECDSA_KEYGEN: + case DROPBEAR_SIGNKEY_ECDSA_NISTP521: + case DROPBEAR_SIGNKEY_ECDSA_NISTP384: + case DROPBEAR_SIGNKEY_ECDSA_NISTP256: + { + ecc_key *ecckey = gen_ecdsa_priv_key(bits); + keytype = ecdsa_signkey_type(ecckey); + *signkey_key_ptr(key, keytype) = ecckey; + } + break; +#endif +#if DROPBEAR_ED25519 + case DROPBEAR_SIGNKEY_ED25519: + key->ed25519key = gen_ed25519_priv_key(bits); + break; +#endif + default: + dropbear_exit("Internal error"); + } + + seedrandom(); + + buf = buf_new(MAX_PRIVKEY_SIZE); + + buf_put_priv_key(buf, key, keytype); + sign_key_free(key); + key = NULL; + buf_setpos(buf, 0); + + fn_temp = m_malloc(strlen(filename) + 30); + snprintf(fn_temp, strlen(filename)+30, "%s.tmp%d", filename, getpid()); + ret = buf_writefile(buf, fn_temp, 0); + + if (ret == DROPBEAR_FAILURE) { + goto out; + } + + if (link(fn_temp, filename) < 0) { + /* If generating keys on connection (skipexist) it's OK to get EEXIST + - we probably just lost a race with another connection to generate the key */ + if (!(skip_exist && errno == EEXIST)) { + if (errno == EPERM || errno == EACCES) { + /* Non-atomic fallback when hard-links not allowed or unsupported */ + buf_setpos(buf, 0); + ret = buf_writefile(buf, filename, skip_exist); + } else { + dropbear_log(LOG_ERR, "Failed moving key file to %s: %s", filename, + strerror(errno)); + ret = DROPBEAR_FAILURE; + } + + goto out; + } + } + + /* ensure directory update is flushed to disk, otherwise we can end up + with zero-byte hostkey files if the power goes off */ + fsync_parent_dir(filename); + +out: + if (buf) { + buf_burn_free(buf); + } + + if (fn_temp) { + unlink(fn_temp); + m_free(fn_temp); + } + + return ret; +} diff --git a/src/gensignkey.h b/src/gensignkey.h new file mode 100644 index 0000000..73b9c3c --- /dev/null +++ b/src/gensignkey.h @@ -0,0 +1,9 @@ +#ifndef DROPBEAR_GENSIGNKEY_H +#define DROPBEAR_GENSIGNKEY_H + +#include "signkey.h" + +int signkey_generate(enum signkey_type type, int bits, const char* filename, int skip_exist); +int signkey_generate_get_bits(enum signkey_type keytype, int bits); + +#endif diff --git a/src/includes.h b/src/includes.h new file mode 100644 index 0000000..98d35de --- /dev/null +++ b/src/includes.h @@ -0,0 +1,198 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_INCLUDES_H_ +#define DROPBEAR_INCLUDES_H_ + +#include "options.h" +#include "debug.h" + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/param.h> /* required for BSD4_4 define */ +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <sys/resource.h> + +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <limits.h> +#include <pwd.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> +#include <syslog.h> +#include <netdb.h> +#include <ctype.h> +#include <stdarg.h> +#include <dirent.h> +#include <time.h> +#include <setjmp.h> + +#ifdef HAVE_UTMP_H +#include <utmp.h> +#endif + +#ifdef HAVE_UTMPX_H +#include <utmpx.h> +#endif + +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif + +#ifdef HAVE_LASTLOG_H +#include <lastlog.h> +#endif + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#include <arpa/inet.h> + +/* netbsd 1.6 needs this to be included before netinet/ip.h for some + * undocumented reason */ +#ifdef HAVE_NETINET_IN_SYSTM_H +#include <netinet/in_systm.h> +#endif + +#include <netinet/ip.h> + +#ifdef HAVE_NETINET_TCP_H +#include <netinet/tcp.h> +#endif + +#ifdef HAVE_INTTYPES_H +#include <inttypes.h> +#endif + +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif + +#ifdef HAVE_CRYPT_H +#include <crypt.h> +#endif + +#ifndef DISABLE_ZLIB +#include <zlib.h> +#endif + +#ifdef HAVE_UTIL_H +#include <util.h> +#endif + +#ifdef HAVE_SHADOW_H +#include <shadow.h> +#endif + +#ifdef HAVE_LIBGEN_H +#include <libgen.h> +#endif + +#ifdef HAVE_SYS_UIO_H +#include <sys/uio.h> +#endif + +#ifdef HAVE_SYS_RANDOM_H +#include <sys/random.h> +#endif + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#ifdef BUNDLED_LIBTOM +#include "../libtomcrypt/src/headers/tomcrypt.h" +#include "../libtommath/tommath.h" +#else +#include <tomcrypt.h> +#include <tommath.h> +#endif + +#include "compat.h" + +#ifndef HAVE_U_INT8_T +typedef unsigned char u_int8_t; +#endif /* HAVE_U_INT8_T */ +#ifndef HAVE_UINT8_T +typedef u_int8_t uint8_t; +#endif /* HAVE_UINT8_T */ + +#ifndef HAVE_U_INT16_T +typedef unsigned short u_int16_t; +#endif /* HAVE_U_INT16_T */ +#ifndef HAVE_UINT16_T +typedef u_int16_t uint16_t; +#endif /* HAVE_UINT16_T */ + +#ifndef HAVE_U_INT32_T +typedef unsigned int u_int32_t; +#endif /* HAVE_U_INT32_T */ +#ifndef HAVE_UINT32_T +typedef u_int32_t uint32_t; +#endif /* HAVE_UINT32_T */ + +#ifndef SIZE_T_MAX +#define SIZE_T_MAX ULONG_MAX +#endif /* SIZE_T_MAX */ + +#ifdef HAVE_LINUX_PKT_SCHED_H +#include <linux/types.h> +#include <linux/pkt_sched.h> +#endif + +#if DROPBEAR_PLUGIN +#include <dlfcn.h> +#endif + +extern char** environ; + +#include "fake-rfc2553.h" + +#include "fuzz.h" + +#ifndef LOG_AUTHPRIV +#define LOG_AUTHPRIV LOG_AUTH +#endif + +/* so we can avoid warnings about unused params (ie in signal handlers etc) */ +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#elif defined(__LCLINT__) +# define UNUSED(x) /*@unused@*/ x +#else +# define UNUSED(x) x +#endif + +#endif /* DROPBEAR_INCLUDES_H_ */ diff --git a/src/kex.h b/src/kex.h new file mode 100644 index 0000000..77cf21a --- /dev/null +++ b/src/kex.h @@ -0,0 +1,113 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_KEX_H_ +#define DROPBEAR_KEX_H_ + +#include "includes.h" +#include "algo.h" +#include "signkey.h" + +void send_msg_kexinit(void); +void recv_msg_kexinit(void); +void send_msg_newkeys(void); +void recv_msg_newkeys(void); +void kexfirstinitialise(void); +void finish_kexhashbuf(void); + +#if DROPBEAR_NORMAL_DH +struct kex_dh_param *gen_kexdh_param(void); +void free_kexdh_param(struct kex_dh_param *param); +void kexdh_comb_key(struct kex_dh_param *param, mp_int *dh_pub_them, + sign_key *hostkey); +#endif + +#if DROPBEAR_ECDH +struct kex_ecdh_param *gen_kexecdh_param(void); +void free_kexecdh_param(struct kex_ecdh_param *param); +void kexecdh_comb_key(struct kex_ecdh_param *param, buffer *pub_them, + sign_key *hostkey); +#endif + +#if DROPBEAR_CURVE25519 +struct kex_curve25519_param *gen_kexcurve25519_param(void); +void free_kexcurve25519_param(struct kex_curve25519_param *param); +void kexcurve25519_comb_key(const struct kex_curve25519_param *param, const buffer *pub_them, + sign_key *hostkey); +#endif + +#ifndef DISABLE_ZLIB +int is_compress_trans(void); +int is_compress_recv(void); +#endif + +void recv_msg_kexdh_init(void); /* server */ + +void send_msg_kexdh_init(void); /* client */ +void recv_msg_kexdh_reply(void); /* client */ + +void recv_msg_ext_info(void); + +struct KEXState { + + unsigned sentkexinit : 1; /*set when we've sent/recv kexinit packet */ + unsigned recvkexinit : 1; + unsigned them_firstfollows : 1; /* true when first_kex_packet_follows is set */ + unsigned sentnewkeys : 1; /* set once we've send MSG_NEWKEYS (will be cleared once we have also received */ + unsigned recvnewkeys : 1; /* set once we've received MSG_NEWKEYS (cleared once we have also sent */ + + unsigned int donefirstkex; /* Set to 1 after the first kex has completed, + ie the transport layer has been set up */ + unsigned int donesecondkex; /* Set to 1 after the second kex has completed */ + + unsigned our_first_follows_matches : 1; + + time_t lastkextime; /* time of the last kex */ + unsigned int datatrans; /* data transmitted since last kex */ + unsigned int datarecv; /* data received since last kex */ + +}; + +#if DROPBEAR_NORMAL_DH +struct kex_dh_param { + mp_int pub; /* e */ + mp_int priv; /* x */ +}; +#endif + +#if DROPBEAR_ECDH +struct kex_ecdh_param { + ecc_key key; +}; +#endif + +#if DROPBEAR_CURVE25519 +#define CURVE25519_LEN 32 +struct kex_curve25519_param { + unsigned char priv[CURVE25519_LEN]; + unsigned char pub[CURVE25519_LEN]; +}; +#endif + +#endif /* DROPBEAR_KEX_H_ */ diff --git a/src/keyimport.c b/src/keyimport.c new file mode 100644 index 0000000..e88ef46 --- /dev/null +++ b/src/keyimport.c @@ -0,0 +1,1147 @@ +/* + * Based on PuTTY's import.c for importing/exporting OpenSSH and SSH.com + * keyfiles. + * + * Modifications copyright 2003-2022 Matt Johnston + * + * PuTTY is copyright 1997-2003 Simon Tatham. + * + * Portions copyright Robert de Bath, Joris van Rantwijk, Delian + * Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, + * Justin Bradford, and CORE SDI S.A. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "keyimport.h" +#include "bignum.h" +#include "buffer.h" +#include "dbutil.h" +#include "ecc.h" +#include "ssh.h" +#include "rsa.h" +#include "dss.h" +#include "ed25519.h" +#include "ecdsa.h" +#include "signkey_ossh.h" + +static const unsigned char OSSH_PKEY_BLOB[] = + "openssh-key-v1\0" /* AUTH_MAGIC */ + "\0\0\0\4none" /* cipher name*/ + "\0\0\0\4none" /* kdf name */ + "\0\0\0\0" /* kdf */ + "\0\0\0\1"; /* key num */ +#define OSSH_PKEY_BLOBLEN (sizeof(OSSH_PKEY_BLOB) - 1) +#if DROPBEAR_ECDSA +static const unsigned char OID_SEC256R1_BLOB[] = {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07}; +static const unsigned char OID_SEC384R1_BLOB[] = {0x2b, 0x81, 0x04, 0x00, 0x22}; +static const unsigned char OID_SEC521R1_BLOB[] = {0x2b, 0x81, 0x04, 0x00, 0x23}; +#endif + +#define PUT_32BIT(cp, value) do { \ + (cp)[3] = (unsigned char)(value); \ + (cp)[2] = (unsigned char)((value) >> 8); \ + (cp)[1] = (unsigned char)((value) >> 16); \ + (cp)[0] = (unsigned char)((value) >> 24); } while (0) + +#define GET_32BIT(cp) \ + (((unsigned long)(unsigned char)(cp)[0] << 24) | \ + ((unsigned long)(unsigned char)(cp)[1] << 16) | \ + ((unsigned long)(unsigned char)(cp)[2] << 8) | \ + ((unsigned long)(unsigned char)(cp)[3])) + +static int openssh_encrypted(const char *filename); +static sign_key *openssh_read(const char *filename, const char *passphrase); +static int openssh_write(const char *filename, sign_key *key, + const char *passphrase); + +static int dropbear_write(const char*filename, sign_key * key); +static sign_key *dropbear_read(const char* filename); + +static int toint(unsigned u); + +#if 0 +static int sshcom_encrypted(const char *filename, char **comment); +static struct ssh2_userkey *sshcom_read(const char *filename, char *passphrase); +static int sshcom_write(const char *filename, struct ssh2_userkey *key, + char *passphrase); +#endif + +int import_encrypted(const char* filename, int filetype) { + + if (filetype == KEYFILE_OPENSSH) { + return openssh_encrypted(filename); +#if 0 + } else if (filetype == KEYFILE_SSHCOM) { + return sshcom_encrypted(filename, NULL); +#endif + } + return 0; +} + +sign_key *import_read(const char *filename, const char *passphrase, int filetype) { + + if (filetype == KEYFILE_OPENSSH) { + return openssh_read(filename, passphrase); + } else if (filetype == KEYFILE_DROPBEAR) { + return dropbear_read(filename); +#if 0 + } else if (filetype == KEYFILE_SSHCOM) { + return sshcom_read(filename, passphrase); +#endif + } + return NULL; +} + +int import_write(const char *filename, sign_key *key, const char *passphrase, + int filetype) { + + if (filetype == KEYFILE_OPENSSH) { + return openssh_write(filename, key, passphrase); + } else if (filetype == KEYFILE_DROPBEAR) { + return dropbear_write(filename, key); +#if 0 + } else if (filetype == KEYFILE_SSHCOM) { + return sshcom_write(filename, key, passphrase); +#endif + } + return 0; +} + +static sign_key *dropbear_read(const char* filename) { + + buffer * buf = NULL; + sign_key *ret = NULL; + enum signkey_type type; + + buf = buf_new(MAX_PRIVKEY_SIZE); + if (buf_readfile(buf, filename) == DROPBEAR_FAILURE) { + goto error; + } + + buf_setpos(buf, 0); + ret = new_sign_key(); + + type = DROPBEAR_SIGNKEY_ANY; + if (buf_get_priv_key(buf, ret, &type) == DROPBEAR_FAILURE){ + goto error; + } + buf_free(buf); + + ret->type = type; + + return ret; + +error: + if (buf) { + buf_free(buf); + } + if (ret) { + sign_key_free(ret); + } + return NULL; +} + +/* returns 0 on fail, 1 on success */ +static int dropbear_write(const char*filename, sign_key * key) { + + buffer * buf; + FILE*fp; + int len; + int ret; + + buf = buf_new(MAX_PRIVKEY_SIZE); + buf_put_priv_key(buf, key, key->type); + + fp = fopen(filename, "w"); + if (!fp) { + ret = 0; + goto out; + } + + buf_setpos(buf, 0); + do { + len = fwrite(buf_getptr(buf, buf->len - buf->pos), + 1, buf->len - buf->pos, fp); + buf_incrpos(buf, len); + } while (len > 0 && buf->len != buf->pos); + + fclose(fp); + + if (buf->pos != buf->len) { + ret = 0; + } else { + ret = 1; + } +out: + buf_free(buf); + return ret; +} + + +/* ---------------------------------------------------------------------- + * Helper routines. (The base64 ones are defined in sshpubk.c.) + */ + +#define isbase64(c) ( ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + ((c) >= '0' && (c) <= '9') || \ + (c) == '+' || (c) == '/' || (c) == '=' \ + ) + +/* cpl has to be less than 100 */ +static void base64_encode_fp(FILE * fp, const unsigned char *data, + int datalen, int cpl) +{ + unsigned char out[100]; + int n; + unsigned long outlen; + int rawcpl; + rawcpl = cpl * 3 / 4; + dropbear_assert((unsigned int)cpl < sizeof(out)); + + while (datalen > 0) { + n = (datalen < rawcpl ? datalen : rawcpl); + outlen = sizeof(out); + base64_encode(data, n, out, &outlen); + data += n; + datalen -= n; + fwrite(out, 1, outlen, fp); + fputc('\n', fp); + } +} +/* + * Read an ASN.1/BER identifier and length pair. + * + * Flags are a combination of the #defines listed below. + * + * Returns -1 if unsuccessful; otherwise returns the number of + * bytes used out of the source data. + */ + +/* ASN.1 tag classes. */ +#define ASN1_CLASS_UNIVERSAL (0 << 6) +#define ASN1_CLASS_APPLICATION (1 << 6) +#define ASN1_CLASS_CONTEXT_SPECIFIC (2 << 6) +#define ASN1_CLASS_PRIVATE (3 << 6) +#define ASN1_CLASS_MASK (3 << 6) + +/* Primitive versus constructed bit. */ +#define ASN1_CONSTRUCTED (1 << 5) + +static int ber_read_id_len(void *source, int sourcelen, + int *id, int *length, int *flags) +{ + unsigned char *p = (unsigned char *) source; + + if (sourcelen == 0) + return -1; + + *flags = (*p & 0xE0); + if ((*p & 0x1F) == 0x1F) { + *id = 0; + while (*p & 0x80) { + p++, sourcelen--; + if (sourcelen == 0) + return -1; + *id = (*id << 7) | (*p & 0x7F); + } + p++, sourcelen--; + } else { + *id = *p & 0x1F; + p++, sourcelen--; + } + + if (sourcelen == 0) + return -1; + + if (*p & 0x80) { + unsigned len; + int n = *p & 0x7F; + p++, sourcelen--; + if (sourcelen < n) + return -1; + len = 0; + while (n--) + len = (len << 8) | (*p++); + sourcelen -= n; + *length = toint(len); + } else { + *length = *p; + p++, sourcelen--; + } + + if (*length < 0) { + printf("Negative ASN.1 length\n"); + return -1; + } + + return p - (unsigned char *) source; +} + +/* + * Write an ASN.1/BER identifier and length pair. Returns the + * number of bytes consumed. Assumes dest contains enough space. + * Will avoid writing anything if dest is NULL, but still return + * amount of space required. + */ +#if DROPBEAR_DSS +static int ber_write_id_len(void *dest, int id, int length, int flags) +{ + unsigned char *d = (unsigned char *)dest; + int len = 0; + + if (id <= 30) { + /* + * Identifier is one byte. + */ + len++; + if (d) *d++ = id | flags; + } else { + int n; + /* + * Identifier is multiple bytes: the first byte is 11111 + * plus the flags, and subsequent bytes encode the value of + * the identifier, 7 bits at a time, with the top bit of + * each byte 1 except the last one which is 0. + */ + len++; + if (d) *d++ = 0x1F | flags; + for (n = 1; (id >> (7*n)) > 0; n++) + continue; /* count the bytes */ + while (n--) { + len++; + if (d) *d++ = (n ? 0x80 : 0) | ((id >> (7*n)) & 0x7F); + } + } + + if (length < 128) { + /* + * Length is one byte. + */ + len++; + if (d) *d++ = length; + } else { + int n; + /* + * Length is multiple bytes. The first is 0x80 plus the + * number of subsequent bytes, and the subsequent bytes + * encode the actual length. + */ + for (n = 1; (length >> (8*n)) > 0; n++) + continue; /* count the bytes */ + len++; + if (d) *d++ = 0x80 | n; + while (n--) { + len++; + if (d) *d++ = (length >> (8*n)) & 0xFF; + } + } + + return len; +} +#endif /* DROPBEAR_DSS */ + + +/* Simple structure to point to an mp-int within a blob. */ +struct mpint_pos { void *start; int bytes; }; + +/* ---------------------------------------------------------------------- + * Code to read and write OpenSSH private keys. + */ + +enum { OSSH_DSA, OSSH_RSA, OSSH_EC, OSSH_PKEY }; +struct openssh_key { + int type; + int encrypted; + char iv[32]; + /* keyblob is publickey1 onwards (ref OpenSSH PROTOCOL.key) */ + unsigned char *keyblob; + unsigned int keyblob_len, keyblob_size; +}; + +static struct openssh_key *load_openssh_key(const char *filename) +{ + struct openssh_key *ret; + buffer *buf = NULL; + FILE *fp = NULL; + char buffer[256]; + char *errmsg = NULL, *p = NULL; + int headers_done; + unsigned long len; + + ret = (struct openssh_key*)m_malloc(sizeof(struct openssh_key)); + ret->keyblob = NULL; + ret->keyblob_len = ret->keyblob_size = 0; + ret->encrypted = 0; + memset(ret->iv, 0, sizeof(ret->iv)); + + if (strlen(filename) == 1 && filename[0] == '-') { + fp = stdin; + } else { + fp = fopen(filename, "r"); + } + if (!fp) { + errmsg = "Unable to open key file"; + goto error; + } + if (!fgets(buffer, sizeof(buffer), fp) || + 0 != strncmp(buffer, "-----BEGIN ", 11) || + 0 != strcmp(buffer+strlen(buffer)-17, "PRIVATE KEY-----\n")) { + errmsg = "File does not begin with OpenSSH key header"; + goto error; + } + if (!strcmp(buffer, "-----BEGIN RSA PRIVATE KEY-----\n")) + ret->type = OSSH_RSA; + else if (!strcmp(buffer, "-----BEGIN DSA PRIVATE KEY-----\n")) + ret->type = OSSH_DSA; + else if (!strcmp(buffer, "-----BEGIN EC PRIVATE KEY-----\n")) + ret->type = OSSH_EC; + else if (!strcmp(buffer, "-----BEGIN OPENSSH PRIVATE KEY-----\n")) + ret->type = OSSH_PKEY; + else { + errmsg = "Unrecognised key type"; + goto error; + } + + headers_done = 0; + buf = buf_new(0); + while (1) { + if (!fgets(buffer, sizeof(buffer), fp)) { + errmsg = "Unexpected end of file"; + goto error; + } + if (0 == strncmp(buffer, "-----END ", 9) && + 0 == strcmp(buffer+strlen(buffer)-17, "PRIVATE KEY-----\n")) + break; /* done */ + if ((p = strchr(buffer, ':')) != NULL) { + if (headers_done) { + errmsg = "Header found in body of key data"; + goto error; + } + *p++ = '\0'; + while (*p && isspace((unsigned char)*p)) p++; + if (!strcmp(buffer, "Proc-Type")) { + if (p[0] != '4' || p[1] != ',') { + errmsg = "Proc-Type is not 4 (only 4 is supported)"; + goto error; + } + p += 2; + if (!strcmp(p, "ENCRYPTED\n")) + ret->encrypted = 1; + } else if (!strcmp(buffer, "DEK-Info")) { + int i, j; + + if (strncmp(p, "DES-EDE3-CBC,", 13)) { + errmsg = "Ciphers other than DES-EDE3-CBC not supported"; + goto error; + } + p += 13; + for (i = 0; i < 8; i++) { + if (1 != sscanf(p, "%2x", &j)) + break; + ret->iv[i] = j; + p += 2; + } + if (i < 8) { + errmsg = "Expected 16-digit iv in DEK-Info"; + goto error; + } + } + } else { + headers_done = 1; + len = strlen(buffer); + buf = buf_resize(buf, buf->size + len); + buf_putbytes(buf, buffer, len); + } + } + + if (buf && buf->len) { + ret->keyblob_size = ret->keyblob_len + buf->len*4/3 + 256; + ret->keyblob = (unsigned char*)m_realloc(ret->keyblob, ret->keyblob_size); + len = ret->keyblob_size; + if (base64_decode((const unsigned char *)buf->data, buf->len, + ret->keyblob, &len) != CRYPT_OK){ + errmsg = "Error decoding base64"; + goto error; + } + ret->keyblob_len = len; + } + + if (ret->type == OSSH_PKEY) { + if (ret->keyblob_len < OSSH_PKEY_BLOBLEN || + memcmp(ret->keyblob, OSSH_PKEY_BLOB, OSSH_PKEY_BLOBLEN)) { + errmsg = "Error decoding OpenSSH key"; + goto error; + } + ret->keyblob_len -= OSSH_PKEY_BLOBLEN; + memmove(ret->keyblob, ret->keyblob + OSSH_PKEY_BLOBLEN, ret->keyblob_len); + } + + if (ret->keyblob_len == 0 || !ret->keyblob) { + errmsg = "Key body not present"; + goto error; + } + + if (ret->encrypted && ret->keyblob_len % 8 != 0) { + errmsg = "Encrypted key blob is not a multiple of cipher block size"; + goto error; + } + + if (buf) { + buf_burn_free(buf); + } + m_burn(buffer, sizeof(buffer)); + return ret; + +error: + if (buf) { + buf_burn_free(buf); + } + m_burn(buffer, sizeof(buffer)); + if (ret) { + if (ret->keyblob) { + m_burn(ret->keyblob, ret->keyblob_size); + m_free(ret->keyblob); + } + m_free(ret); + } + if (fp) { + fclose(fp); + } + if (errmsg) { + fprintf(stderr, "Error: %s\n", errmsg); + } + return NULL; +} + +static int openssh_encrypted(const char *filename) +{ + struct openssh_key *key = load_openssh_key(filename); + int ret; + + if (!key) + return 0; + ret = key->encrypted; + m_burn(key->keyblob, key->keyblob_size); + m_free(key->keyblob); + m_free(key); + return ret; +} + +static sign_key *openssh_read(const char *filename, const char * UNUSED(passphrase)) +{ + struct openssh_key *key; + unsigned char *p; + int ret, id, len, flags; + int i, num_integers = 0; + sign_key *retval = NULL; + char *errmsg; + unsigned char *modptr = NULL; + int modlen = -9999; + enum signkey_type type; + + sign_key *retkey; + buffer * blobbuf = NULL; + + retkey = new_sign_key(); + + key = load_openssh_key(filename); + + if (!key) + return NULL; + + if (key->encrypted) { + errmsg = "Encrypted keys are not supported. Please convert with ssh-keygen first"; + goto error; + } + + /* + * Now we have a decrypted key blob, which contains OpenSSH + * encoded private key. We must now untangle the OpenSSH format. + */ + if (key->type == OSSH_PKEY) { + blobbuf = buf_new(key->keyblob_len); + buf_putbytes(blobbuf, key->keyblob, key->keyblob_len); + buf_setpos(blobbuf, 0); + + /* limit length of public key blob */ + len = buf_getint(blobbuf); + + type = DROPBEAR_SIGNKEY_ANY; + if (buf_get_pub_key(blobbuf, retkey, &type) + != DROPBEAR_SUCCESS) { + errmsg = "Error parsing OpenSSH key"; + goto ossh_error; + } + + /* restore full length */ + buf_setlen(blobbuf, key->keyblob_len); + + /* length of private key part. we can discard it */ + buf_getint(blobbuf); + + /* discard checkkey1 */ + buf_getint(blobbuf); + /* discard checkkey2 */ + buf_getint(blobbuf); + + errmsg = "Unsupported OpenSSH key type"; + retkey->type = type; + ret = DROPBEAR_FAILURE; + /* Parse private key part */ +#if DROPBEAR_RSA + if (type == DROPBEAR_SIGNKEY_RSA) { + errmsg = "Error parsing OpenSSH RSA key"; + ret = buf_get_rsa_priv_ossh(blobbuf, retkey); + } +#endif +#if DROPBEAR_ED25519 + if (type == DROPBEAR_SIGNKEY_ED25519) { + errmsg = "Error parsing OpenSSH ed25519 key"; + ret = buf_get_ed25519_priv_ossh(blobbuf, retkey); + } +#endif +#if DROPBEAR_ECDSA + if (signkey_is_ecdsa(type)) { + errmsg = "Error parsing OpenSSH ecdsa key"; + ret = buf_get_ecdsa_priv_ossh(blobbuf, retkey); + } +#endif + if (ret == DROPBEAR_SUCCESS) { + errmsg = NULL; + retval = retkey; + goto error; + } + +ossh_error: + sign_key_free(retkey); + retkey = NULL; + goto error; + } + + /* + * Now we have a decrypted key blob, which contains an ASN.1 + * encoded private key. We must now untangle the ASN.1. + * + * We expect the whole key blob to be formatted as a SEQUENCE + * (0x30 followed by a length code indicating that the rest of + * the blob is part of the sequence). Within that SEQUENCE we + * expect to see a bunch of INTEGERs. What those integers mean + * depends on the key type: + * + * - For RSA, we expect the integers to be 0, n, e, d, p, q, + * dmp1, dmq1, iqmp in that order. (The last three are d mod + * (p-1), d mod (q-1), inverse of q mod p respectively.) + * + * - For DSA, we expect them to be 0, p, q, g, y, x in that + * order. + */ + + p = key->keyblob; + + /* Expect the SEQUENCE header. Take its absence as a failure to decrypt. */ + ret = ber_read_id_len(p, key->keyblob_len, &id, &len, &flags); + p += ret; + if (ret < 0 || id != 16 || len < 0 || + key->keyblob+key->keyblob_len-p < len) { + errmsg = "ASN.1 decoding failure"; + goto error; + } + + /* Expect a load of INTEGERs. */ + if (key->type == OSSH_RSA) + num_integers = 9; + else if (key->type == OSSH_DSA) + num_integers = 6; + else if (key->type == OSSH_EC) + num_integers = 1; + + /* + * Space to create key blob in. + */ + blobbuf = buf_new(3000); + +#if DROPBEAR_DSS + if (key->type == OSSH_DSA) { + buf_putstring(blobbuf, "ssh-dss", 7); + retkey->type = DROPBEAR_SIGNKEY_DSS; + } +#endif +#if DROPBEAR_RSA + if (key->type == OSSH_RSA) { + buf_putstring(blobbuf, "ssh-rsa", 7); + retkey->type = DROPBEAR_SIGNKEY_RSA; + } +#endif + + for (i = 0; i < num_integers; i++) { + ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, + &id, &len, &flags); + p += ret; + if (ret < 0 || id != 2 || len < 0 || + key->keyblob+key->keyblob_len-p < len) { + errmsg = "ASN.1 decoding failure"; + goto error; + } + + if (i == 0) { + /* First integer is a version indicator */ + int expected = -1; + switch (key->type) { + case OSSH_RSA: + case OSSH_DSA: + expected = 0; + break; + case OSSH_EC: + expected = 1; + break; + } + if (len != 1 || p[0] != expected) { + errmsg = "Version number mismatch"; + goto error; + } + } else if (key->type == OSSH_RSA) { + /* + * OpenSSH key order is n, e, d, p, q, dmp1, dmq1, iqmp + * but we want e, n, d, p, q + */ + if (i == 1) { + /* Save the details for after we deal with number 2. */ + modptr = p; + modlen = len; + } else if (i >= 2 && i <= 5) { + buf_putstring(blobbuf, (const char*)p, len); + if (i == 2) { + buf_putstring(blobbuf, (const char*)modptr, modlen); + } + } + } else if (key->type == OSSH_DSA) { + /* + * OpenSSH key order is p, q, g, y, x, + * we want the same. + */ + buf_putstring(blobbuf, (const char*)p, len); + } + + /* Skip past the number. */ + p += len; + } + +#if DROPBEAR_ECDSA + if (key->type == OSSH_EC) { + unsigned char* private_key_bytes = NULL; + int private_key_len = 0; + unsigned char* public_key_bytes = NULL; + int public_key_len = 0; + ecc_key *ecc = NULL; + const struct dropbear_ecc_curve *curve = NULL; + + /* See SEC1 v2, Appendix C.4 */ + /* OpenSSL (so OpenSSH) seems to include the optional parts. */ + + /* privateKey OCTET STRING, */ + ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, + &id, &len, &flags); + p += ret; + /* id==4 for octet string */ + if (ret < 0 || id != 4 || len < 0 || + key->keyblob+key->keyblob_len-p < len) { + errmsg = "ASN.1 decoding failure"; + goto error; + } + private_key_bytes = p; + private_key_len = len; + p += len; + + /* parameters [0] ECDomainParameters {{ SECGCurveNames }} OPTIONAL, */ + ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, + &id, &len, &flags); + p += ret; + /* id==0 */ + if (ret < 0 || id != 0 || len < 0) { + errmsg = "ASN.1 decoding failure"; + goto error; + } + + ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, + &id, &len, &flags); + p += ret; + /* id==6 for object */ + if (ret < 0 || id != 6 || len < 0 || + key->keyblob+key->keyblob_len-p < len) { + errmsg = "ASN.1 decoding failure"; + goto error; + } + + if (0) {} +#if DROPBEAR_ECC_256 + else if (len == sizeof(OID_SEC256R1_BLOB) + && memcmp(p, OID_SEC256R1_BLOB, len) == 0) { + retkey->type = DROPBEAR_SIGNKEY_ECDSA_NISTP256; + curve = &ecc_curve_nistp256; + } +#endif +#if DROPBEAR_ECC_384 + else if (len == sizeof(OID_SEC384R1_BLOB) + && memcmp(p, OID_SEC384R1_BLOB, len) == 0) { + retkey->type = DROPBEAR_SIGNKEY_ECDSA_NISTP384; + curve = &ecc_curve_nistp384; + } +#endif +#if DROPBEAR_ECC_521 + else if (len == sizeof(OID_SEC521R1_BLOB) + && memcmp(p, OID_SEC521R1_BLOB, len) == 0) { + retkey->type = DROPBEAR_SIGNKEY_ECDSA_NISTP521; + curve = &ecc_curve_nistp521; + } +#endif + else { + errmsg = "Unknown ECC key type"; + goto error; + } + p += len; + + /* publicKey [1] BIT STRING OPTIONAL */ + ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, + &id, &len, &flags); + p += ret; + /* id==1 */ + if (ret < 0 || id != 1 || len < 0) { + errmsg = "ASN.1 decoding failure"; + goto error; + } + + ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, + &id, &len, &flags); + p += ret; + /* id==3 for bit string */ + if (ret < 0 || id != 3 || len < 0 || + key->keyblob+key->keyblob_len-p < len) { + errmsg = "ASN.1 decoding failure"; + goto error; + } + public_key_bytes = p+1; + public_key_len = len-1; + p += len; + + buf_putbytes(blobbuf, public_key_bytes, public_key_len); + ecc = buf_get_ecc_raw_pubkey(blobbuf, curve); + if (!ecc) { + errmsg = "Error parsing ECC key"; + goto error; + } + m_mp_alloc_init_multi((mp_int**)&ecc->k, NULL); + if (mp_from_ubin(ecc->k, private_key_bytes, private_key_len) + != MP_OKAY) { + errmsg = "Error parsing ECC key"; + goto error; + } + + *signkey_key_ptr(retkey, retkey->type) = ecc; + } +#endif /* DROPBEAR_ECDSA */ + + /* + * Now put together the actual key. Simplest way to do this is + * to assemble our own key blobs and feed them to the createkey + * functions; this is a bit faffy but it does mean we get all + * the sanity checks for free. + */ + if (key->type == OSSH_RSA || key->type == OSSH_DSA) { + buf_setpos(blobbuf, 0); + type = DROPBEAR_SIGNKEY_ANY; + if (buf_get_priv_key(blobbuf, retkey, &type) + != DROPBEAR_SUCCESS) { + errmsg = "unable to create key structure"; + sign_key_free(retkey); + retkey = NULL; + goto error; + } + } + + errmsg = NULL; /* no error */ + retval = retkey; + + error: + if (blobbuf) { + buf_burn_free(blobbuf); + } + m_burn(key->keyblob, key->keyblob_size); + m_free(key->keyblob); + m_burn(key, sizeof(*key)); + m_free(key); + if (errmsg) { + fprintf(stderr, "Error: %s\n", errmsg); + } + return retval; +} + +static int openssh_write(const char *filename, sign_key *key, + const char *passphrase) +{ + buffer * keyblob = NULL; + buffer * extrablob = NULL; /* used for calculated values to write */ + unsigned char *outblob = NULL; + int outlen = -9999; + int pos = 0, len = 0, i; + char *header = NULL, *footer = NULL; + int ret = 0; + FILE *fp; + +#if DROPBEAR_DSS + if (key->type == DROPBEAR_SIGNKEY_DSS) { + char zero[1]; + struct mpint_pos numbers[9]; + int nnumbers = -1, seqlen; + /* + * Fetch the key blobs. + */ + keyblob = buf_new(3000); + buf_put_priv_key(keyblob, key, key->type); + + buf_setpos(keyblob, 0); + /* skip the "ssh-rsa" or "ssh-dss" header */ + buf_incrpos(keyblob, buf_getint(keyblob)); + + /* + * Find the sequence of integers to be encoded into the OpenSSH + * key blob, and also decide on the header line. + */ + numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0'; + + if (key->type == DROPBEAR_SIGNKEY_DSS) { + + /* p */ + numbers[1].bytes = buf_getint(keyblob); + numbers[1].start = buf_getptr(keyblob, numbers[1].bytes); + buf_incrpos(keyblob, numbers[1].bytes); + + /* q */ + numbers[2].bytes = buf_getint(keyblob); + numbers[2].start = buf_getptr(keyblob, numbers[2].bytes); + buf_incrpos(keyblob, numbers[2].bytes); + + /* g */ + numbers[3].bytes = buf_getint(keyblob); + numbers[3].start = buf_getptr(keyblob, numbers[3].bytes); + buf_incrpos(keyblob, numbers[3].bytes); + + /* y */ + numbers[4].bytes = buf_getint(keyblob); + numbers[4].start = buf_getptr(keyblob, numbers[4].bytes); + buf_incrpos(keyblob, numbers[4].bytes); + + /* x */ + numbers[5].bytes = buf_getint(keyblob); + numbers[5].start = buf_getptr(keyblob, numbers[5].bytes); + buf_incrpos(keyblob, numbers[5].bytes); + + nnumbers = 6; + header = "-----BEGIN DSA PRIVATE KEY-----\n"; + footer = "-----END DSA PRIVATE KEY-----\n"; + } + + /* + * Now count up the total size of the ASN.1 encoded integers, + * so as to determine the length of the containing SEQUENCE. + */ + len = 0; + for (i = 0; i < nnumbers; i++) { + len += ber_write_id_len(NULL, 2, numbers[i].bytes, 0); + len += numbers[i].bytes; + } + seqlen = len; + /* Now add on the SEQUENCE header. */ + len += ber_write_id_len(NULL, 16, seqlen, ASN1_CONSTRUCTED); + /* Round up to the cipher block size, ensuring we have at least one + * byte of padding (see below). */ + outlen = len; + if (passphrase) + outlen = (outlen+8) &~ 7; + + /* + * Now we know how big outblob needs to be. Allocate it. + */ + outblob = (unsigned char*)m_malloc(outlen); + + /* + * And write the data into it. + */ + pos = 0; + pos += ber_write_id_len(outblob+pos, 16, seqlen, ASN1_CONSTRUCTED); + for (i = 0; i < nnumbers; i++) { + pos += ber_write_id_len(outblob+pos, 2, numbers[i].bytes, 0); + memcpy(outblob+pos, numbers[i].start, numbers[i].bytes); + pos += numbers[i].bytes; + } + } /* end DSS handling */ +#endif /* DROPBEAR_DSS */ + + if (0 +#if DROPBEAR_RSA + || key->type == DROPBEAR_SIGNKEY_RSA +#endif +#if DROPBEAR_ED25519 + || key->type == DROPBEAR_SIGNKEY_ED25519 +#endif +#if DROPBEAR_ECDSA + || signkey_is_ecdsa(key->type) +#endif + ) { + buffer *buf = buf_new(3200); + keyblob = buf_new(3000); + extrablob = buf_new(3100); + + /* private key blob w/o header */ +#if DROPBEAR_RSA + if (key->type == DROPBEAR_SIGNKEY_RSA) { + buf_put_rsa_priv_ossh(keyblob, key); + } +#endif +#if DROPBEAR_ED25519 + if (key->type == DROPBEAR_SIGNKEY_ED25519) { + buf_put_ed25519_priv_ossh(keyblob, key); + } +#endif +#if DROPBEAR_ECDSA + if (signkey_is_ecdsa(key->type)) { + buf_put_ecdsa_priv_ossh(keyblob, key); + } +#endif + + /* header */ + buf_putbytes(buf, OSSH_PKEY_BLOB, OSSH_PKEY_BLOBLEN); + + /* public key */ + buf_put_pub_key(buf, key, key->type); + + /* private key */ + buf_putint(extrablob, 0); /* checkint 1 */ + buf_putint(extrablob, 0); /* checkint 2 */ + /* raw openssh private key */ + buf_putbytes(extrablob, keyblob->data, keyblob->len); + /* comment */ + buf_putstring(extrablob, "", 0); + /* padding to cipher block length */ + len = (extrablob->len+8) & ~7; + for (i = 1; len - extrablob->len > 0; i++) + buf_putbyte(extrablob, i); + buf_setpos(extrablob, 0); + buf_putbytes(extrablob, "\0\0\0\0\0\0\0\0", 8); + buf_putbufstring(buf, extrablob); + + outlen = len = pos = buf->len; + outblob = (unsigned char*)m_malloc(outlen); + memcpy(outblob, buf->data, buf->len); + + buf_burn_free(buf); + buf = NULL; + + header = "-----BEGIN OPENSSH PRIVATE KEY-----\n"; + footer = "-----END OPENSSH PRIVATE KEY-----\n"; + } + + /* + * Padding on OpenSSH keys is deterministic. The number of + * padding bytes is always more than zero, and always at most + * the cipher block length. The value of each padding byte is + * equal to the number of padding bytes. So a plaintext that's + * an exact multiple of the block size will be padded with 08 + * 08 08 08 08 08 08 08 (assuming a 64-bit block cipher); a + * plaintext one byte less than a multiple of the block size + * will be padded with just 01. + * + * This enables the OpenSSL key decryption function to strip + * off the padding algorithmically and return the unpadded + * plaintext to the next layer: it looks at the final byte, and + * then expects to find that many bytes at the end of the data + * with the same value. Those are all removed and the rest is + * returned. + */ + dropbear_assert(pos == len); + while (pos < outlen) { + outblob[pos++] = outlen - len; + } + + /* + * Encrypt the key. + */ + if (passphrase) { + fprintf(stderr, "Encrypted keys aren't supported currently\n"); + goto error; + } + + /* + * And save it. We'll use Unix line endings just in case it's + * subsequently transferred in binary mode. + */ + if (strlen(filename) == 1 && filename[0] == '-') { + fp = stdout; + } else { + fp = fopen(filename, "wb"); /* ensure Unix line endings */ + } + if (!fp) { + fprintf(stderr, "Failed opening output file\n"); + goto error; + } + fputs(header, fp); + base64_encode_fp(fp, outblob, outlen, 64); + fputs(footer, fp); + fclose(fp); + ret = 1; + + error: + if (outblob) { + memset(outblob, 0, outlen); + m_free(outblob); + } + if (keyblob) { + buf_burn_free(keyblob); + } + if (extrablob) { + buf_burn_free(extrablob); + } + return ret; +} + +/* From PuTTY misc.c */ +static int toint(unsigned u) +{ + /* + * Convert an unsigned to an int, without running into the + * undefined behaviour which happens by the strict C standard if + * the value overflows. You'd hope that sensible compilers would + * do the sensible thing in response to a cast, but actually I + * don't trust modern compilers not to do silly things like + * assuming that _obviously_ you wouldn't have caused an overflow + * and so they can elide an 'if (i < 0)' test immediately after + * the cast. + * + * Sensible compilers ought of course to optimise this entire + * function into 'just return the input value'! + */ + if (u <= (unsigned)INT_MAX) + return (int)u; + else if (u >= (unsigned)INT_MIN) /* wrap in cast _to_ unsigned is OK */ + return INT_MIN + (int)(u - (unsigned)INT_MIN); + else + return INT_MIN; /* fallback; should never occur on binary machines */ +} diff --git a/src/keyimport.h b/src/keyimport.h new file mode 100644 index 0000000..b566fc9 --- /dev/null +++ b/src/keyimport.h @@ -0,0 +1,42 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_KEYIMPORT_H_ +#define DROPBEAR_KEYIMPORT_H_ + +#include "includes.h" +#include "signkey.h" + +enum { + KEYFILE_DROPBEAR, + KEYFILE_OPENSSH, + KEYFILE_SSHCOM +}; + +int import_write(const char *filename, sign_key *key, const char *passphrase, + int filetype); +sign_key *import_read(const char *filename, const char *passphrase, int filetype); +int import_encrypted(const char* filename, int filetype); + +#endif /* DROPBEAR_KEYIMPORT_H_ */ diff --git a/src/list.c b/src/list.c new file mode 100644 index 0000000..eeba7c3 --- /dev/null +++ b/src/list.c @@ -0,0 +1,49 @@ +#include "includes.h" +#include "dbutil.h" +#include "list.h" + +void list_append(m_list *list, void *item) { + m_list_elem *elem; + + elem = m_malloc(sizeof(*elem)); + elem->item = item; + elem->list = list; + elem->next = NULL; + if (!list->first) { + list->first = elem; + elem->prev = NULL; + } else { + elem->prev = list->last; + list->last->next = elem; + } + list->last = elem; +} + +m_list * list_new() { + m_list *ret = m_malloc(sizeof(m_list)); + ret->first = ret->last = NULL; + return ret; +} + +void * list_remove(m_list_elem *elem) { + void *item = elem->item; + m_list *list = elem->list; + if (list->first == elem) + { + list->first = elem->next; + } + if (list->last == elem) + { + list->last = elem->prev; + } + if (elem->prev) + { + elem->prev->next = elem->next; + } + if (elem->next) + { + elem->next->prev = elem->prev; + } + m_free(elem); + return item; +} diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..2b5cc07 --- /dev/null +++ b/src/list.h @@ -0,0 +1,28 @@ +#ifndef DROPBEAR_DROPBEAR_LIST_H +#define DROPBEAR_DROPBEAR_LIST_H + +struct _m_list; + +struct _m_list_elem { + void *item; + struct _m_list_elem *next; + struct _m_list_elem *prev; + struct _m_list *list; +}; + +typedef struct _m_list_elem m_list_elem; + +struct _m_list { + m_list_elem *first; + m_list_elem *last; +}; + +typedef struct _m_list m_list; + +m_list * list_new(void); +void list_append(m_list *list, void *item); +/* returns the item for the element removed */ +void * list_remove(m_list_elem *elem); + + +#endif /* DROPBEAR_DROPBEAR_LIST_H */ diff --git a/src/listener.c b/src/listener.c new file mode 100644 index 0000000..4c60589 --- /dev/null +++ b/src/listener.c @@ -0,0 +1,174 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "listener.h" +#include "session.h" +#include "dbutil.h" + +void listeners_initialise() { + + /* just one slot to start with */ + ses.listeners = (struct Listener**)m_malloc(sizeof(struct Listener*)); + ses.listensize = 1; + ses.listeners[0] = NULL; + +} + +void set_listener_fds(fd_set * readfds) { + + unsigned int i, j; + struct Listener *listener; + + /* check each in turn */ + for (i = 0; i < ses.listensize; i++) { + listener = ses.listeners[i]; + if (listener != NULL) { + for (j = 0; j < listener->nsocks; j++) { + FD_SET(listener->socks[j], readfds); + } + } + } +} + + +void handle_listeners(const fd_set * readfds) { + + unsigned int i, j; + struct Listener *listener; + int sock; + + /* check each in turn */ + for (i = 0; i < ses.listensize; i++) { + listener = ses.listeners[i]; + if (listener != NULL) { + for (j = 0; j < listener->nsocks; j++) { + sock = listener->socks[j]; + if (FD_ISSET(sock, readfds)) { + listener->acceptor(listener, sock); + } + } + } + } +} /* Woo brace matching */ + + +/* acceptor(int fd, void* typedata) is a function to accept connections, + * cleanup(void* typedata) happens when cleaning up */ +struct Listener* new_listener(const int socks[], unsigned int nsocks, + int type, void* typedata, + void (*acceptor)(const struct Listener* listener, int sock), + void (*cleanup)(const struct Listener*)) { + + unsigned int i, j; + struct Listener *newlisten = NULL; + /* try get a new structure to hold it */ + for (i = 0; i < ses.listensize; i++) { + if (ses.listeners[i] == NULL) { + break; + } + } + + /* or create a new one */ + if (i == ses.listensize) { + if (ses.listensize > MAX_LISTENERS) { + TRACE(("leave newlistener: too many already")) + for (j = 0; j < nsocks; j++) { + close(socks[i]); + } + return NULL; + } + + ses.listeners = (struct Listener**)m_realloc(ses.listeners, + (ses.listensize+LISTENER_EXTEND_SIZE) + *sizeof(struct Listener*)); + + ses.listensize += LISTENER_EXTEND_SIZE; + + for (j = i; j < ses.listensize; j++) { + ses.listeners[j] = NULL; + } + } + + for (j = 0; j < nsocks; j++) { + ses.maxfd = MAX(ses.maxfd, socks[j]); + } + + TRACE(("new listener num %d ", i)) + + newlisten = (struct Listener*)m_malloc(sizeof(struct Listener)); + newlisten->index = i; + newlisten->type = type; + newlisten->typedata = typedata; + newlisten->nsocks = nsocks; + memcpy(newlisten->socks, socks, nsocks * sizeof(int)); + newlisten->acceptor = acceptor; + newlisten->cleanup = cleanup; + + ses.listeners[i] = newlisten; + return newlisten; +} + +/* Return the first listener which matches the type-specific comparison + * function. Particularly needed for global requests, like tcp */ +struct Listener * get_listener(int type, const void* typedata, + int (*match)(const void*, const void*)) { + + unsigned int i; + struct Listener* listener; + + for (i = 0, listener = ses.listeners[i]; i < ses.listensize; i++) { + if (listener && listener->type == type + && match(typedata, listener->typedata)) { + return listener; + } + } + + return NULL; +} + +void remove_listener(struct Listener* listener) { + + unsigned int j; + + if (listener->cleanup) { + listener->cleanup(listener); + } + + for (j = 0; j < listener->nsocks; j++) { + close(listener->socks[j]); + } + ses.listeners[listener->index] = NULL; + m_free(listener); +} + +void remove_all_listeners(void) { + unsigned int i; + for (i = 0; i < ses.listensize; i++) { + if (ses.listeners[i]) { + remove_listener(ses.listeners[i]); + } + } + m_free(ses.listeners); +} diff --git a/src/listener.h b/src/listener.h new file mode 100644 index 0000000..4a7f5ff --- /dev/null +++ b/src/listener.h @@ -0,0 +1,65 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_LISTENER_H +#define DROPBEAR_LISTENER_H + +#define MAX_LISTENERS 20 +#define LISTENER_EXTEND_SIZE 1 + +struct Listener { + + int socks[DROPBEAR_MAX_SOCKS]; + unsigned int nsocks; + + int index; /* index in the array of listeners */ + + void (*acceptor)(const struct Listener*, int sock); + void (*cleanup)(const struct Listener*); + + int type; /* CHANNEL_ID_X11, CHANNEL_ID_AGENT, + CHANNEL_ID_TCPDIRECT (for clients), + CHANNEL_ID_TCPFORWARDED (for servers) */ + + void *typedata; + +}; + +void listeners_initialise(void); +void handle_listeners(const fd_set * readfds); +void set_listener_fds(fd_set * readfds); + +struct Listener* new_listener(const int socks[], unsigned int nsocks, + int type, void* typedata, + void (*acceptor)(const struct Listener* listener, int sock), + void (*cleanup)(const struct Listener*)); + +struct Listener * get_listener(int type, const void* typedata, + int (*match)(const void*, const void*)); + +void remove_listener(struct Listener* listener); + +void remove_all_listeners(void); + +#endif /* DROPBEAR_LISTENER_H */ diff --git a/src/loginrec.c b/src/loginrec.c new file mode 100644 index 0000000..b543bcb --- /dev/null +++ b/src/loginrec.c @@ -0,0 +1,1380 @@ +/* + * Copyright (c) 2000 Andre Lucas. All rights reserved. + * Portions copyright (c) 1998 Todd C. Miller + * Portions copyright (c) 1996 Jason Downs + * Portions copyright (c) 1996 Theo de Raadt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + ** loginrec.c: platform-independent login recording and lastlog retrieval + **/ + +/* For now lastlog code has been removed as it wasn't being used by Dropbear. */ + +/* + The new login code explained + ============================ + + This code attempts to provide a common interface to login recording + (utmp and friends) and last login time retrieval. + + Its primary means of achieving this is to use 'struct logininfo', a + union of all the useful fields in the various different types of + system login record structures one finds on UNIX variants. + + We depend on autoconf to define which recording methods are to be + used, and which fields are contained in the relevant data structures + on the local system. Many C preprocessor symbols affect which code + gets compiled here. + + The code is designed to make it easy to modify a particular + recording method, without affecting other methods nor requiring so + many nested conditional compilation blocks as were commonplace in + the old code. + + For login recording, we try to use the local system's libraries as + these are clearly most likely to work correctly. For utmp systems + this usually means login() and logout() or setutent() etc., probably + in libutil, along with logwtmp() etc. On these systems, we fall back + to writing the files directly if we have to, though this method + requires very thorough testing so we do not corrupt local auditing + information. These files and their access methods are very system + specific indeed. + + For utmpx systems, the corresponding library functions are + setutxent() etc. To the author's knowledge, all utmpx systems have + these library functions and so no direct write is attempted. If such + a system exists and needs support, direct analogues of the [uw]tmp + code should suffice. + + Retrieving the time of last login ('lastlog') is in some ways even + more problemmatic than login recording. Some systems provide a + simple table of all users which we seek based on uid and retrieve a + relatively standard structure. Others record the same information in + a directory with a separate file, and others don't record the + information separately at all. For systems in the latter category, + we look backwards in the wtmp or wtmpx file for the last login entry + for our user. Naturally this is slower and on busy systems could + incur a significant performance penalty. + + Calling the new code + -------------------- + + In OpenSSH all login recording and retrieval is performed in + login.c. Here you'll find working examples. Also, in the logintest.c + program there are more examples. + + Internal handler calling method + ------------------------------- + + When a call is made to login_login() or login_logout(), both + routines set a struct logininfo flag defining which action (log in, + or log out) is to be taken. They both then call login_write(), which + calls whichever of the many structure-specific handlers autoconf + selects for the local system. + + The handlers themselves handle system data structure specifics. Both + struct utmp and struct utmpx have utility functions (see + construct_utmp*()) to try to make it simpler to add extra systems + that introduce new features to either structure. + + While it may seem terribly wasteful to replicate so much similar + code for each method, experience has shown that maintaining code to + write both struct utmp and utmpx in one function, whilst maintaining + support for all systems whether they have library support or not, is + a difficult and time-consuming task. + + Lastlog support proceeds similarly. Functions login_get_lastlog() + (and its OpenSSH-tuned friend login_get_lastlog_time()) call + getlast_entry(), which tries one of three methods to find the last + login time. It uses local system lastlog support if it can, + otherwise it tries wtmp or wtmpx before giving up and returning 0, + meaning "tilt". + + Maintenance + ----------- + + In many cases it's possible to tweak autoconf to select the correct + methods for a particular platform, either by improving the detection + code (best), or by presetting DISABLE_<method> or CONF_<method>_FILE + symbols for the platform. + + Use logintest to check which symbols are defined before modifying + configure.ac and loginrec.c. (You have to build logintest yourself + with 'make logintest' as it's not built by default.) + + Otherwise, patches to the specific method(s) are very helpful! + +*/ + +/** + ** TODO: + ** homegrown ttyslot() + ** test, test, test + ** + ** Platform status: + ** ---------------- + ** + ** Known good: + ** Linux (Redhat 6.2, Debian) + ** Solaris + ** HP-UX 10.20 (gcc only) + ** IRIX + ** NeXT - M68k/HPPA/Sparc (4.2/3.3) + ** + ** Testing required: Please send reports! + ** NetBSD + ** HP-UX 11 + ** AIX + ** + ** Platforms with known problems: + ** Some variants of Slackware Linux + ** + **/ + + +#include "includes.h" +#include "loginrec.h" +#include "dbutil.h" +#include "atomicio.h" + +/** + ** prototypes for helper functions in this file + **/ + +#if HAVE_UTMP_H +void set_utmp_time(struct logininfo *li, struct utmp *ut); +void construct_utmp(struct logininfo *li, struct utmp *ut); +#endif + +#ifdef HAVE_UTMPX_H +void set_utmpx_time(struct logininfo *li, struct utmpx *ut); +void construct_utmpx(struct logininfo *li, struct utmpx *ut); +#endif + +int utmp_write_entry(struct logininfo *li); +int utmpx_write_entry(struct logininfo *li); +int wtmp_write_entry(struct logininfo *li); +int wtmpx_write_entry(struct logininfo *li); +int lastlog_write_entry(struct logininfo *li); +int syslogin_write_entry(struct logininfo *li); + +int wtmp_get_entry(struct logininfo *li); +int wtmpx_get_entry(struct logininfo *li); + +/* pick the shortest string */ +#define MIN_SIZEOF(s1,s2) ( sizeof(s1) < sizeof(s2) ? sizeof(s1) : sizeof(s2) ) + +/** + ** platform-independent login functions + **/ + +/* login_login(struct logininfo *) -Record a login + * + * Call with a pointer to a struct logininfo initialised with + * login_init_entry() or login_alloc_entry() + * + * Returns: + * >0 if successful + * 0 on failure (will use OpenSSH's logging facilities for diagnostics) + */ +int +login_login (struct logininfo *li) +{ + li->type = LTYPE_LOGIN; + return login_write(li); +} + + +/* login_logout(struct logininfo *) - Record a logout + * + * Call as with login_login() + * + * Returns: + * >0 if successful + * 0 on failure (will use OpenSSH's logging facilities for diagnostics) + */ +int +login_logout(struct logininfo *li) +{ + li->type = LTYPE_LOGOUT; + return login_write(li); +} + + +/* login_alloc_entry(int, char*, char*, char*) - Allocate and initialise + * a logininfo structure + * + * This function creates a new struct logininfo, a data structure + * meant to carry the information required to portably record login info. + * + * Returns a pointer to a newly created struct logininfo. If memory + * allocation fails, the program halts. + */ +struct +logininfo *login_alloc_entry(int pid, const char *username, + const char *hostname, const char *line) +{ + struct logininfo *newli; + + newli = (struct logininfo *) m_malloc (sizeof(*newli)); + (void)login_init_entry(newli, pid, username, hostname, line); + return newli; +} + + +/* login_free_entry(struct logininfo *) - free struct memory */ +void +login_free_entry(struct logininfo *li) +{ + m_free(li); +} + + +/* login_init_entry(struct logininfo *, int, char*, char*, char*) + * - initialise a struct logininfo + * + * Populates a new struct logininfo, a data structure meant to carry + * the information required to portably record login info. + * + * Returns: 1 + */ +int +login_init_entry(struct logininfo *li, int pid, const char *username, + const char *hostname, const char *line) +{ + struct passwd *pw; + + memset(li, 0, sizeof(*li)); + + li->pid = pid; + + /* set the line information */ + if (line) + line_fullname(li->line, line, sizeof(li->line)); + + if (username) { + strlcpy(li->username, username, sizeof(li->username)); + pw = getpwnam(li->username); + if (pw == NULL) + dropbear_exit("login_init_entry: Cannot find user \"%s\"", + li->username); + li->uid = pw->pw_uid; + } + + if (hostname) + strlcpy(li->hostname, hostname, sizeof(li->hostname)); + + return 1; +} + +/* login_set_current_time(struct logininfo *) - set the current time + * + * Set the current time in a logininfo structure. This function is + * meant to eliminate the need to deal with system dependencies for + * time handling. + */ +void +login_set_current_time(struct logininfo *li) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + li->tv_sec = tv.tv_sec; + li->tv_usec = tv.tv_usec; +} + +/** + ** login_write: Call low-level recording functions based on autoconf + ** results + **/ +int +login_write (struct logininfo *li) +{ +#ifndef HAVE_CYGWIN + if ((int)geteuid() != 0) { + return 1; + } +#endif + + /* set the timestamp */ + login_set_current_time(li); +#ifdef USE_LOGIN + syslogin_write_entry(li); +#endif +#ifdef USE_LASTLOG + if (li->type == LTYPE_LOGIN) { + lastlog_write_entry(li); + } +#endif +#ifdef USE_UTMP + utmp_write_entry(li); +#endif +#ifdef USE_WTMP + wtmp_write_entry(li); +#endif +#ifdef USE_UTMPX + utmpx_write_entry(li); +#endif +#ifdef USE_WTMPX + wtmpx_write_entry(li); +#endif + return 0; +} + +#ifdef LOGIN_NEEDS_UTMPX +int +login_utmp_only(struct logininfo *li) +{ + li->type = LTYPE_LOGIN; + login_set_current_time(li); +# ifdef USE_UTMP + utmp_write_entry(li); +# endif +# ifdef USE_WTMP + wtmp_write_entry(li); +# endif +# ifdef USE_UTMPX + utmpx_write_entry(li); +# endif +# ifdef USE_WTMPX + wtmpx_write_entry(li); +# endif + return 0; +} +#endif + + + +/* + * 'line' string utility functions + * + * These functions process the 'line' string into one of three forms: + * + * 1. The full filename (including '/dev') + * 2. The stripped name (excluding '/dev') + * 3. The abbreviated name (e.g. /dev/ttyp00 -> yp00 + * /dev/pts/1 -> ts/1 ) + * + * Form 3 is used on some systems to identify a .tmp.? entry when + * attempting to remove it. Typically both addition and removal is + * performed by one application - say, sshd - so as long as the choice + * uniquely identifies a terminal it's ok. + */ + + +/* line_fullname(): add the leading '/dev/' if it doesn't exist make + * sure dst has enough space, if not just copy src (ugh) */ +char * +line_fullname(char *dst, const char *src, size_t dstsize) +{ + memset(dst, '\0', dstsize); + if ((strncmp(src, "/dev/", 5) == 0) || (dstsize < (strlen(src) + 5))) { + strlcpy(dst, src, dstsize); + } else { + strlcpy(dst, "/dev/", dstsize); + strlcat(dst, src, dstsize); + } + return dst; +} + +/* line_stripname(): strip the leading '/dev' if it exists, return dst */ +char * +line_stripname(char *dst, const char *src, size_t dstsize) +{ + memset(dst, '\0', dstsize); + if (strncmp(src, "/dev/", 5) == 0) + strlcpy(dst, src + 5, dstsize); + else + strlcpy(dst, src, dstsize); + return dst; +} + +/* line_abbrevname(): Return the abbreviated (usually four-character) + * form of the line (Just use the last <dstsize> characters of the + * full name.) + * + * NOTE: use strncpy because we do NOT necessarily want zero + * termination */ +char * +line_abbrevname(char *dst, const char *src, size_t dstsize) +{ + size_t len; + + memset(dst, '\0', dstsize); + + /* Always skip prefix if present */ + if (strncmp(src, "/dev/", 5) == 0) + src += 5; + +#ifdef WITH_ABBREV_NO_TTY + if (strncmp(src, "tty", 3) == 0) + src += 3; +#endif + + len = strlen(src); + + if (len > 0) { + if (((int)len - dstsize) > 0) + src += ((int)len - dstsize); + + /* note: _don't_ change this to strlcpy */ + strncpy(dst, src, (size_t)dstsize); + } + + return dst; +} + +/** + ** utmp utility functions + ** + ** These functions manipulate struct utmp, taking system differences + ** into account. + **/ + +#if defined(USE_UTMP) || defined (USE_WTMP) || defined (USE_LOGIN) + +/* build the utmp structure */ +void +set_utmp_time(struct logininfo *li, struct utmp *ut) +{ + /* struct utmp in glibc isn't y2038 safe yet */ +# ifdef HAVE_STRUCT_UTMP_UT_TV + ut->ut_tv.tv_sec = li->tv_sec; + ut->ut_tv.tv_usec = li->tv_usec; +# else +# ifdef HAVE_STRUCT_UTMP_UT_TIME + ut->ut_time = li->tv_sec; +# endif +# endif +} + +void +construct_utmp(struct logininfo *li, + struct utmp *ut) +{ +# ifdef HAVE_ADDR_V6_IN_UTMP + struct sockaddr_in6 *sa6; +# endif + memset(ut, '\0', sizeof(*ut)); + + /* First fill out fields used for both logins and logouts */ + +# ifdef HAVE_STRUCT_UTMP_UT_ID + line_abbrevname(ut->ut_id, li->line, sizeof(ut->ut_id)); +# endif + +# ifdef HAVE_STRUCT_UTMP_UT_TYPE + /* This is done here to keep utmp constants out of struct logininfo */ + switch (li->type) { + case LTYPE_LOGIN: + ut->ut_type = USER_PROCESS; +#ifdef _UNICOS + cray_set_tmpdir(ut); +#endif + break; + case LTYPE_LOGOUT: + ut->ut_type = DEAD_PROCESS; +#ifdef _UNICOS + cray_retain_utmp(ut, li->pid); +#endif + break; + } +# endif + set_utmp_time(li, ut); + + line_stripname(ut->ut_line, li->line, sizeof(ut->ut_line)); + +# ifdef HAVE_STRUCT_UTMP_UT_PID + ut->ut_pid = li->pid; +# endif + + /* If we're logging out, leave all other fields blank */ + if (li->type == LTYPE_LOGOUT) + return; + + /* + * These fields are only used when logging in, and are blank + * for logouts. + */ + + /* Use strncpy because we don't necessarily want null termination */ + strncpy(ut->ut_name, li->username, MIN_SIZEOF(ut->ut_name, li->username)); +# ifdef HAVE_STRUCT_UTMP_UT_HOST + strncpy(ut->ut_host, li->hostname, MIN_SIZEOF(ut->ut_host, li->hostname)); +# endif +# ifdef HAVE_STRUCT_UTMP_UT_ADDR + /* this is just a 32-bit IP address */ + if (li->hostaddr.sa.sa_family == AF_INET) + ut->ut_addr = li->hostaddr.sa_in.sin_addr.s_addr; +# endif +# ifdef HAVE_ADDR_V6_IN_UTMP + /* this is just a 128-bit IPv6 address */ + if (li->hostaddr.sa.sa_family == AF_INET6) { + sa6 = ((struct sockaddr_in6 *)&li->hostaddr.sa); + memcpy(ut->ut_addr_v6, sa6->sin6_addr.s6_addr, 16); + if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) { + ut->ut_addr_v6[0] = ut->ut_addr_v6[3]; + ut->ut_addr_v6[1] = 0; + ut->ut_addr_v6[2] = 0; + ut->ut_addr_v6[3] = 0; + } + } +# endif +} +#endif /* USE_UTMP || USE_WTMP || USE_LOGIN */ + +/** + ** utmpx utility functions + ** + ** These functions manipulate struct utmpx, accounting for system + ** variations. + **/ + +#if defined(USE_UTMPX) || defined (USE_WTMPX) +/* build the utmpx structure */ +void +set_utmpx_time(struct logininfo *li, struct utmpx *utx) +{ +# ifdef HAVE_STRUCT_UTMPX_UT_TV + utx->ut_tv.tv_sec = li->tv_sec; + utx->ut_tv.tv_usec = li->tv_usec; +# else /* HAVE_STRUCT_UTMPX_UT_TV */ +# ifdef HAVE_STRUCT_UTMPX_UT_TIME + utx->ut_time = li->tv_sec; +# endif /* HAVE_STRUCT_UTMPX_UT_TIME */ +# endif /* HAVE_STRUCT_UTMPX_UT_TV */ +} + +void +construct_utmpx(struct logininfo *li, struct utmpx *utx) +{ +# ifdef HAVE_ADDR_V6_IN_UTMP + struct sockaddr_in6 *sa6; +# endif + memset(utx, '\0', sizeof(*utx)); +# ifdef HAVE_STRUCT_UTMPX_UT_ID + line_abbrevname(utx->ut_id, li->line, sizeof(utx->ut_id)); +# endif + + /* this is done here to keep utmp constants out of loginrec.h */ + switch (li->type) { + case LTYPE_LOGIN: + utx->ut_type = USER_PROCESS; + break; + case LTYPE_LOGOUT: + utx->ut_type = DEAD_PROCESS; + break; + } + line_stripname(utx->ut_line, li->line, sizeof(utx->ut_line)); + set_utmpx_time(li, utx); + utx->ut_pid = li->pid; + /* strncpy(): Don't necessarily want null termination */ + strncpy(utx->ut_name, li->username, MIN_SIZEOF(utx->ut_name, li->username)); + + if (li->type == LTYPE_LOGOUT) + return; + + /* + * These fields are only used when logging in, and are blank + * for logouts. + */ + +# ifdef HAVE_STRUCT_UTMPX_UT_HOST + strncpy(utx->ut_host, li->hostname, MIN_SIZEOF(utx->ut_host, li->hostname)); +# endif +# ifdef HAVE_STRUCT_UTMPX_UT_ADDR + /* this is just a 32-bit IP address */ + if (li->hostaddr.sa.sa_family == AF_INET) + utx->ut_addr = li->hostaddr.sa_in.sin_addr.s_addr; +# endif +# ifdef HAVE_ADDR_V6_IN_UTMP + /* this is just a 128-bit IPv6 address */ + if (li->hostaddr.sa.sa_family == AF_INET6) { + sa6 = ((struct sockaddr_in6 *)&li->hostaddr.sa); + memcpy(ut->ut_addr_v6, sa6->sin6_addr.s6_addr, 16); + if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) { + ut->ut_addr_v6[0] = ut->ut_addr_v6[3]; + ut->ut_addr_v6[1] = 0; + ut->ut_addr_v6[2] = 0; + ut->ut_addr_v6[3] = 0; + } + } +# endif +# ifdef HAVE_STRUCT_UTMPX_UT_SYSLEN + /* ut_syslen is the length of the utx_host string */ + utx->ut_syslen = MIN(strlen(li->hostname), sizeof(utx->ut_host)); +# endif +} +#endif /* USE_UTMPX || USE_WTMPX */ + +/** + ** Low-level utmp functions + **/ + +/* FIXME: (ATL) utmp_write_direct needs testing */ +#ifdef USE_UTMP + +/* if we can, use pututline() etc. */ +# if !defined(DISABLE_PUTUTLINE) && defined(HAVE_SETUTENT) && \ + defined(HAVE_PUTUTLINE) +# define UTMP_USE_LIBRARY +# endif + + +/* write a utmp entry with the system's help (pututline() and pals) */ +# ifdef UTMP_USE_LIBRARY +static int +utmp_write_library(struct logininfo *li, struct utmp *ut) +{ + setutent(); + pututline(ut); + +# ifdef HAVE_ENDUTENT + endutent(); +# endif + return 1; +} +# else /* UTMP_USE_LIBRARY */ + +/* write a utmp entry direct to the file */ +/* This is a slightly modification of code in OpenBSD's login.c */ +static int +utmp_write_direct(struct logininfo *li, struct utmp *ut) +{ + struct utmp old_ut; + register int fd; + int tty; + + /* FIXME: (ATL) ttyslot() needs local implementation */ + +#if defined(HAVE_GETTTYENT) + register struct ttyent *ty; + + tty=0; + + setttyent(); + while ((struct ttyent *)0 != (ty = getttyent())) { + tty++; + if (!strncmp(ty->ty_name, ut->ut_line, sizeof(ut->ut_line))) + break; + } + endttyent(); + + if((struct ttyent *)0 == ty) { + dropbear_log(LOG_WARNING, "utmp_write_entry: tty not found"); + return(1); + } +#else /* FIXME */ + + tty = ttyslot(); /* seems only to work for /dev/ttyp? style names */ + +#endif /* HAVE_GETTTYENT */ + + if (tty > 0 && (fd = open(UTMP_FILE, O_RDWR|O_CREAT, 0644)) >= 0) { + (void)lseek(fd, (off_t)(tty * sizeof(struct utmp)), SEEK_SET); + /* + * Prevent luser from zero'ing out ut_host. + * If the new ut_line is empty but the old one is not + * and ut_line and ut_name match, preserve the old ut_line. + */ + if (atomicio(read, fd, &old_ut, sizeof(old_ut)) == sizeof(old_ut) && + (ut->ut_host[0] == '\0') && (old_ut.ut_host[0] != '\0') && + (strncmp(old_ut.ut_line, ut->ut_line, sizeof(ut->ut_line)) == 0) && + (strncmp(old_ut.ut_name, ut->ut_name, sizeof(ut->ut_name)) == 0)) { + (void)memcpy(ut->ut_host, old_ut.ut_host, sizeof(ut->ut_host)); + } + + (void)lseek(fd, (off_t)(tty * sizeof(struct utmp)), SEEK_SET); + if (atomicio(vwrite, fd, ut, sizeof(*ut)) != sizeof(*ut)) + dropbear_log(LOG_WARNING, "utmp_write_direct: error writing %s: %s", + UTMP_FILE, strerror(errno)); + + (void)close(fd); + return 1; + } else { + return 0; + } +} +# endif /* UTMP_USE_LIBRARY */ + +static int +utmp_perform_login(struct logininfo *li) +{ + struct utmp ut; + + construct_utmp(li, &ut); +# ifdef UTMP_USE_LIBRARY + if (!utmp_write_library(li, &ut)) { + dropbear_log(LOG_WARNING, "utmp_perform_login: utmp_write_library() failed"); + return 0; + } +# else + if (!utmp_write_direct(li, &ut)) { + dropbear_log(LOG_WARNING, "utmp_perform_login: utmp_write_direct() failed"); + return 0; + } +# endif + return 1; +} + + +static int +utmp_perform_logout(struct logininfo *li) +{ + struct utmp ut; + + construct_utmp(li, &ut); +# ifdef UTMP_USE_LIBRARY + if (!utmp_write_library(li, &ut)) { + dropbear_log(LOG_WARNING, "utmp_perform_logout: utmp_write_library() failed"); + return 0; + } +# else + if (!utmp_write_direct(li, &ut)) { + dropbear_log(LOG_WARNING, "utmp_perform_logout: utmp_write_direct() failed"); + return 0; + } +# endif + return 1; +} + + +int +utmp_write_entry(struct logininfo *li) +{ + switch(li->type) { + case LTYPE_LOGIN: + return utmp_perform_login(li); + + case LTYPE_LOGOUT: + return utmp_perform_logout(li); + + default: + dropbear_log(LOG_WARNING, "utmp_write_entry: invalid type field"); + return 0; + } +} +#endif /* USE_UTMP */ + + +/** + ** Low-level utmpx functions + **/ + +/* not much point if we don't want utmpx entries */ +#ifdef USE_UTMPX + +/* if we have the wherewithall, use pututxline etc. */ +# if !defined(DISABLE_PUTUTXLINE) && defined(HAVE_SETUTXENT) && \ + defined(HAVE_PUTUTXLINE) +# define UTMPX_USE_LIBRARY +# endif + + +/* write a utmpx entry with the system's help (pututxline() and pals) */ +# ifdef UTMPX_USE_LIBRARY +static int +utmpx_write_library(struct logininfo *li, struct utmpx *utx) +{ + setutxent(); + pututxline(utx); + +# ifdef HAVE_ENDUTXENT + endutxent(); +# endif + return 1; +} + +# else /* UTMPX_USE_LIBRARY */ + +/* write a utmp entry direct to the file */ +static int +utmpx_write_direct(struct logininfo *li, struct utmpx *utx) +{ + dropbear_log(LOG_WARNING, "utmpx_write_direct: not implemented!"); + return 0; +} +# endif /* UTMPX_USE_LIBRARY */ + +static int +utmpx_perform_login(struct logininfo *li) +{ + struct utmpx utx; + + construct_utmpx(li, &utx); +# ifdef UTMPX_USE_LIBRARY + if (!utmpx_write_library(li, &utx)) { + dropbear_log(LOG_WARNING, "utmpx_perform_login: utmp_write_library() failed"); + return 0; + } +# else + if (!utmpx_write_direct(li, &utx)) { + dropbear_log(LOG_WARNING, "utmpx_perform_login: utmp_write_direct() failed"); + return 0; + } +# endif + return 1; +} + + +static int +utmpx_perform_logout(struct logininfo *li) +{ + struct utmpx utx; + + construct_utmpx(li, &utx); +# ifdef HAVE_STRUCT_UTMPX_UT_ID + line_abbrevname(utx.ut_id, li->line, sizeof(utx.ut_id)); +# endif +# ifdef HAVE_STRUCT_UTMPX_UT_TYPE + utx.ut_type = DEAD_PROCESS; +# endif + +# ifdef UTMPX_USE_LIBRARY + utmpx_write_library(li, &utx); +# else + utmpx_write_direct(li, &utx); +# endif + return 1; +} + +int +utmpx_write_entry(struct logininfo *li) +{ + switch(li->type) { + case LTYPE_LOGIN: + return utmpx_perform_login(li); + case LTYPE_LOGOUT: + return utmpx_perform_logout(li); + default: + dropbear_log(LOG_WARNING, "utmpx_write_entry: invalid type field"); + return 0; + } +} +#endif /* USE_UTMPX */ + + +/** + ** Low-level wtmp functions + **/ + +#ifdef USE_WTMP + +/* write a wtmp entry direct to the end of the file */ +/* This is a slight modification of code in OpenBSD's logwtmp.c */ +static int +wtmp_write(struct logininfo *li, struct utmp *ut) +{ + struct stat buf; + int fd, ret = 1; + + if ((fd = open(WTMP_FILE, O_WRONLY|O_APPEND, 0)) < 0) { + dropbear_log(LOG_WARNING, "wtmp_write: problem writing %s: %s", + WTMP_FILE, strerror(errno)); + return 0; + } + if (fstat(fd, &buf) == 0) + if (atomicio(vwrite, fd, ut, sizeof(*ut)) != sizeof(*ut)) { + ftruncate(fd, buf.st_size); + dropbear_log(LOG_WARNING, "wtmp_write: problem writing %s: %s", + WTMP_FILE, strerror(errno)); + ret = 0; + } + (void)close(fd); + return ret; +} + +static int +wtmp_perform_login(struct logininfo *li) +{ + struct utmp ut; + + construct_utmp(li, &ut); + return wtmp_write(li, &ut); +} + + +static int +wtmp_perform_logout(struct logininfo *li) +{ + struct utmp ut; + + construct_utmp(li, &ut); + return wtmp_write(li, &ut); +} + + +int +wtmp_write_entry(struct logininfo *li) +{ + switch(li->type) { + case LTYPE_LOGIN: + return wtmp_perform_login(li); + case LTYPE_LOGOUT: + return wtmp_perform_logout(li); + default: + dropbear_log(LOG_WARNING, "wtmp_write_entry: invalid type field"); + return 0; + } +} + + +/* Notes on fetching login data from wtmp/wtmpx + * + * Logouts are usually recorded with (amongst other things) a blank + * username on a given tty line. However, some systems (HP-UX is one) + * leave all fields set, but change the ut_type field to DEAD_PROCESS. + * + * Since we're only looking for logins here, we know that the username + * must be set correctly. On systems that leave it in, we check for + * ut_type==USER_PROCESS (indicating a login.) + * + * Portability: Some systems may set something other than USER_PROCESS + * to indicate a login process. I don't know of any as I write. Also, + * it's possible that some systems may both leave the username in + * place and not have ut_type. + */ + +/* return true if this wtmp entry indicates a login */ +static int +wtmp_islogin(struct logininfo *li, struct utmp *ut) +{ + if (strncmp(li->username, ut->ut_name, + MIN_SIZEOF(li->username, ut->ut_name)) == 0) { +# ifdef HAVE_STRUCT_UTMP_UT_TYPE + if (ut->ut_type & USER_PROCESS) + return 1; +# else + return 1; +# endif + } + return 0; +} + +int +wtmp_get_entry(struct logininfo *li) +{ + struct stat st; + struct utmp ut; + int fd, found=0; + + /* Clear the time entries in our logininfo */ + li->tv_sec = li->tv_usec = 0; + + if ((fd = open(WTMP_FILE, O_RDONLY)) < 0) { + dropbear_log(LOG_WARNING, "wtmp_get_entry: problem opening %s: %s", + WTMP_FILE, strerror(errno)); + return 0; + } + if (fstat(fd, &st) != 0) { + dropbear_log(LOG_WARNING, "wtmp_get_entry: couldn't stat %s: %s", + WTMP_FILE, strerror(errno)); + close(fd); + return 0; + } + + /* Seek to the start of the last struct utmp */ + if (lseek(fd, -(off_t)sizeof(struct utmp), SEEK_END) == -1) { + /* Looks like we've got a fresh wtmp file */ + close(fd); + return 0; + } + + while (!found) { + if (atomicio(read, fd, &ut, sizeof(ut)) != sizeof(ut)) { + dropbear_log(LOG_WARNING, "wtmp_get_entry: read of %s failed: %s", + WTMP_FILE, strerror(errno)); + close (fd); + return 0; + } + if ( wtmp_islogin(li, &ut) ) { + found = 1; + /* We've already checked for a time in struct + * utmp, in login_getlast(). */ +# ifdef HAVE_STRUCT_UTMP_UT_TIME + li->tv_sec = ut.ut_time; +# else +# if HAVE_STRUCT_UTMP_UT_TV + li->tv_sec = ut.ut_tv.tv_sec; +# endif +# endif + line_fullname(li->line, ut.ut_line, + MIN_SIZEOF(li->line, ut.ut_line)); +# ifdef HAVE_STRUCT_UTMP_UT_HOST + strlcpy(li->hostname, ut.ut_host, + MIN_SIZEOF(li->hostname, ut.ut_host)); +# endif + continue; + } + /* Seek back 2 x struct utmp */ + if (lseek(fd, -(off_t)(2 * sizeof(struct utmp)), SEEK_CUR) == -1) { + /* We've found the start of the file, so quit */ + close (fd); + return 0; + } + } + + /* We found an entry. Tidy up and return */ + close(fd); + return 1; +} +# endif /* USE_WTMP */ + + +/** + ** Low-level wtmpx functions + **/ + +#ifdef USE_WTMPX +/* write a wtmpx entry direct to the end of the file */ +/* This is a slight modification of code in OpenBSD's logwtmp.c */ +static int +wtmpx_write(struct logininfo *li, struct utmpx *utx) +{ + struct stat buf; + int fd, ret = 1; + + if ((fd = open(WTMPX_FILE, O_WRONLY|O_APPEND, 0)) < 0) { + dropbear_log(LOG_WARNING, "wtmpx_write: problem opening %s: %s", + WTMPX_FILE, strerror(errno)); + return 0; + } + + if (fstat(fd, &buf) == 0) + if (atomicio(vwrite, fd, utx, sizeof(*utx)) != sizeof(*utx)) { + ftruncate(fd, buf.st_size); + dropbear_log(LOG_WARNING, "wtmpx_write: problem writing %s: %s", + WTMPX_FILE, strerror(errno)); + ret = 0; + } + (void)close(fd); + + return ret; +} + + +static int +wtmpx_perform_login(struct logininfo *li) +{ + struct utmpx utx; + + construct_utmpx(li, &utx); + return wtmpx_write(li, &utx); +} + + +static int +wtmpx_perform_logout(struct logininfo *li) +{ + struct utmpx utx; + + construct_utmpx(li, &utx); + return wtmpx_write(li, &utx); +} + + +int +wtmpx_write_entry(struct logininfo *li) +{ + switch(li->type) { + case LTYPE_LOGIN: + return wtmpx_perform_login(li); + case LTYPE_LOGOUT: + return wtmpx_perform_logout(li); + default: + dropbear_log(LOG_WARNING, "wtmpx_write_entry: invalid type field"); + return 0; + } +} + +/* Please see the notes above wtmp_islogin() for information about the + next two functions */ + +/* Return true if this wtmpx entry indicates a login */ +static int +wtmpx_islogin(struct logininfo *li, struct utmpx *utx) +{ + if ( strncmp(li->username, utx->ut_name, + MIN_SIZEOF(li->username, utx->ut_name)) == 0 ) { +# ifdef HAVE_STRUCT_UTMPX_UT_TYPE + if (utx->ut_type == USER_PROCESS) + return 1; +# else + return 1; +# endif + } + return 0; +} + + +int +wtmpx_get_entry(struct logininfo *li) +{ + struct stat st; + struct utmpx utx; + int fd, found=0; + + /* Clear the time entries */ + li->tv_sec = li->tv_usec = 0; + + if ((fd = open(WTMPX_FILE, O_RDONLY)) < 0) { + dropbear_log(LOG_WARNING, "wtmpx_get_entry: problem opening %s: %s", + WTMPX_FILE, strerror(errno)); + return 0; + } + if (fstat(fd, &st) != 0) { + dropbear_log(LOG_WARNING, "wtmpx_get_entry: couldn't stat %s: %s", + WTMPX_FILE, strerror(errno)); + close(fd); + return 0; + } + + /* Seek to the start of the last struct utmpx */ + if (lseek(fd, -(off_t)sizeof(struct utmpx), SEEK_END) == -1 ) { + /* probably a newly rotated wtmpx file */ + close(fd); + return 0; + } + + while (!found) { + if (atomicio(read, fd, &utx, sizeof(utx)) != sizeof(utx)) { + dropbear_log(LOG_WARNING, "wtmpx_get_entry: read of %s failed: %s", + WTMPX_FILE, strerror(errno)); + close (fd); + return 0; + } + /* Logouts are recorded as a blank username on a particular line. + * So, we just need to find the username in struct utmpx */ + if ( wtmpx_islogin(li, &utx) ) { + found = 1; +# ifdef HAVE_STRUCT_UTMPX_UT_TV + li->tv_sec = utx.ut_tv.tv_sec; +# else +# ifdef HAVE_STRUCT_UTMPX_UT_TIME + li->tv_sec = utx.ut_time; +# endif +# endif + line_fullname(li->line, utx.ut_line, sizeof(li->line)); +# ifdef HAVE_STRUCT_UTMPX_UT_HOST + strlcpy(li->hostname, utx.ut_host, + MIN_SIZEOF(li->hostname, utx.ut_host)); +# endif + continue; + } + if (lseek(fd, -(off_t)(2 * sizeof(struct utmpx)), SEEK_CUR) == -1) { + close (fd); + return 0; + } + } + + close(fd); + return 1; +} +#endif /* USE_WTMPX */ + +/** + ** Low-level libutil login() functions + **/ + +#ifdef USE_LOGIN +static int +syslogin_perform_login(struct logininfo *li) +{ + struct utmp *ut; + + if (! (ut = (struct utmp *)malloc(sizeof(*ut)))) { + dropbear_log(LOG_WARNING, "syslogin_perform_login: couldn't malloc()"); + return 0; + } + construct_utmp(li, ut); + login(ut); + free(ut); + + return 1; +} + +static int +syslogin_perform_logout(struct logininfo *li) +{ +# ifdef HAVE_LOGOUT + char line[8]; + + (void)line_stripname(line, li->line, sizeof(line)); + + if (!logout(line)) { + dropbear_log(LOG_WARNING, "syslogin_perform_logout: logout(%s) returned an error: %s", line, strerror(errno)); +# ifdef HAVE_LOGWTMP + } else { + logwtmp(line, "", ""); +# endif + } + /* FIXME: (ATL - if the need arises) What to do if we have + * login, but no logout? what if logout but no logwtmp? All + * routines are in libutil so they should all be there, + * but... */ +# endif + return 1; +} + +int +syslogin_write_entry(struct logininfo *li) +{ + switch (li->type) { + case LTYPE_LOGIN: + return syslogin_perform_login(li); + case LTYPE_LOGOUT: + return syslogin_perform_logout(li); + default: + dropbear_log(LOG_WARNING, "syslogin_write_entry: Invalid type field"); + return 0; + } +} +#endif /* USE_LOGIN */ + +/* end of file log-syslogin.c */ + +/** + ** Low-level lastlog functions + **/ + +#ifdef USE_LASTLOG +#define LL_FILE 1 +#define LL_DIR 2 +#define LL_OTHER 3 + +static void +lastlog_construct(struct logininfo *li, struct lastlog *last) +{ + /* clear the structure */ + memset(last, '\0', sizeof(*last)); + + (void)line_stripname(last->ll_line, li->line, sizeof(last->ll_line)); + strlcpy(last->ll_host, li->hostname, + MIN_SIZEOF(last->ll_host, li->hostname)); + /* struct lastlog in glibc isn't y2038 safe yet */ + last->ll_time = li->tv_sec; +} + +static int +lastlog_filetype(char *filename) +{ + struct stat st; + + if (stat(filename, &st) != 0) { + dropbear_log(LOG_WARNING, "lastlog_perform_login: Couldn't stat %s: %s", filename, + strerror(errno)); + return 0; + } + if (S_ISDIR(st.st_mode)) + return LL_DIR; + else if (S_ISREG(st.st_mode)) + return LL_FILE; + else + return LL_OTHER; +} + + +/* open the file (using filemode) and seek to the login entry */ +static int +lastlog_openseek(struct logininfo *li, int *fd, int filemode) +{ + off_t offset; + int type; + char lastlog_file[1024]; + + type = lastlog_filetype(LASTLOG_FILE); + switch (type) { + case LL_FILE: + strlcpy(lastlog_file, LASTLOG_FILE, sizeof(lastlog_file)); + break; + case LL_DIR: + snprintf(lastlog_file, sizeof(lastlog_file), "%s/%s", + LASTLOG_FILE, li->username); + break; + default: + dropbear_log(LOG_WARNING, "lastlog_openseek: %.100s is not a file or directory!", + LASTLOG_FILE); + return 0; + } + + *fd = open(lastlog_file, filemode, 0600); + if ( *fd < 0) { + dropbear_log(LOG_INFO, "lastlog_openseek: Couldn't open %s: %s", + lastlog_file, strerror(errno)); + return 0; + } + + if (type == LL_FILE) { + /* find this uid's offset in the lastlog file */ + offset = (off_t) ((long)li->uid * sizeof(struct lastlog)); + + if ( lseek(*fd, offset, SEEK_SET) != offset ) { + dropbear_log(LOG_WARNING, "lastlog_openseek: %s->lseek(): %s", + lastlog_file, strerror(errno)); + m_close(*fd); + return 0; + } + } + + return 1; +} + +static int +lastlog_perform_login(struct logininfo *li) +{ + struct lastlog last; + int fd; + + /* create our struct lastlog */ + lastlog_construct(li, &last); + + if (!lastlog_openseek(li, &fd, O_RDWR|O_CREAT)) + return(0); + + /* write the entry */ + if (atomicio(vwrite, fd, &last, sizeof(last)) != sizeof(last)) { + close(fd); + dropbear_log(LOG_WARNING, "lastlog_write_filemode: Error writing to %s: %s", + LASTLOG_FILE, strerror(errno)); + return 0; + } + + close(fd); + return 1; +} + +int +lastlog_write_entry(struct logininfo *li) +{ + switch(li->type) { + case LTYPE_LOGIN: + return lastlog_perform_login(li); + default: + dropbear_log(LOG_WARNING, "lastlog_write_entry: Invalid type field"); + return 0; + } +} + +#endif /* USE_LASTLOG */ diff --git a/src/loginrec.h b/src/loginrec.h new file mode 100644 index 0000000..6abde48 --- /dev/null +++ b/src/loginrec.h @@ -0,0 +1,181 @@ +#ifndef DROPBEAR_HAVE_LOGINREC_H_ +#define DROPBEAR_HAVE_LOGINREC_H_ + +/* + * Copyright (c) 2000 Andre Lucas. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + ** loginrec.h: platform-independent login recording and lastlog retrieval + **/ + +#include "includes.h" + +/* RCSID("Id: loginrec.h,v 1.2 2004/05/04 10:17:43 matt Exp "); */ + +/* The following #defines are from OpenSSH's defines.h, required for loginrec */ + +/* FIXME: put default paths back in */ +#ifndef UTMP_FILE +# ifdef _PATH_UTMP +# define UTMP_FILE _PATH_UTMP +# else +# ifdef CONF_UTMP_FILE +# define UTMP_FILE CONF_UTMP_FILE +# endif +# endif +#endif +#ifndef WTMP_FILE +# ifdef _PATH_WTMP +# define WTMP_FILE _PATH_WTMP +# else +# ifdef CONF_WTMP_FILE +# define WTMP_FILE CONF_WTMP_FILE +# endif +# endif +#endif +/* pick up the user's location for lastlog if given */ +#ifndef LASTLOG_FILE +# ifdef _PATH_LASTLOG +# define LASTLOG_FILE _PATH_LASTLOG +# else +# ifdef CONF_LASTLOG_FILE +# define LASTLOG_FILE CONF_LASTLOG_FILE +# endif +# endif +#endif + + +/* The login() library function in libutil is first choice */ +#if defined(HAVE_LOGIN) && !defined(DISABLE_LOGIN) +# define USE_LOGIN + +#else +/* Simply select your favourite login types. */ +/* Can't do if-else because some systems use several... <sigh> */ +# if defined(HAVE_UTMPX_H) && defined(UTMPX_FILE) && !defined(DISABLE_UTMPX) +# define USE_UTMPX +# endif +# if defined(HAVE_UTMP_H) && defined(UTMP_FILE) && !defined(DISABLE_UTMP) +# define USE_UTMP +# endif +# if defined(WTMPX_FILE) && !defined(DISABLE_WTMPX) +# define USE_WTMPX +# endif +# if defined(WTMP_FILE) && !defined(DISABLE_WTMP) +# define USE_WTMP +# endif + +#endif + +/* I hope that the presence of LASTLOG_FILE is enough to detect this */ +#if defined(LASTLOG_FILE) && !defined(DISABLE_LASTLOG) +# define USE_LASTLOG +#endif + + +/** + ** you should use the login_* calls to work around platform dependencies + **/ + +/* + * login_netinfo structure + */ + +union login_netinfo { + struct sockaddr sa; + struct sockaddr_in sa_in; +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE + struct sockaddr_storage sa_storage; +#endif +}; + +/* + * * logininfo structure * + */ +/* types - different to utmp.h 'type' macros */ +/* (though set to the same value as linux, openbsd and others...) */ +#define LTYPE_LOGIN 7 +#define LTYPE_LOGOUT 8 + +/* string lengths - set very long */ +#define LINFO_PROGSIZE 64 +#define LINFO_LINESIZE 64 +#define LINFO_NAMESIZE 64 +#define LINFO_HOSTSIZE 256 + +struct logininfo { + char progname[LINFO_PROGSIZE]; /* name of program (for PAM) */ + int progname_null; + short int type; /* type of login (LTYPE_*) */ + int pid; /* PID of login process */ + int uid; /* UID of this user */ + char line[LINFO_LINESIZE]; /* tty/pty name */ + char username[LINFO_NAMESIZE]; /* login username */ + char hostname[LINFO_HOSTSIZE]; /* remote hostname */ + /* 'exit_status' structure components */ + int exit; /* process exit status */ + int termination; /* process termination status */ + /* struct timeval (sys/time.h) isn't always available, if it isn't we'll + * use time_t's value as tv_sec and set tv_usec to 0 + */ + time_t tv_sec; + suseconds_t tv_usec; + union login_netinfo hostaddr; /* caller's host address(es) */ +}; /* struct logininfo */ + +/* + * login recording functions + */ + +/** 'public' functions */ + +struct logininfo *login_alloc_entry(int pid, const char *username, + const char *hostname, const char *line); +/* free a structure */ +void login_free_entry(struct logininfo *li); +/* fill out a pre-allocated structure with useful information */ +int login_init_entry(struct logininfo *li, int pid, const char *username, + const char *hostname, const char *line); +/* place the current time in a logininfo struct */ +void login_set_current_time(struct logininfo *li); + +/* record the entry */ +int login_login (struct logininfo *li); +int login_logout(struct logininfo *li); +#ifdef LOGIN_NEEDS_UTMPX +int login_utmp_only(struct logininfo *li); +#endif + +/** End of public functions */ + +/* record the entry */ +int login_write (struct logininfo *li); +int login_log_entry(struct logininfo *li); + +/* produce various forms of the line filename */ +char *line_fullname(char *dst, const char *src, size_t dstsize); +char *line_stripname(char *dst, const char *src, size_t dstsize); +char *line_abbrevname(char *dst, const char *src, size_t dstsize); + +#endif /* DROPBEAR_HAVE_LOGINREC_H_ */ diff --git a/src/ltc_prng.c b/src/ltc_prng.c new file mode 100644 index 0000000..4f2e9e1 --- /dev/null +++ b/src/ltc_prng.c @@ -0,0 +1,136 @@ +/* Copied from libtomcrypt/src/prngs/sprng.c and modified to + * use Dropbear's genrandom(). */ + +/* LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tomstdenis@gmail.com, http://libtomcrypt.com + */ +#include "includes.h" +#include "dbrandom.h" +#include "ltc_prng.h" + +/** + @file sprng.c + Secure PRNG, Tom St Denis +*/ + +/* A secure PRNG using the RNG functions. Basically this is a + * wrapper that allows you to use a secure RNG as a PRNG + * in the various other functions. + */ + +#if DROPBEAR_LTC_PRNG + +/** + Start the PRNG + @param prng [out] The PRNG state to initialize + @return CRYPT_OK if successful +*/ +int dropbear_prng_start(prng_state* UNUSED(prng)) +{ + return CRYPT_OK; +} + +/** + Add entropy to the PRNG state + @param in The data to add + @param inlen Length of the data to add + @param prng PRNG state to update + @return CRYPT_OK if successful +*/ +int dropbear_prng_add_entropy(const unsigned char* UNUSED(in), unsigned long UNUSED(inlen), prng_state* UNUSED(prng)) +{ + return CRYPT_OK; +} + +/** + Make the PRNG ready to read from + @param prng The PRNG to make active + @return CRYPT_OK if successful +*/ +int dropbear_prng_ready(prng_state* UNUSED(prng)) +{ + return CRYPT_OK; +} + +/** + Read from the PRNG + @param out Destination + @param outlen Length of output + @param prng The active PRNG to read from + @return Number of octets read +*/ +unsigned long dropbear_prng_read(unsigned char* out, unsigned long outlen, prng_state* UNUSED(prng)) +{ + LTC_ARGCHK(out != NULL); + genrandom(out, outlen); + return outlen; +} + +/** + Terminate the PRNG + @param prng The PRNG to terminate + @return CRYPT_OK if successful +*/ +int dropbear_prng_done(prng_state* UNUSED(prng)) +{ + return CRYPT_OK; +} + +/** + Export the PRNG state + @param out [out] Destination + @param outlen [in/out] Max size and resulting size of the state + @param prng The PRNG to export + @return CRYPT_OK if successful +*/ +int dropbear_prng_export(unsigned char* UNUSED(out), unsigned long* outlen, prng_state* UNUSED(prng)) +{ + LTC_ARGCHK(outlen != NULL); + + *outlen = 0; + return CRYPT_OK; +} + +/** + Import a PRNG state + @param in The PRNG state + @param inlen Size of the state + @param prng The PRNG to import + @return CRYPT_OK if successful +*/ +int dropbear_prng_import(const unsigned char* UNUSED(in), unsigned long UNUSED(inlen), prng_state* UNUSED(prng)) +{ + return CRYPT_OK; +} + +/** + PRNG self-test + @return CRYPT_OK if successful, CRYPT_NOP if self-testing has been disabled +*/ +int dropbear_prng_test(void) +{ + return CRYPT_OK; +} + +const struct ltc_prng_descriptor dropbear_prng_desc = +{ + "dropbear_prng", 0, + dropbear_prng_start, + dropbear_prng_add_entropy, + dropbear_prng_ready, + dropbear_prng_read, + dropbear_prng_done, + dropbear_prng_export, + dropbear_prng_import, + dropbear_prng_test +}; + + +#endif /* DROPBEAR_LTC_PRNG */ diff --git a/src/ltc_prng.h b/src/ltc_prng.h new file mode 100644 index 0000000..6bc8273 --- /dev/null +++ b/src/ltc_prng.h @@ -0,0 +1,12 @@ +#ifndef DROPBEAR_LTC_PRNG_H_DROPBEAR +#define DROPBEAR_LTC_PRNG_H_DROPBEAR + +#include "includes.h" + +#if DROPBEAR_LTC_PRNG + +extern const struct ltc_prng_descriptor dropbear_prng_desc; + +#endif /* DROPBEAR_LTC_PRNG */ + +#endif /* DROPBEAR_LTC_PRNG_H_DROPBEAR */ diff --git a/src/netio.c b/src/netio.c new file mode 100644 index 0000000..b8aebea --- /dev/null +++ b/src/netio.c @@ -0,0 +1,699 @@ +#include "netio.h" +#include "list.h" +#include "dbutil.h" +#include "session.h" +#include "debug.h" +#include "runopts.h" + +struct dropbear_progress_connection { + struct addrinfo *res; + struct addrinfo *res_iter; + + char *remotehost, *remoteport; /* For error reporting */ + + connect_callback cb; + void *cb_data; + + struct Queue *writequeue; /* A queue of encrypted packets to send with TCP fastopen, + or NULL. */ + + int sock; + + char* errstring; + char *bind_address, *bind_port; + enum dropbear_prio prio; +}; + +/* Deallocate a progress connection. Removes from the pending list if iter!=NULL. +Does not close sockets */ +static void remove_connect(struct dropbear_progress_connection *c, m_list_elem *iter) { + if (c->res) { + freeaddrinfo(c->res); + } + m_free(c->remotehost); + m_free(c->remoteport); + m_free(c->errstring); + m_free(c->bind_address); + m_free(c->bind_port); + m_free(c); + + if (iter) { + list_remove(iter); + } +} + +static void cancel_callback(int result, int sock, void* UNUSED(data), const char* UNUSED(errstring)) { + if (result == DROPBEAR_SUCCESS) + { + m_close(sock); + } +} + +void cancel_connect(struct dropbear_progress_connection *c) { + c->cb = cancel_callback; + c->cb_data = NULL; +} + +static void connect_try_next(struct dropbear_progress_connection *c) { + struct addrinfo *r; + int err; + int res = 0; + int fastopen = 0; +#if DROPBEAR_CLIENT_TCP_FAST_OPEN + struct msghdr message; +#endif + + for (r = c->res_iter; r; r = r->ai_next) + { + dropbear_assert(c->sock == -1); + + c->sock = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if (c->sock < 0) { + continue; + } + + if (c->bind_address || c->bind_port) { + /* bind to a source port/address */ + struct addrinfo hints; + struct addrinfo *bindaddr = NULL; + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = r->ai_family; + hints.ai_flags = AI_PASSIVE; + + err = getaddrinfo(c->bind_address, c->bind_port, &hints, &bindaddr); + if (err) { + int len = 100 + strlen(gai_strerror(err)); + m_free(c->errstring); + c->errstring = (char*)m_malloc(len); + snprintf(c->errstring, len, "Error resolving bind address '%s' (port %s). %s", + c->bind_address, c->bind_port, gai_strerror(err)); + TRACE(("Error resolving bind: %s", gai_strerror(err))) + close(c->sock); + c->sock = -1; + continue; + } + res = bind(c->sock, bindaddr->ai_addr, bindaddr->ai_addrlen); + freeaddrinfo(bindaddr); + bindaddr = NULL; + if (res < 0) { + /* failure */ + int keep_errno = errno; + int len = 300; + m_free(c->errstring); + c->errstring = m_malloc(len); + snprintf(c->errstring, len, "Error binding local address '%s' (port %s). %s", + c->bind_address, c->bind_port, strerror(keep_errno)); + close(c->sock); + c->sock = -1; + continue; + } + } + + ses.maxfd = MAX(ses.maxfd, c->sock); + set_sock_nodelay(c->sock); + set_sock_priority(c->sock, c->prio); + setnonblocking(c->sock); + +#if DROPBEAR_CLIENT_TCP_FAST_OPEN + fastopen = (c->writequeue != NULL); + + if (fastopen) { + memset(&message, 0x0, sizeof(message)); + message.msg_name = r->ai_addr; + message.msg_namelen = r->ai_addrlen; + /* 6 is arbitrary, enough to hold initial packets */ + unsigned int iovlen = 6; /* Linux msg_iovlen is a size_t */ + struct iovec iov[6]; + packet_queue_to_iovec(c->writequeue, iov, &iovlen); + message.msg_iov = iov; + message.msg_iovlen = iovlen; + res = sendmsg(c->sock, &message, MSG_FASTOPEN); + /* Returns EINPROGRESS if FASTOPEN wasn't available */ + if (res < 0) { + if (errno != EINPROGRESS) { + m_free(c->errstring); + c->errstring = m_strdup(strerror(errno)); + /* Not entirely sure which kind of errors are normal - 2.6.32 seems to + return EPIPE for any (nonblocking?) sendmsg(). just fall back */ + TRACE(("sendmsg tcp_fastopen failed, falling back. %s", strerror(errno))); + /* No kernel MSG_FASTOPEN support. Fall back below */ + fastopen = 0; + /* Set to NULL to avoid trying again */ + c->writequeue = NULL; + } + } else { + packet_queue_consume(c->writequeue, res); + } + } +#endif + + /* Normal connect(), used as fallback for TCP fastopen too */ + if (!fastopen) { + res = connect(c->sock, r->ai_addr, r->ai_addrlen); + } + + if (res < 0 && errno != EINPROGRESS) { + /* failure */ + m_free(c->errstring); + c->errstring = m_strdup(strerror(errno)); + close(c->sock); + c->sock = -1; + continue; + } else { + /* new connection was successful, wait for it to complete */ + break; + } + } + + if (r) { + c->res_iter = r->ai_next; + } else { + c->res_iter = NULL; + } +} + +/* Connect via TCP to a host. */ +struct dropbear_progress_connection *connect_remote(const char* remotehost, const char* remoteport, + connect_callback cb, void* cb_data, + const char* bind_address, const char* bind_port, enum dropbear_prio prio) +{ + struct dropbear_progress_connection *c = NULL; + int err; + struct addrinfo hints; + + c = m_malloc(sizeof(*c)); + c->remotehost = m_strdup(remotehost); + c->remoteport = m_strdup(remoteport); + c->sock = -1; + c->cb = cb; + c->cb_data = cb_data; + c->prio = prio; + + list_append(&ses.conn_pending, c); + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + c->errstring = m_strdup("fuzzing connect_remote always fails"); + return c; + } +#endif + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + + err = getaddrinfo(remotehost, remoteport, &hints, &c->res); + if (err) { + int len; + len = 100 + strlen(gai_strerror(err)); + c->errstring = (char*)m_malloc(len); + snprintf(c->errstring, len, "Error resolving '%s' port '%s'. %s", + remotehost, remoteport, gai_strerror(err)); + TRACE(("Error resolving: %s", gai_strerror(err))) + } else { + c->res_iter = c->res; + } + + if (bind_address) { + c->bind_address = m_strdup(bind_address); + } + if (bind_port) { + c->bind_port = m_strdup(bind_port); + } + + return c; +} + +void remove_connect_pending() { + while (ses.conn_pending.first) { + struct dropbear_progress_connection *c = ses.conn_pending.first->item; + remove_connect(c, ses.conn_pending.first); + } +} + + +void set_connect_fds(fd_set *writefd) { + m_list_elem *iter; + iter = ses.conn_pending.first; + while (iter) { + m_list_elem *next_iter = iter->next; + struct dropbear_progress_connection *c = iter->item; + /* Set one going */ + while (c->res_iter && c->sock < 0) { + connect_try_next(c); + } + if (c->sock >= 0) { + FD_SET(c->sock, writefd); + } else { + /* Final failure */ + if (!c->errstring) { + c->errstring = m_strdup("unexpected failure"); + } + c->cb(DROPBEAR_FAILURE, -1, c->cb_data, c->errstring); + remove_connect(c, iter); + } + iter = next_iter; + } +} + +void handle_connect_fds(const fd_set *writefd) { + m_list_elem *iter; + for (iter = ses.conn_pending.first; iter; iter = iter->next) { + int val; + socklen_t vallen = sizeof(val); + struct dropbear_progress_connection *c = iter->item; + + if (c->sock < 0 || !FD_ISSET(c->sock, writefd)) { + continue; + } + + TRACE(("handling %s port %s socket %d", c->remotehost, c->remoteport, c->sock)); + + if (getsockopt(c->sock, SOL_SOCKET, SO_ERROR, &val, &vallen) != 0) { + TRACE(("handle_connect_fds getsockopt(%d) SO_ERROR failed: %s", c->sock, strerror(errno))) + /* This isn't expected to happen - Unix has surprises though, continue gracefully. */ + m_close(c->sock); + c->sock = -1; + } else if (val != 0) { + /* Connect failed */ + TRACE(("connect to %s port %s failed.", c->remotehost, c->remoteport)) + m_close(c->sock); + c->sock = -1; + + m_free(c->errstring); + c->errstring = m_strdup(strerror(val)); + } else { + /* New connection has been established */ + c->cb(DROPBEAR_SUCCESS, c->sock, c->cb_data, NULL); + remove_connect(c, iter); + TRACE(("leave handle_connect_fds - success")) + /* Must return here - remove_connect() invalidates iter */ + return; + } + } +} + +void connect_set_writequeue(struct dropbear_progress_connection *c, struct Queue *writequeue) { + c->writequeue = writequeue; +} + +void packet_queue_to_iovec(const struct Queue *queue, struct iovec *iov, unsigned int *iov_count) { + struct Link *l; + unsigned int i; + int len; + buffer *writebuf; + +#ifndef IOV_MAX + #if defined(__CYGWIN__) && !defined(UIO_MAXIOV) + #define IOV_MAX 1024 + #elif defined(__sgi) + #define IOV_MAX 512 + #else + #define IOV_MAX UIO_MAXIOV + #endif +#endif + + *iov_count = MIN(MIN(queue->count, IOV_MAX), *iov_count); + + for (l = queue->head, i = 0; i < *iov_count; l = l->link, i++) + { + writebuf = (buffer*)l->item; + len = writebuf->len - writebuf->pos; + dropbear_assert(len > 0); + TRACE2(("write_packet writev #%d len %d/%d", i, + len, writebuf->len)) + iov[i].iov_base = buf_getptr(writebuf, len); + iov[i].iov_len = len; + } +} + +void packet_queue_consume(struct Queue *queue, ssize_t written) { + buffer *writebuf; + int len; + while (written > 0) { + writebuf = (buffer*)examine(queue); + len = writebuf->len - writebuf->pos; + if (len > written) { + /* partial buffer write */ + buf_incrpos(writebuf, written); + written = 0; + } else { + written -= len; + dequeue(queue); + buf_free(writebuf); + } + } +} + +void set_sock_nodelay(int sock) { + int val; + + /* disable nagle */ + val = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&val, sizeof(val)); +} + +#if DROPBEAR_SERVER_TCP_FAST_OPEN +void set_listen_fast_open(int sock) { + int qlen = MAX(MAX_UNAUTH_PER_IP, 5); + if (setsockopt(sock, SOL_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen)) != 0) { + TRACE(("set_listen_fast_open failed for socket %d: %s", sock, strerror(errno))) + } +} + +#endif + +void set_sock_priority(int sock, enum dropbear_prio prio) { + + int rc; + int val; + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + TRACE(("fuzzing skips set_sock_prio")) + return; + } +#endif + /* Don't log ENOTSOCK errors so that this can harmlessly be called + * on a client '-J' proxy pipe */ + + if (opts.disable_ip_tos == 0) { +#ifdef IP_TOS + /* Set the DSCP field for outbound IP packet priority. + rfc4594 has some guidance to meanings. + + We set AF21 as "Low-Latency" class for interactive (tty session, + also handshake/setup packets). Other traffic is left at the default. + + OpenSSH at present uses AF21/CS1, rationale + https://cvsweb.openbsd.org/src/usr.bin/ssh/readconf.c#rev1.284 + + Old Dropbear/OpenSSH and Debian/Ubuntu OpenSSH (at Jan 2022) use + IPTOS_LOWDELAY/IPTOS_THROUGHPUT + + DSCP constants are from Linux headers, applicable to other platforms + such as macos. + */ + if (prio == DROPBEAR_PRIO_LOWDELAY) { + val = 0x48; /* IPTOS_DSCP_AF21 */ + } else { + val = 0; /* default */ + } +#if defined(IPPROTO_IPV6) && defined(IPV6_TCLASS) + rc = setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, (void*)&val, sizeof(val)); + if (rc < 0 && errno != ENOTSOCK) { + TRACE(("Couldn't set IPV6_TCLASS (%s)", strerror(errno))); + } +#endif + rc = setsockopt(sock, IPPROTO_IP, IP_TOS, (void*)&val, sizeof(val)); + if (rc < 0 && errno != ENOTSOCK) { + TRACE(("Couldn't set IP_TOS (%s)", strerror(errno))); + } +#endif /* IP_TOS */ + } + +#ifdef HAVE_LINUX_PKT_SCHED_H + /* Set scheduling priority within the local Linux network stack */ + if (prio == DROPBEAR_PRIO_LOWDELAY) { + val = TC_PRIO_INTERACTIVE; + } else { + val = 0; + } + /* linux specific, sets QoS class. see tc-prio(8) */ + rc = setsockopt(sock, SOL_SOCKET, SO_PRIORITY, (void*) &val, sizeof(val)); + if (rc < 0 && errno != ENOTSOCK) { + TRACE(("Couldn't set SO_PRIORITY (%s)", strerror(errno))) + } +#endif + +} + +/* from openssh/canohost.c avoid premature-optimization */ +int get_sock_port(int sock) { + struct sockaddr_storage from; + socklen_t fromlen; + char strport[NI_MAXSERV]; + int r; + + /* Get IP address of client. */ + fromlen = sizeof(from); + memset(&from, 0, sizeof(from)); + if (getsockname(sock, (struct sockaddr *)&from, &fromlen) < 0) { + TRACE(("getsockname failed: %d", errno)) + return 0; + } + + /* Work around Linux IPv6 weirdness */ + if (from.ss_family == AF_INET6) + fromlen = sizeof(struct sockaddr_in6); + + /* Non-inet sockets don't have a port number. */ + if (from.ss_family != AF_INET && from.ss_family != AF_INET6) + return 0; + + /* Return port number. */ + if ((r = getnameinfo((struct sockaddr *)&from, fromlen, NULL, 0, + strport, sizeof(strport), NI_NUMERICSERV)) != 0) { + TRACE(("netio.c/get_sock_port/getnameinfo NI_NUMERICSERV failed: %d", r)) + } + return atoi(strport); +} + +/* Listen on address:port. + * Special cases are address of "" listening on everything, + * and address of NULL listening on localhost only. + * Returns the number of sockets bound on success, or -1 on failure. On + * failure, if errstring wasn't NULL, it'll be a newly malloced error + * string.*/ +int dropbear_listen(const char* address, const char* port, + int *socks, unsigned int sockcount, char **errstring, int *maxfd) { + + struct addrinfo hints, *res = NULL, *res0 = NULL; + int err; + unsigned int nsock; + struct linger linger; + int val; + int sock; + uint16_t *allocated_lport_p = NULL; + int allocated_lport = 0; + + TRACE(("enter dropbear_listen")) + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + return fuzz_dropbear_listen(address, port, socks, sockcount, errstring, maxfd); + } +#endif + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* TODO: let them flag v4 only etc */ + hints.ai_socktype = SOCK_STREAM; + + /* for calling getaddrinfo: + address == NULL and !AI_PASSIVE: local loopback + address == NULL and AI_PASSIVE: all interfaces + address != NULL: whatever the address says */ + if (!address) { + TRACE(("dropbear_listen: local loopback")) + } else { + if (address[0] == '\0') { + TRACE(("dropbear_listen: all interfaces")) + address = NULL; + } + hints.ai_flags = AI_PASSIVE; + } + err = getaddrinfo(address, port, &hints, &res0); + + if (err) { + if (errstring != NULL && *errstring == NULL) { + int len; + len = 20 + strlen(gai_strerror(err)); + *errstring = (char*)m_malloc(len); + snprintf(*errstring, len, "Error resolving: %s", gai_strerror(err)); + } + if (res0) { + freeaddrinfo(res0); + res0 = NULL; + } + TRACE(("leave dropbear_listen: failed resolving")) + return -1; + } + + /* When listening on server-assigned-port 0 + * the assigned ports may differ for address families (v4/v6) + * causing problems for tcpip-forward. + * Caller can do a get_socket_address to discover assigned-port + * hence, use same port for all address families */ + allocated_lport = 0; + nsock = 0; + for (res = res0; res != NULL && nsock < sockcount; + res = res->ai_next) { + if (allocated_lport > 0) { + if (AF_INET == res->ai_family) { + allocated_lport_p = &((struct sockaddr_in *)res->ai_addr)->sin_port; + } else if (AF_INET6 == res->ai_family) { + allocated_lport_p = &((struct sockaddr_in6 *)res->ai_addr)->sin6_port; + } + *allocated_lport_p = htons(allocated_lport); + } + + /* Get a socket */ + socks[nsock] = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + sock = socks[nsock]; /* For clarity */ + if (sock < 0) { + err = errno; + TRACE(("socket() failed")) + continue; + } + + /* Various useful socket options */ + val = 1; + /* set to reuse, quick timeout */ + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &val, sizeof(val)); + linger.l_onoff = 1; + linger.l_linger = 5; + setsockopt(sock, SOL_SOCKET, SO_LINGER, (void*)&linger, sizeof(linger)); + +#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) + if (res->ai_family == AF_INET6) { + int on = 1; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + &on, sizeof(on)) == -1) { + dropbear_log(LOG_WARNING, "Couldn't set IPV6_V6ONLY"); + } + } +#endif + set_sock_nodelay(sock); + + if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { + err = errno; + close(sock); + TRACE(("bind(%s) failed", port)) + continue; + } + + if (listen(sock, DROPBEAR_LISTEN_BACKLOG) < 0) { + err = errno; + close(sock); + TRACE(("listen() failed")) + continue; + } + + if (0 == allocated_lport) { + allocated_lport = get_sock_port(sock); + } + + *maxfd = MAX(*maxfd, sock); + nsock++; + } + + if (res0) { + freeaddrinfo(res0); + res0 = NULL; + } + + if (nsock == 0) { + if (errstring != NULL && *errstring == NULL) { + int len; + len = 20 + strlen(strerror(err)); + *errstring = (char*)m_malloc(len); + snprintf(*errstring, len, "Error listening: %s", strerror(err)); + } + TRACE(("leave dropbear_listen: failure, %s", strerror(err))) + return -1; + } + + TRACE(("leave dropbear_listen: success, %d socks bound", nsock)) + return nsock; +} + +void get_socket_address(int fd, char **local_host, char **local_port, + char **remote_host, char **remote_port, int host_lookup) +{ + struct sockaddr_storage addr; + socklen_t addrlen; + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + fuzz_get_socket_address(fd, local_host, local_port, remote_host, remote_port, host_lookup); + return; + } +#endif + + if (local_host || local_port) { + addrlen = sizeof(addr); + if (getsockname(fd, (struct sockaddr*)&addr, &addrlen) < 0) { + dropbear_exit("Failed socket address: %s", strerror(errno)); + } + getaddrstring(&addr, local_host, local_port, host_lookup); + } + if (remote_host || remote_port) { + addrlen = sizeof(addr); + if (getpeername(fd, (struct sockaddr*)&addr, &addrlen) < 0) { + dropbear_exit("Failed socket address: %s", strerror(errno)); + } + getaddrstring(&addr, remote_host, remote_port, host_lookup); + } +} + +/* Return a string representation of the socket address passed. The return + * value is allocated with malloc() */ +void getaddrstring(struct sockaddr_storage* addr, + char **ret_host, char **ret_port, + int host_lookup) { + + char host[NI_MAXHOST+1], serv[NI_MAXSERV+1]; + unsigned int len; + int ret; + + int flags = NI_NUMERICSERV | NI_NUMERICHOST; + +#if !DO_HOST_LOOKUP + host_lookup = 0; +#endif + + if (host_lookup) { + flags = NI_NUMERICSERV; + } + + len = sizeof(struct sockaddr_storage); + /* Some platforms such as Solaris 8 require that len is the length + * of the specific structure. Some older linux systems (glibc 2.1.3 + * such as debian potato) have sockaddr_storage.__ss_family instead + * but we'll ignore them */ +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY + if (addr->ss_family == AF_INET) { + len = sizeof(struct sockaddr_in); + } +#ifdef AF_INET6 + if (addr->ss_family == AF_INET6) { + len = sizeof(struct sockaddr_in6); + } +#endif +#endif + + ret = getnameinfo((struct sockaddr*)addr, len, host, sizeof(host)-1, + serv, sizeof(serv)-1, flags); + + if (ret != 0) { + if (host_lookup) { + /* On some systems (Darwin does it) we get EINTR from getnameinfo + * somehow. Eew. So we'll just return the IP, since that doesn't seem + * to exhibit that behaviour. */ + getaddrstring(addr, ret_host, ret_port, 0); + return; + } else { + /* if we can't do a numeric lookup, something's gone terribly wrong */ + dropbear_exit("Failed lookup: %s", gai_strerror(ret)); + } + } + + if (ret_host) { + *ret_host = m_strdup(host); + } + if (ret_port) { + *ret_port = m_strdup(serv); + } +} + diff --git a/src/netio.h b/src/netio.h new file mode 100644 index 0000000..605512b --- /dev/null +++ b/src/netio.h @@ -0,0 +1,65 @@ +#ifndef DROPBEAR_NETIO_H +#define DROPBEAR_NETIO_H + +#include "includes.h" +#include "buffer.h" +#include "queue.h" + +enum dropbear_prio { + DROPBEAR_PRIO_NORMAL = 0, /* the rest - tcp-fwd, scp, rsync, git, etc */ + DROPBEAR_PRIO_LOWDELAY, /* pty shell, x11 */ +}; + +void set_sock_nodelay(int sock); +void set_sock_priority(int sock, enum dropbear_prio prio); + +int get_sock_port(int sock); +void get_socket_address(int fd, char **local_host, char **local_port, + char **remote_host, char **remote_port, int host_lookup); +void getaddrstring(struct sockaddr_storage* addr, + char **ret_host, char **ret_port, int host_lookup); +int dropbear_listen(const char* address, const char* port, + int *socks, unsigned int sockcount, char **errstring, int *maxfd); + +struct dropbear_progress_connection; + +/* result is DROPBEAR_SUCCESS or DROPBEAR_FAILURE. +errstring is only set on DROPBEAR_FAILURE, returns failure message for the last attempted socket */ +typedef void(*connect_callback)(int result, int sock, void* data, const char* errstring); + +/* Always returns a progress connection, if it fails it will call the callback at a later point */ +struct dropbear_progress_connection * connect_remote (const char* remotehost, const char* remoteport, + connect_callback cb, void *cb_data, const char* bind_address, const char* bind_port, + enum dropbear_prio prio); + +/* Sets up for select() */ +void set_connect_fds(fd_set *writefd); +/* Handles ready sockets after select() */ +void handle_connect_fds(const fd_set *writefd); +/* Cleanup */ +void remove_connect_pending(void); + +/* Doesn't actually stop the connect, but adds a dummy callback instead */ +void cancel_connect(struct dropbear_progress_connection *c); + +void connect_set_writequeue(struct dropbear_progress_connection *c, struct Queue *writequeue); + +/* TODO: writev #ifdef guard */ +/* Fills out iov which contains iov_count slots, returning the number filled in iov_count */ +void packet_queue_to_iovec(const struct Queue *queue, struct iovec *iov, unsigned int *iov_count); +void packet_queue_consume(struct Queue *queue, ssize_t written); + +#if DROPBEAR_SERVER_TCP_FAST_OPEN +/* Try for any Linux builds, will fall back if the kernel doesn't support it */ +void set_listen_fast_open(int sock); +/* Define values which may be supported by the kernel even if the libc is too old */ +#ifndef TCP_FASTOPEN +#define TCP_FASTOPEN 23 +#endif +#ifndef MSG_FASTOPEN +#define MSG_FASTOPEN 0x20000000 +#endif +#endif + +#endif + diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..c12cfc9 --- /dev/null +++ b/src/options.h @@ -0,0 +1,26 @@ +#ifndef DROPBEAR_OPTIONS_H +#define DROPBEAR_OPTIONS_H + +/* + > > > Don't edit this file any more! < < < + +Local compile-time configuration should be defined in localoptions.h +in the build directory. +See default_options.h.in for a description of the available options. +*/ + +/* Some configuration options or checks depend on system config */ +#include "config.h" + +#ifdef LOCALOPTIONS_H_EXISTS +#include "localoptions.h" +#endif + +/* default_options.h is processed to add #ifndef guards */ +#include "default_options_guard.h" + +/* Some other defines that mostly should be left alone are defined + * in sysoptions.h */ +#include "sysoptions.h" + +#endif /* DROPBEAR_OPTIONS_H */ diff --git a/src/packet.c b/src/packet.c new file mode 100644 index 0000000..1055588 --- /dev/null +++ b/src/packet.c @@ -0,0 +1,758 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "packet.h" +#include "session.h" +#include "dbutil.h" +#include "ssh.h" +#include "algo.h" +#include "buffer.h" +#include "kex.h" +#include "dbrandom.h" +#include "service.h" +#include "auth.h" +#include "channel.h" +#include "netio.h" +#include "runopts.h" + +static int read_packet_init(void); +static void make_mac(unsigned int seqno, const struct key_context_directional * key_state, + buffer * clear_buf, unsigned int clear_len, + unsigned char *output_mac); +static int checkmac(void); + +/* For exact details see http://www.zlib.net/zlib_tech.html + * 5 bytes per 16kB block, plus 6 bytes for the stream. + * We might allocate 5 unnecessary bytes here if it's an + * exact multiple. */ +#define ZLIB_COMPRESS_EXPANSION (((RECV_MAX_PAYLOAD_LEN/16384)+1)*5 + 6) +#define ZLIB_DECOMPRESS_INCR 1024 +#ifndef DISABLE_ZLIB +static buffer* buf_decompress(const buffer* buf, unsigned int len); +static void buf_compress(buffer * dest, buffer * src, unsigned int len); +#endif + +/* non-blocking function writing out a current encrypted packet */ +void write_packet() { + + ssize_t written; +#if defined(HAVE_WRITEV) && (defined(IOV_MAX) || defined(UIO_MAXIOV)) + /* 50 is somewhat arbitrary */ + unsigned int iov_count = 50; + struct iovec iov[50]; +#else + int len; + buffer* writebuf; +#endif + + TRACE2(("enter write_packet")) + dropbear_assert(!isempty(&ses.writequeue)); + +#if defined(HAVE_WRITEV) && (defined(IOV_MAX) || defined(UIO_MAXIOV)) + + packet_queue_to_iovec(&ses.writequeue, iov, &iov_count); + /* This may return EAGAIN. The main loop sometimes + calls write_packet() without bothering to test with select() since + it's likely to be necessary */ +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + /* pretend to write one packet at a time */ + /* TODO(fuzz): randomise amount written based on the fuzz input */ + written = iov[0].iov_len; + } + else +#endif + { + written = writev(ses.sock_out, iov, iov_count); + if (written < 0) { + if (errno == EINTR || errno == EAGAIN) { + TRACE2(("leave write_packet: EINTR")) + return; + } else { + dropbear_exit("Error writing: %s", strerror(errno)); + } + } + } + + packet_queue_consume(&ses.writequeue, written); + ses.writequeue_len -= written; + + if (written == 0) { + ses.remoteclosed(); + } + +#else /* No writev () */ +#if DROPBEAR_FUZZ + _Static_assert(0, "No fuzzing code for no-writev writes"); +#endif + /* Get the next buffer in the queue of encrypted packets to write*/ + writebuf = (buffer*)examine(&ses.writequeue); + + len = writebuf->len - writebuf->pos; + dropbear_assert(len > 0); + /* Try to write as much as possible */ + written = write(ses.sock_out, buf_getptr(writebuf, len), len); + + if (written < 0) { + if (errno == EINTR || errno == EAGAIN) { + TRACE2(("leave writepacket: EINTR")) + return; + } else { + dropbear_exit("Error writing: %s", strerror(errno)); + } + } + + if (written == 0) { + ses.remoteclosed(); + } + + ses.writequeue_len -= written; + + if (written == len) { + /* We've finished with the packet, free it */ + dequeue(&ses.writequeue); + buf_free(writebuf); + writebuf = NULL; + } else { + /* More packet left to write, leave it in the queue for later */ + buf_incrpos(writebuf, written); + } +#endif /* writev */ + + TRACE2(("leave write_packet")) +} + +/* Non-blocking function reading available portion of a packet into the + * ses's buffer, decrypting the length if encrypted, decrypting the + * full portion if possible */ +void read_packet() { + + int len; + unsigned int maxlen; + unsigned char blocksize; + + TRACE2(("enter read_packet")) + blocksize = ses.keys->recv.algo_crypt->blocksize; + + if (ses.readbuf == NULL || ses.readbuf->len < blocksize) { + int ret; + /* In the first blocksize of a packet */ + + /* Read the first blocksize of the packet, so we can decrypt it and + * find the length of the whole packet */ + ret = read_packet_init(); + + if (ret == DROPBEAR_FAILURE) { + /* didn't read enough to determine the length */ + TRACE2(("leave read_packet: packetinit done")) + return; + } + } + + /* Attempt to read the remainder of the packet, note that there + * mightn't be any available (EAGAIN) */ + maxlen = ses.readbuf->len - ses.readbuf->pos; + if (maxlen == 0) { + /* Occurs when the packet is only a single block long and has all + * been read in read_packet_init(). Usually means that MAC is disabled + */ + len = 0; + } else { + len = read(ses.sock_in, buf_getptr(ses.readbuf, maxlen), maxlen); + + if (len == 0) { + ses.remoteclosed(); + } + + if (len < 0) { + if (errno == EINTR || errno == EAGAIN) { + TRACE2(("leave read_packet: EINTR or EAGAIN")) + return; + } else { + dropbear_exit("Error reading: %s", strerror(errno)); + } + } + + buf_incrpos(ses.readbuf, len); + } + + if ((unsigned int)len == maxlen) { + /* The whole packet has been read */ + decrypt_packet(); + /* The main select() loop process_packet() to + * handle the packet contents... */ + } + TRACE2(("leave read_packet")) +} + +/* Function used to read the initial portion of a packet, and determine the + * length. Only called during the first BLOCKSIZE of a packet. */ +/* Returns DROPBEAR_SUCCESS if the length is determined, + * DROPBEAR_FAILURE otherwise */ +static int read_packet_init() { + + unsigned int maxlen; + int slen; + unsigned int len, plen; + unsigned int blocksize; + unsigned int macsize; + + + blocksize = ses.keys->recv.algo_crypt->blocksize; + macsize = ses.keys->recv.algo_mac->hashsize; + + if (ses.readbuf == NULL) { + /* start of a new packet */ + ses.readbuf = buf_new(INIT_READBUF); + } + + maxlen = blocksize - ses.readbuf->pos; + + /* read the rest of the packet if possible */ + slen = read(ses.sock_in, buf_getwriteptr(ses.readbuf, maxlen), + maxlen); + if (slen == 0) { + ses.remoteclosed(); + } + if (slen < 0) { + if (errno == EINTR || errno == EAGAIN) { + TRACE2(("leave read_packet_init: EINTR")) + return DROPBEAR_FAILURE; + } + dropbear_exit("Error reading: %s", strerror(errno)); + } + + buf_incrwritepos(ses.readbuf, slen); + + if ((unsigned int)slen != maxlen) { + /* don't have enough bytes to determine length, get next time */ + return DROPBEAR_FAILURE; + } + + /* now we have the first block, need to get packet length, so we decrypt + * the first block (only need first 4 bytes) */ + buf_setpos(ses.readbuf, 0); +#if DROPBEAR_AEAD_MODE + if (ses.keys->recv.crypt_mode->aead_crypt) { + if (ses.keys->recv.crypt_mode->aead_getlength(ses.recvseq, + buf_getptr(ses.readbuf, blocksize), &plen, + blocksize, + &ses.keys->recv.cipher_state) != CRYPT_OK) { + dropbear_exit("Error decrypting"); + } + len = plen + 4 + macsize; + } else +#endif + { + if (ses.keys->recv.crypt_mode->decrypt(buf_getptr(ses.readbuf, blocksize), + buf_getwriteptr(ses.readbuf, blocksize), + blocksize, + &ses.keys->recv.cipher_state) != CRYPT_OK) { + dropbear_exit("Error decrypting"); + } + plen = buf_getint(ses.readbuf) + 4; + len = plen + macsize; + } + + TRACE2(("packet size is %u, block %u mac %u", len, blocksize, macsize)) + + + /* check packet length */ + if ((len > RECV_MAX_PACKET_LEN) || + (plen < blocksize) || + (plen % blocksize != 0)) { + dropbear_exit("Integrity error (bad packet size %u)", len); + } + + if (len > ses.readbuf->size) { + ses.readbuf = buf_resize(ses.readbuf, len); + } + buf_setlen(ses.readbuf, len); + buf_setpos(ses.readbuf, blocksize); + return DROPBEAR_SUCCESS; +} + +/* handle the received packet */ +void decrypt_packet() { + + unsigned char blocksize; + unsigned char macsize; + unsigned int padlen; + unsigned int len; + + TRACE2(("enter decrypt_packet")) + blocksize = ses.keys->recv.algo_crypt->blocksize; + macsize = ses.keys->recv.algo_mac->hashsize; + + ses.kexstate.datarecv += ses.readbuf->len; + +#if DROPBEAR_AEAD_MODE + if (ses.keys->recv.crypt_mode->aead_crypt) { + /* first blocksize is not decrypted yet */ + buf_setpos(ses.readbuf, 0); + + /* decrypt it in-place */ + len = ses.readbuf->len - macsize - ses.readbuf->pos; + if (ses.keys->recv.crypt_mode->aead_crypt(ses.recvseq, + buf_getptr(ses.readbuf, len + macsize), + buf_getwriteptr(ses.readbuf, len), + len, macsize, + &ses.keys->recv.cipher_state, LTC_DECRYPT) != CRYPT_OK) { + dropbear_exit("Error decrypting"); + } + buf_incrpos(ses.readbuf, len); + } else +#endif + { + /* we've already decrypted the first blocksize in read_packet_init */ + buf_setpos(ses.readbuf, blocksize); + + /* decrypt it in-place */ + len = ses.readbuf->len - macsize - ses.readbuf->pos; + if (ses.keys->recv.crypt_mode->decrypt( + buf_getptr(ses.readbuf, len), + buf_getwriteptr(ses.readbuf, len), + len, + &ses.keys->recv.cipher_state) != CRYPT_OK) { + dropbear_exit("Error decrypting"); + } + buf_incrpos(ses.readbuf, len); + + /* check the hmac */ + if (checkmac() != DROPBEAR_SUCCESS) { + dropbear_exit("Integrity error"); + } + + } + +#if DROPBEAR_FUZZ + fuzz_dump(ses.readbuf->data, ses.readbuf->len); +#endif + + /* get padding length */ + buf_setpos(ses.readbuf, PACKET_PADDING_OFF); + padlen = buf_getbyte(ses.readbuf); + + /* payload length */ + /* - 4 - 1 is for LEN and PADLEN values */ + len = ses.readbuf->len - padlen - 4 - 1 - macsize; + if ((len > RECV_MAX_PAYLOAD_LEN+ZLIB_COMPRESS_EXPANSION) || (len < 1)) { + dropbear_exit("Bad packet size %u", len); + } + + buf_setpos(ses.readbuf, PACKET_PAYLOAD_OFF); + +#ifndef DISABLE_ZLIB + if (is_compress_recv()) { + /* decompress */ + ses.payload = buf_decompress(ses.readbuf, len); + buf_setpos(ses.payload, 0); + ses.payload_beginning = 0; + buf_free(ses.readbuf); + } else +#endif + { + ses.payload = ses.readbuf; + ses.payload_beginning = ses.payload->pos; + buf_setlen(ses.payload, ses.payload->pos + len); + } + ses.readbuf = NULL; + + ses.recvseq++; + + TRACE2(("leave decrypt_packet")) +} + +/* Checks the mac at the end of a decrypted readbuf. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int checkmac() { + + unsigned char mac_bytes[MAX_MAC_LEN]; + unsigned int mac_size, contents_len; + + mac_size = ses.keys->recv.algo_mac->hashsize; + contents_len = ses.readbuf->len - mac_size; + + buf_setpos(ses.readbuf, 0); + make_mac(ses.recvseq, &ses.keys->recv, ses.readbuf, contents_len, mac_bytes); + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + /* fail 1 in 2000 times to test error path. */ + unsigned int value = 0; + if (mac_size > sizeof(value)) { + memcpy(&value, mac_bytes, sizeof(value)); + } + if (value % 2000 == 99) { + return DROPBEAR_FAILURE; + } + return DROPBEAR_SUCCESS; + } +#endif + + /* compare the hash */ + buf_setpos(ses.readbuf, contents_len); + if (constant_time_memcmp(mac_bytes, buf_getptr(ses.readbuf, mac_size), mac_size) != 0) { + return DROPBEAR_FAILURE; + } else { + return DROPBEAR_SUCCESS; + } +} + +#ifndef DISABLE_ZLIB +/* returns a pointer to a newly created buffer */ +static buffer* buf_decompress(const buffer* buf, unsigned int len) { + + int result; + buffer * ret; + z_streamp zstream; + + zstream = ses.keys->recv.zstream; + /* We use RECV_MAX_PAYLOAD_LEN+1 here to ensure that + we can detect an oversized payload after inflate() */ + ret = buf_new(RECV_MAX_PAYLOAD_LEN+1); + + zstream->avail_in = len; + zstream->next_in = buf_getptr(buf, len); + zstream->avail_out = ret->size; + zstream->next_out = ret->data; + + result = inflate(zstream, Z_SYNC_FLUSH); + if (result != Z_OK) { + dropbear_exit("zlib error"); + } + + buf_setlen(ret, ret->size - zstream->avail_out); + + if (zstream->avail_in > 0 || ret->len > RECV_MAX_PAYLOAD_LEN) { + /* The remote side sent larger than a payload size + * of uncompressed data. + */ + dropbear_exit("bad packet, oversized decompressed"); + } + + /* Success. All input was consumed and avail_out > 0 */ + return ret; + +} +#endif + + +/* returns 1 if the packet is a valid type during kex (see 7.1 of rfc4253) */ +static int packet_is_okay_kex(unsigned char type) { + if (type >= SSH_MSG_USERAUTH_REQUEST) { + return 0; + } + if (type == SSH_MSG_SERVICE_REQUEST || type == SSH_MSG_SERVICE_ACCEPT) { + return 0; + } + if (type == SSH_MSG_KEXINIT) { + /* XXX should this die horribly if !dataallowed ?? */ + return 0; + } + return 1; +} + +static void enqueue_reply_packet() { + struct packetlist * new_item = NULL; + new_item = m_malloc(sizeof(struct packetlist)); + new_item->next = NULL; + + new_item->payload = buf_newcopy(ses.writepayload); + buf_setpos(ses.writepayload, 0); + buf_setlen(ses.writepayload, 0); + + if (ses.reply_queue_tail) { + ses.reply_queue_tail->next = new_item; + } else { + ses.reply_queue_head = new_item; + } + ses.reply_queue_tail = new_item; +} + +void maybe_flush_reply_queue() { + struct packetlist *tmp_item = NULL, *curr_item = NULL; + if (!ses.dataallowed) + { + TRACE(("maybe_empty_reply_queue - no data allowed")) + return; + } + + for (curr_item = ses.reply_queue_head; curr_item; ) { + CHECKCLEARTOWRITE(); + buf_putbytes(ses.writepayload, + curr_item->payload->data, curr_item->payload->len); + + buf_free(curr_item->payload); + tmp_item = curr_item; + curr_item = curr_item->next; + m_free(tmp_item); + encrypt_packet(); + } + ses.reply_queue_head = ses.reply_queue_tail = NULL; +} + +/* encrypt the writepayload, putting into writebuf, ready for write_packet() + * to put on the wire */ +void encrypt_packet() { + + unsigned char padlen; + unsigned char blocksize, mac_size; + buffer * writebuf; /* the packet which will go on the wire. This is + encrypted in-place. */ + unsigned char packet_type; + unsigned int len, encrypt_buf_size; + unsigned char mac_bytes[MAX_MAC_LEN]; + + time_t now; + + TRACE2(("enter encrypt_packet()")) + + buf_setpos(ses.writepayload, 0); + packet_type = buf_getbyte(ses.writepayload); + buf_setpos(ses.writepayload, 0); + + TRACE2(("encrypt_packet type is %d", packet_type)) + + if ((!ses.dataallowed && !packet_is_okay_kex(packet_type))) { + /* During key exchange only particular packets are allowed. + Since this packet_type isn't OK we just enqueue it to send + after the KEX, see maybe_flush_reply_queue */ + enqueue_reply_packet(); + return; + } + + blocksize = ses.keys->trans.algo_crypt->blocksize; + mac_size = ses.keys->trans.algo_mac->hashsize; + + /* Encrypted packet len is payload+5. We need to then make sure + * there is enough space for padding or MIN_PACKET_LEN. + * Add extra 3 since we need at least 4 bytes of padding */ + encrypt_buf_size = (ses.writepayload->len+4+1) + + MAX(MIN_PACKET_LEN, blocksize) + 3 + /* add space for the MAC at the end */ + + mac_size +#ifndef DISABLE_ZLIB + /* some extra in case 'compression' makes it larger */ + + ZLIB_COMPRESS_EXPANSION +#endif + /* and an extra cleartext (stripped before transmission) byte for the + * packet type */ + + 1; + + writebuf = buf_new(encrypt_buf_size); + buf_setlen(writebuf, PACKET_PAYLOAD_OFF); + buf_setpos(writebuf, PACKET_PAYLOAD_OFF); + +#ifndef DISABLE_ZLIB + /* compression */ + if (is_compress_trans()) { + buf_compress(writebuf, ses.writepayload, ses.writepayload->len); + } else +#endif + { + memcpy(buf_getwriteptr(writebuf, ses.writepayload->len), + buf_getptr(ses.writepayload, ses.writepayload->len), + ses.writepayload->len); + buf_incrwritepos(writebuf, ses.writepayload->len); + } + + /* finished with payload */ + buf_setpos(ses.writepayload, 0); + buf_setlen(ses.writepayload, 0); + + /* length of padding - packet length excluding the packetlength uint32 + * field in aead mode must be a multiple of blocksize, with a minimum of + * 4 bytes of padding */ + len = writebuf->len; +#if DROPBEAR_AEAD_MODE + if (ses.keys->trans.crypt_mode->aead_crypt) { + len -= 4; + } +#endif + padlen = blocksize - len % blocksize; + if (padlen < 4) { + padlen += blocksize; + } + /* check for min packet length */ + if (writebuf->len + padlen < MIN_PACKET_LEN) { + padlen += blocksize; + } + + buf_setpos(writebuf, 0); + /* packet length excluding the packetlength uint32 */ + buf_putint(writebuf, writebuf->len + padlen - 4); + + /* padding len */ + buf_putbyte(writebuf, padlen); + /* actual padding */ + buf_setpos(writebuf, writebuf->len); + buf_incrlen(writebuf, padlen); + genrandom(buf_getptr(writebuf, padlen), padlen); + +#if DROPBEAR_AEAD_MODE + if (ses.keys->trans.crypt_mode->aead_crypt) { + /* do the actual encryption, in-place */ + buf_setpos(writebuf, 0); + /* encrypt it in-place*/ + len = writebuf->len; + buf_incrlen(writebuf, mac_size); + if (ses.keys->trans.crypt_mode->aead_crypt(ses.transseq, + buf_getptr(writebuf, len), + buf_getwriteptr(writebuf, len + mac_size), + len, mac_size, + &ses.keys->trans.cipher_state, LTC_ENCRYPT) != CRYPT_OK) { + dropbear_exit("Error encrypting"); + } + buf_incrpos(writebuf, len + mac_size); + } else +#endif + { + make_mac(ses.transseq, &ses.keys->trans, writebuf, writebuf->len, mac_bytes); + + /* do the actual encryption, in-place */ + buf_setpos(writebuf, 0); + /* encrypt it in-place*/ + len = writebuf->len; + if (ses.keys->trans.crypt_mode->encrypt( + buf_getptr(writebuf, len), + buf_getwriteptr(writebuf, len), + len, + &ses.keys->trans.cipher_state) != CRYPT_OK) { + dropbear_exit("Error encrypting"); + } + buf_incrpos(writebuf, len); + + /* stick the MAC on it */ + buf_putbytes(writebuf, mac_bytes, mac_size); + } + + /* Update counts */ + ses.kexstate.datatrans += writebuf->len; + + writebuf_enqueue(writebuf); + + /* Update counts */ + ses.transseq++; + + now = monotonic_now(); + ses.last_packet_time_any_sent = now; + /* idle timeout shouldn't be affected by responses to keepalives. + send_msg_keepalive() itself also does tricks with + ses.last_packet_idle_time - read that if modifying this code */ + if (packet_type != SSH_MSG_REQUEST_FAILURE + && packet_type != SSH_MSG_UNIMPLEMENTED + && packet_type != SSH_MSG_IGNORE) { + ses.last_packet_time_idle = now; + + } + + TRACE2(("leave encrypt_packet()")) +} + +void writebuf_enqueue(buffer * writebuf) { + /* enqueue the packet for sending. It will get freed after transmission. */ + buf_setpos(writebuf, 0); + enqueue(&ses.writequeue, (void*)writebuf); + ses.writequeue_len += writebuf->len; +} + + +/* Create the packet mac, and append H(seqno|clearbuf) to the output */ +/* output_mac must have ses.keys->trans.algo_mac->hashsize bytes. */ +static void make_mac(unsigned int seqno, const struct key_context_directional * key_state, + buffer * clear_buf, unsigned int clear_len, + unsigned char *output_mac) { + unsigned char seqbuf[4]; + unsigned long bufsize; + hmac_state hmac; + + if (key_state->algo_mac->hashsize > 0) { + /* calculate the mac */ + if (hmac_init(&hmac, + key_state->hash_index, + key_state->mackey, + key_state->algo_mac->keysize) != CRYPT_OK) { + dropbear_exit("HMAC error"); + } + + /* sequence number */ + STORE32H(seqno, seqbuf); + if (hmac_process(&hmac, seqbuf, 4) != CRYPT_OK) { + dropbear_exit("HMAC error"); + } + + /* the actual contents */ + buf_setpos(clear_buf, 0); + if (hmac_process(&hmac, + buf_getptr(clear_buf, clear_len), + clear_len) != CRYPT_OK) { + dropbear_exit("HMAC error"); + } + + bufsize = MAX_MAC_LEN; + if (hmac_done(&hmac, output_mac, &bufsize) != CRYPT_OK) { + dropbear_exit("HMAC error"); + } + } + TRACE2(("leave writemac")) +} + +#ifndef DISABLE_ZLIB +/* compresses len bytes from src, outputting to dest (starting from the + * respective current positions. dest must have sufficient space, + * len+ZLIB_COMPRESS_EXPANSION */ +static void buf_compress(buffer * dest, buffer * src, unsigned int len) { + + unsigned int endpos = src->pos + len; + int result; + + TRACE2(("enter buf_compress")) + + dropbear_assert(dest->size - dest->pos >= len+ZLIB_COMPRESS_EXPANSION); + + ses.keys->trans.zstream->avail_in = endpos - src->pos; + ses.keys->trans.zstream->next_in = + buf_getptr(src, ses.keys->trans.zstream->avail_in); + + ses.keys->trans.zstream->avail_out = dest->size - dest->pos; + ses.keys->trans.zstream->next_out = + buf_getwriteptr(dest, ses.keys->trans.zstream->avail_out); + + result = deflate(ses.keys->trans.zstream, Z_SYNC_FLUSH); + + buf_setpos(src, endpos - ses.keys->trans.zstream->avail_in); + buf_setlen(dest, dest->size - ses.keys->trans.zstream->avail_out); + buf_setpos(dest, dest->len); + + if (result != Z_OK) { + dropbear_exit("zlib error"); + } + + /* fails if destination buffer wasn't large enough */ + dropbear_assert(ses.keys->trans.zstream->avail_in == 0); + TRACE2(("leave buf_compress")) +} +#endif diff --git a/src/packet.h b/src/packet.h new file mode 100644 index 0000000..e3ab808 --- /dev/null +++ b/src/packet.h @@ -0,0 +1,53 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_PACKET_H_ + +#define DROPBEAR_PACKET_H_ + +#include "includes.h" +#include "queue.h" +#include "buffer.h" + +void write_packet(void); +void read_packet(void); +void decrypt_packet(void); +void encrypt_packet(void); + +void writebuf_enqueue(buffer * writebuf); + +void process_packet(void); + +void maybe_flush_reply_queue(void); +typedef struct PacketType { + unsigned char type; /* SSH_MSG_FOO */ + void (*handler)(void); +} packettype; + +#define PACKET_PADDING_OFF 4 +#define PACKET_PAYLOAD_OFF 5 + +#define INIT_READBUF 128 + +#endif /* DROPBEAR_PACKET_H_ */ diff --git a/src/process-packet.c b/src/process-packet.c new file mode 100644 index 0000000..9454160 --- /dev/null +++ b/src/process-packet.c @@ -0,0 +1,180 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002-2004 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "packet.h" +#include "session.h" +#include "dbutil.h" +#include "ssh.h" +#include "algo.h" +#include "buffer.h" +#include "kex.h" +#include "dbrandom.h" +#include "service.h" +#include "auth.h" +#include "channel.h" + +#define MAX_UNAUTH_PACKET_TYPE SSH_MSG_USERAUTH_PK_OK + +static void recv_unimplemented(void); + +/* process a decrypted packet, call the appropriate handler */ +void process_packet() { + + unsigned char type; + unsigned int i; + time_t now; + + TRACE2(("enter process_packet")) + + type = buf_getbyte(ses.payload); + TRACE(("process_packet: packet type = %d, len %d", type, ses.payload->len)) + + now = monotonic_now(); + ses.last_packet_time_keepalive_recv = now; + + /* These packets we can receive at any time */ + switch(type) { + + case SSH_MSG_IGNORE: + goto out; + case SSH_MSG_DEBUG: + goto out; + + case SSH_MSG_UNIMPLEMENTED: + /* debugging XXX */ + TRACE(("SSH_MSG_UNIMPLEMENTED")) + goto out; + + case SSH_MSG_DISCONNECT: + /* TODO cleanup? */ + dropbear_close("Disconnect received"); + } + + /* Ignore these packet types so that keepalives don't interfere with + idle detection. This is slightly incorrect since a tcp forwarded + global request with failure won't trigger the idle timeout, + but that's probably acceptable */ + if (!(type == SSH_MSG_GLOBAL_REQUEST + || type == SSH_MSG_REQUEST_FAILURE + || type == SSH_MSG_CHANNEL_FAILURE)) { + ses.last_packet_time_idle = now; + } + + /* This applies for KEX, where the spec says the next packet MUST be + * NEWKEYS */ + if (ses.requirenext != 0) { + if (ses.requirenext == type) + { + /* Got what we expected */ + TRACE(("got expected packet %d during kexinit", type)) + } + else + { + /* RFC4253 7.1 - various messages are allowed at this point. + The only ones we know about have already been handled though, + so just return "unimplemented" */ + if (type >= 1 && type <= 49 + && type != SSH_MSG_SERVICE_REQUEST + && type != SSH_MSG_SERVICE_ACCEPT + && type != SSH_MSG_KEXINIT) + { + TRACE(("unknown allowed packet during kexinit")) + recv_unimplemented(); + goto out; + } + else + { + TRACE(("disallowed packet during kexinit")) + dropbear_exit("Unexpected packet type %d, expected %d", type, + ses.requirenext); + } + } + } + + /* Check if we should ignore this packet. Used currently only for + * KEX code, with first_kex_packet_follows */ + if (ses.ignorenext) { + TRACE(("Ignoring packet, type = %d", type)) + ses.ignorenext = 0; + goto out; + } + + /* Only clear the flag after we have checked ignorenext */ + if (ses.requirenext != 0 && ses.requirenext == type) + { + ses.requirenext = 0; + } + + + /* Kindly the protocol authors gave all the preauth packets type values + * less-than-or-equal-to 60 ( == MAX_UNAUTH_PACKET_TYPE ). + * NOTE: if the protocol changes and new types are added, revisit this + * assumption */ + if ( !ses.authstate.authdone && type > MAX_UNAUTH_PACKET_TYPE ) { + dropbear_exit("Received message %d before userauth", type); + } + + for (i = 0; ; i++) { + if (ses.packettypes[i].type == 0) { + /* end of list */ + break; + } + + if (ses.packettypes[i].type == type) { + ses.packettypes[i].handler(); + goto out; + } + } + + + /* TODO do something more here? */ + TRACE(("preauth unknown packet")) + recv_unimplemented(); + +out: + ses.lastpacket = type; + buf_free(ses.payload); + ses.payload = NULL; + + TRACE2(("leave process_packet")) +} + + + +/* This must be called directly after receiving the unimplemented packet. + * Isn't the most clean implementation, it relies on packet processing + * occurring directly after decryption (direct use of ses.recvseq). + * This is reasonably valid, since there is only a single decryption buffer */ +static void recv_unimplemented() { + + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_UNIMPLEMENTED); + /* the decryption routine increments the sequence number, we must + * decrement */ + buf_putint(ses.writepayload, ses.recvseq - 1); + + encrypt_packet(); +} diff --git a/src/progressmeter.c b/src/progressmeter.c new file mode 100644 index 0000000..2038fd3 --- /dev/null +++ b/src/progressmeter.c @@ -0,0 +1,294 @@ +#ifdef PROGRESS_METER +/* + * Copyright (c) 2003 Nils Nordman. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "includes.h" +/*RCSID("$OpenBSD: progressmeter.c,v 1.24 2005/06/07 13:25:23 jaredy Exp $");*/ + +#include "progressmeter.h" +#include "atomicio.h" +#include "scpmisc.h" + +#define DEFAULT_WINSIZE 80 +#define MAX_WINSIZE 512 +#define PADDING 1 /* padding between the progress indicators */ +#define UPDATE_INTERVAL 1 /* update the progress meter every second */ +#define STALL_TIME 5 /* we're stalled after this many seconds */ + +/* determines whether we can output to the terminal */ +static int can_output(void); + +/* formats and inserts the specified size into the given buffer */ +static void format_size(char *, int, off_t); +static void format_rate(char *, int, off_t); + +/* window resizing */ +static void sig_winch(int); +static void setscreensize(void); + +/* updates the progressmeter to reflect the current state of the transfer */ +void refresh_progress_meter(void); + +/* signal handler for updating the progress meter */ +static void update_progress_meter(int); + +static time_t start; /* start progress */ +static time_t last_update; /* last progress update */ +static char *file; /* name of the file being transferred */ +static off_t end_pos; /* ending position of transfer */ +static off_t cur_pos; /* transfer position as of last refresh */ +static volatile off_t *counter; /* progress counter */ +static long stalled; /* how long we have been stalled */ +static int bytes_per_second; /* current speed in bytes per second */ +static int win_size; /* terminal window size */ +static volatile sig_atomic_t win_resized; /* for window resizing */ + +/* units for format_size */ +static const char unit[] = " KMGT"; + +static int +can_output(void) +{ + return (getpgrp() == tcgetpgrp(STDOUT_FILENO)); +} + +static void +format_rate(char *buf, int size, off_t bytes) +{ + int i; + + bytes *= 100; + for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++) + bytes = (bytes + 512) / 1024; + if (i == 0) { + i++; + bytes = (bytes + 512) / 1024; + } + snprintf(buf, size, "%3lld.%1lld%c%s", + (long long) (bytes + 5) / 100, + (long long) (bytes + 5) / 10 % 10, + unit[i], + i ? "B" : " "); +} + +static void +format_size(char *buf, int size, off_t bytes) +{ + int i; + + for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++) + bytes = (bytes + 512) / 1024; + snprintf(buf, size, "%4lld%c%s", + (long long) bytes, + unit[i], + i ? "B" : " "); +} + +void +refresh_progress_meter(void) +{ + char buf[MAX_WINSIZE + 1]; + time_t now; + off_t transferred; + double elapsed; + int percent; + off_t bytes_left; + int cur_speed; + int hours, minutes, seconds; + int i, len; + int file_len; + + transferred = *counter - cur_pos; + cur_pos = *counter; + now = time(NULL); + bytes_left = end_pos - cur_pos; + + if (bytes_left > 0) + elapsed = now - last_update; + else { + elapsed = now - start; + /* Calculate true total speed when done */ + transferred = end_pos; + bytes_per_second = 0; + } + + /* calculate speed */ + if (elapsed != 0) + cur_speed = (transferred / elapsed); + else + cur_speed = transferred; + +#define AGE_FACTOR 0.9 + if (bytes_per_second != 0) { + bytes_per_second = (bytes_per_second * AGE_FACTOR) + + (cur_speed * (1.0 - AGE_FACTOR)); + } else + bytes_per_second = cur_speed; + + /* filename */ + buf[0] = '\0'; + file_len = win_size - 35; + if (file_len > 0) { + len = snprintf(buf, file_len + 1, "\r%s", file); + if (len < 0) + len = 0; + if (len >= file_len + 1) + len = file_len; + for (i = len; i < file_len; i++ ) + buf[i] = ' '; + buf[file_len] = '\0'; + } + + /* percent of transfer done */ + if (end_pos != 0) + percent = ((float)cur_pos / end_pos) * 100; + else + percent = 100; + snprintf(buf + strlen(buf), win_size - strlen(buf), + " %3d%% ", percent); + + /* amount transferred */ + format_size(buf + strlen(buf), win_size - strlen(buf), + cur_pos); + strlcat(buf, " ", win_size); + + /* bandwidth usage */ + format_rate(buf + strlen(buf), win_size - strlen(buf), + (off_t)bytes_per_second); + strlcat(buf, "/s ", win_size); + + /* ETA */ + if (!transferred) + stalled += elapsed; + else + stalled = 0; + + if (stalled >= STALL_TIME) + strlcat(buf, "- stalled -", win_size); + else if (bytes_per_second == 0 && bytes_left) + strlcat(buf, " --:-- ETA", win_size); + else { + if (bytes_left > 0) + seconds = bytes_left / bytes_per_second; + else + seconds = elapsed; + + hours = seconds / 3600; + seconds -= hours * 3600; + minutes = seconds / 60; + seconds -= minutes * 60; + + if (hours != 0) + snprintf(buf + strlen(buf), win_size - strlen(buf), + "%d:%02d:%02d", hours, minutes, seconds); + else + snprintf(buf + strlen(buf), win_size - strlen(buf), + " %02d:%02d", minutes, seconds); + + if (bytes_left > 0) + strlcat(buf, " ETA", win_size); + else + strlcat(buf, " ", win_size); + } + + atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1); + last_update = now; +} + +static void +update_progress_meter(int ignore) +{ + int save_errno; + + save_errno = errno; + + if (win_resized) { + setscreensize(); + win_resized = 0; + } + if (can_output()) + refresh_progress_meter(); + + signal(SIGALRM, update_progress_meter); + alarm(UPDATE_INTERVAL); + errno = save_errno; +} + +void +start_progress_meter(char *f, off_t filesize, off_t *ctr) +{ + start = last_update = time(NULL); + file = f; + end_pos = filesize; + cur_pos = 0; + counter = ctr; + stalled = 0; + bytes_per_second = 0; + + setscreensize(); + if (can_output()) + refresh_progress_meter(); + + signal(SIGALRM, update_progress_meter); + signal(SIGWINCH, sig_winch); + alarm(UPDATE_INTERVAL); +} + +void +stop_progress_meter(void) +{ + alarm(0); + + if (!can_output()) + return; + + /* Ensure we complete the progress */ + if (cur_pos != end_pos) + refresh_progress_meter(); + + atomicio(vwrite, STDOUT_FILENO, "\n", 1); +} + +static void +sig_winch(int sig) +{ + win_resized = 1; +} + +static void +setscreensize(void) +{ + struct winsize winsize; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 && + winsize.ws_col != 0) { + if (winsize.ws_col > MAX_WINSIZE) + win_size = MAX_WINSIZE; + else + win_size = winsize.ws_col; + } else + win_size = DEFAULT_WINSIZE; + win_size += 1; /* trailing \0 */ +} +#endif /* PROGRESS_METER */ diff --git a/src/progressmeter.h b/src/progressmeter.h new file mode 100644 index 0000000..bfb9a0b --- /dev/null +++ b/src/progressmeter.h @@ -0,0 +1,27 @@ +/* $OpenBSD: progressmeter.h,v 1.1 2003/01/10 08:19:07 fgsch Exp $ */ +/* + * Copyright (c) 2002 Nils Nordman. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +void start_progress_meter(char *, off_t, off_t *); +void stop_progress_meter(void); diff --git a/src/pubkeyapi.h b/src/pubkeyapi.h new file mode 100644 index 0000000..21b1f24 --- /dev/null +++ b/src/pubkeyapi.h @@ -0,0 +1,151 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ +#ifndef DROPBEAR_PUBKEY_H +#define DROPBEAR_PUBKEY_H + + +/* External Public Key API (EPKA) Plug-in Interface + * + * See: + * https://github.com/fabriziobertocci/dropbear-epka + * for additional information and examples about this API + * + */ + +struct PluginInstance; +struct PluginSession; + +/* API VERSION INFORMATION - + * Dropbear will: + * - Reject any plugin with a major version mismatch + * - Load and print a warning if the plugin's minor version is HIGHER than + * dropbear's minor version (assumes properties are added at the end of + * PluginInstance or PluginSession). This is a case of plugin newer than dropbear. + * - Reject if the plugin minor version is SMALLER than dropbear one (case + * of plugin older than dropbear). + * - Load (with no warnings) if version match. + */ +#define DROPBEAR_PLUGIN_VERSION_MAJOR 1 +#define DROPBEAR_PLUGIN_VERSION_MINOR 0 + + +/* Creates an instance of the plugin. + * + * This is the main entry point of the plug-in and should be IMMUTABLE across + * different API versions. Dropbear will check the version number + * returned in the api_version to match the version it understands and reject + * any plugin for which API major version does not match. + * + * If the version MINOR is different, dropbear will allow the plugin to run + * only if: plugin_MINOR > dropbear_MINOR + * + * If plugin_MINOR < dropbear_MINOR or if the MAJOR version is different + * dropbear will reject the plugin and terminate the execution. + * + * addrstring is the IP address of the client. + * + * Returns NULL in case of failure, otherwise a void * of the instance that need + * to be passed to all the subsequent call to the plugin + */ +typedef struct PluginInstance *(* PubkeyExtPlugin_newFn)(int verbose, + const char *options, + const char *addrstring); +#define DROPBEAR_PUBKEY_PLUGIN_FNNAME_NEW "plugin_new" + + +/* Validate a client through public key authentication + * + * If session has not been already created, creates it and store it + * in *sessionInOut. + * If session is a non-NULL, it will reuse it. + * + * Returns DROPBEAR_SUCCESS (0) if success or DROPBEAR_FAILURE (-1) if + * authentication fails + */ +typedef int (* PubkeyExtPlugin_checkPubKeyFn)(struct PluginInstance *PluginInstance, + struct PluginSession **sessionInOut, + const char* algo, + unsigned int algolen, + const unsigned char* keyblob, + unsigned int keybloblen, + const char *username); + +/* Notify the plugin that auth completed (after signature verification) + */ +typedef void (* PubkeyExtPlugin_authSuccessFn)(struct PluginSession *session); + +/* Deletes a session + * TODO: Add a reason why the session is terminated. See svr_dropbear_exit (in svr-session.c) + */ +typedef void (* PubkeyExtPlugin_sessionDeleteFn)(struct PluginSession *session); + +/* Deletes the plugin instance */ +typedef void (* PubkeyExtPlugin_deleteFn)(struct PluginInstance *PluginInstance); + + +/* The PluginInstance object - A simple container of the pointer to the functions used + * by Dropbear. + * + * A plug-in can extend it to add its own properties + * + * The instance is created from the call to the plugin_new() function of the + * shared library. + * The delete_plugin function should delete the object. + */ +struct PluginInstance { + int api_version[2]; /* 0=Major, 1=Minor */ + + PubkeyExtPlugin_checkPubKeyFn checkpubkey; /* mandatory */ + PubkeyExtPlugin_authSuccessFn auth_success; /* optional */ + PubkeyExtPlugin_sessionDeleteFn delete_session; /* mandatory */ + PubkeyExtPlugin_deleteFn delete_plugin; /* mandatory */ +}; + +/***************************************************************************** + * SESSION + ****************************************************************************/ +/* Returns the options from the session. + * The returned buffer will be destroyed when the session is deleted. + * Option buffer string NULL-terminated + */ +typedef char * (* PubkeyExtPlugin_getOptionsFn)(struct PluginSession *session); + + +/* An SSH Session. Created during pre-auth and reused during the authentication. + * The plug-in should delete this object (or any object extending it) from + * the delete_session() function. + * + * Extend it to cache user and authentication information that can be + * reused between pre-auth and auth (and to store whatever session-specific + * variable you need to keep). + * + * Store any optional auth options in the auth_options property of the session. + */ +struct PluginSession { + struct PluginInstance * plugin_instance; + + PubkeyExtPlugin_getOptionsFn get_options; +}; + +#endif diff --git a/src/queue.c b/src/queue.c new file mode 100644 index 0000000..f3ece7f --- /dev/null +++ b/src/queue.c @@ -0,0 +1,87 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "queue.h" + +void initqueue(struct Queue* queue) { + + queue->head = NULL; + queue->tail = NULL; + queue->count = 0; +} + +int isempty(const struct Queue* queue) { + + return (queue->head == NULL); +} + +void* dequeue(struct Queue* queue) { + + void* ret; + struct Link* oldhead; + dropbear_assert(!isempty(queue)); + + ret = queue->head->item; + oldhead = queue->head; + + if (oldhead->link != NULL) { + queue->head = oldhead->link; + } else { + queue->head = NULL; + queue->tail = NULL; + TRACE(("empty queue dequeing")) + } + + m_free(oldhead); + queue->count--; + return ret; +} + +void *examine(const struct Queue* queue) { + + dropbear_assert(!isempty(queue)); + return queue->head->item; +} + +void enqueue(struct Queue* queue, void* item) { + + struct Link* newlink; + + newlink = (struct Link*)m_malloc(sizeof(struct Link)); + + newlink->item = item; + newlink->link = NULL; + + if (queue->tail != NULL) { + queue->tail->link = newlink; + } + queue->tail = newlink; + + if (queue->head == NULL) { + queue->head = newlink; + } + queue->count++; +} diff --git a/src/queue.h b/src/queue.h new file mode 100644 index 0000000..ee8ea43 --- /dev/null +++ b/src/queue.h @@ -0,0 +1,49 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_QUEUE_H_ +#define DROPBEAR_QUEUE_H_ + +struct Link { + + void* item; + struct Link* link; + +}; + +struct Queue { + + struct Link* head; + struct Link* tail; + unsigned int count; + +}; + +void initqueue(struct Queue* queue); +int isempty(const struct Queue* queue); +void* dequeue(struct Queue* queue); +void *examine(const struct Queue* queue); +void enqueue(struct Queue* queue, void* item); + +#endif diff --git a/src/rsa.c b/src/rsa.c new file mode 100644 index 0000000..6152e1c --- /dev/null +++ b/src/rsa.c @@ -0,0 +1,431 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* Perform RSA operations on data, including reading keys, signing and + * verification. + * + * The format is specified in rfc2437, Applied Cryptography or The Handbook of + * Applied Cryptography detail the general algorithm. */ + +#include "includes.h" +#include "dbutil.h" +#include "bignum.h" +#include "rsa.h" +#include "buffer.h" +#include "ssh.h" +#include "dbrandom.h" +#include "signkey.h" + +#if DROPBEAR_RSA + +#if !(DROPBEAR_RSA_SHA1 || DROPBEAR_RSA_SHA256) +#error Somehow RSA was enabled with neither DROPBEAR_RSA_SHA1 nor DROPBEAR_RSA_SHA256 +#endif + +static void rsa_pad_em(const dropbear_rsa_key * key, + const buffer *data_buf, mp_int * rsa_em, enum signature_type sigtype); + +/* Load a public rsa key from a buffer, initialising the values. + * The key will have the same format as buf_put_rsa_key. + * These should be freed with rsa_key_free. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int buf_get_rsa_pub_key(buffer* buf, dropbear_rsa_key *key) { + + int ret = DROPBEAR_FAILURE; + TRACE(("enter buf_get_rsa_pub_key")) + dropbear_assert(key != NULL); + m_mp_alloc_init_multi(&key->e, &key->n, NULL); + key->d = NULL; + key->p = NULL; + key->q = NULL; + + buf_incrpos(buf, 4+SSH_SIGNKEY_RSA_LEN); /* int + "ssh-rsa" */ + + if (buf_getmpint(buf, key->e) == DROPBEAR_FAILURE + || buf_getmpint(buf, key->n) == DROPBEAR_FAILURE) { + TRACE(("leave buf_get_rsa_pub_key: failure")) + goto out; + } + + if (mp_count_bits(key->n) < MIN_RSA_KEYLEN) { + dropbear_log(LOG_WARNING, "RSA key too short"); + goto out; + } + + /* 64 bit is limit used by openssl, so we won't block any keys in the wild */ + if (mp_count_bits(key->e) > 64) { + dropbear_log(LOG_WARNING, "RSA key bad e"); + goto out; + } + + TRACE(("leave buf_get_rsa_pub_key: success")) + ret = DROPBEAR_SUCCESS; +out: + if (ret == DROPBEAR_FAILURE) { + m_mp_free_multi(&key->e, &key->n, NULL); + } + return ret; +} + +/* Same as buf_get_rsa_pub_key, but reads private bits at the end. + * Loads a private rsa key from a buffer + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int buf_get_rsa_priv_key(buffer* buf, dropbear_rsa_key *key) { + int ret = DROPBEAR_FAILURE; + + TRACE(("enter buf_get_rsa_priv_key")) + dropbear_assert(key != NULL); + + if (buf_get_rsa_pub_key(buf, key) == DROPBEAR_FAILURE) { + TRACE(("leave buf_get_rsa_priv_key: pub: ret == DROPBEAR_FAILURE")) + return DROPBEAR_FAILURE; + } + + key->d = NULL; + key->p = NULL; + key->q = NULL; + + m_mp_alloc_init_multi(&key->d, NULL); + if (buf_getmpint(buf, key->d) == DROPBEAR_FAILURE) { + TRACE(("leave buf_get_rsa_priv_key: d: ret == DROPBEAR_FAILURE")) + goto out; + } + + if (buf->pos == buf->len) { + /* old Dropbear private keys didn't keep p and q, so we will ignore them*/ + } else { + m_mp_alloc_init_multi(&key->p, &key->q, NULL); + + if (buf_getmpint(buf, key->p) == DROPBEAR_FAILURE) { + TRACE(("leave buf_get_rsa_priv_key: p: ret == DROPBEAR_FAILURE")) + goto out; + } + + if (buf_getmpint(buf, key->q) == DROPBEAR_FAILURE) { + TRACE(("leave buf_get_rsa_priv_key: q: ret == DROPBEAR_FAILURE")) + goto out; + } + } + + ret = DROPBEAR_SUCCESS; +out: + if (ret == DROPBEAR_FAILURE) { + m_mp_free_multi(&key->d, &key->p, &key->q, NULL); + } + TRACE(("leave buf_get_rsa_priv_key")) + return ret; +} + + +/* Clear and free the memory used by a public or private key */ +void rsa_key_free(dropbear_rsa_key *key) { + + TRACE2(("enter rsa_key_free")) + + if (key == NULL) { + TRACE2(("leave rsa_key_free: key == NULL")) + return; + } + m_mp_free_multi(&key->d, &key->e, &key->p, &key->q, &key->n, NULL); + m_free(key); + TRACE2(("leave rsa_key_free")) +} + +/* Put the public rsa key into the buffer in the required format: + * + * string "ssh-rsa" + * mp_int e + * mp_int n + */ +void buf_put_rsa_pub_key(buffer* buf, const dropbear_rsa_key *key) { + + TRACE(("enter buf_put_rsa_pub_key")) + dropbear_assert(key != NULL); + + buf_putstring(buf, SSH_SIGNKEY_RSA, SSH_SIGNKEY_RSA_LEN); + buf_putmpint(buf, key->e); + buf_putmpint(buf, key->n); + + TRACE(("leave buf_put_rsa_pub_key")) + +} + +/* Same as buf_put_rsa_pub_key, but with the private "x" key appended */ +void buf_put_rsa_priv_key(buffer* buf, const dropbear_rsa_key *key) { + + TRACE(("enter buf_put_rsa_priv_key")) + + dropbear_assert(key != NULL); + buf_put_rsa_pub_key(buf, key); + buf_putmpint(buf, key->d); + + /* new versions have p and q, old versions don't */ + if (key->p) { + buf_putmpint(buf, key->p); + } + if (key->q) { + buf_putmpint(buf, key->q); + } + + + TRACE(("leave buf_put_rsa_priv_key")) + +} + +#if DROPBEAR_SIGNKEY_VERIFY +/* Verify a signature in buf, made on data by the key given. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int buf_rsa_verify(buffer * buf, const dropbear_rsa_key *key, + enum signature_type sigtype, const buffer *data_buf) { + unsigned int slen; + DEF_MP_INT(rsa_s); + DEF_MP_INT(rsa_mdash); + DEF_MP_INT(rsa_em); + int ret = DROPBEAR_FAILURE; + + TRACE(("enter buf_rsa_verify")) + + dropbear_assert(key != NULL); + + m_mp_init_multi(&rsa_mdash, &rsa_s, &rsa_em, NULL); + + slen = buf_getint(buf); + if (slen != (unsigned int)mp_ubin_size(key->n)) { + TRACE(("bad size")) + goto out; + } + + if (mp_from_ubin(&rsa_s, buf_getptr(buf, buf->len - buf->pos), + buf->len - buf->pos) != MP_OKAY) { + TRACE(("failed reading rsa_s")) + goto out; + } + + /* check that s <= n-1 */ + if (mp_cmp(&rsa_s, key->n) != MP_LT) { + TRACE(("s > n-1")) + goto out; + } + + /* create the magic PKCS padded value */ + rsa_pad_em(key, data_buf, &rsa_em, sigtype); + + if (mp_exptmod(&rsa_s, key->e, key->n, &rsa_mdash) != MP_OKAY) { + TRACE(("failed exptmod rsa_s")) + goto out; + } + + if (mp_cmp(&rsa_em, &rsa_mdash) == MP_EQ) { + /* signature is valid */ + TRACE(("success!")) + ret = DROPBEAR_SUCCESS; + } + +out: + mp_clear_multi(&rsa_mdash, &rsa_s, &rsa_em, NULL); + TRACE(("leave buf_rsa_verify: ret %d", ret)) + return ret; +} + +#endif /* DROPBEAR_SIGNKEY_VERIFY */ + +/* Sign the data presented with key, writing the signature contents + * to the buffer */ +void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, + enum signature_type sigtype, const buffer *data_buf) { + const char *name = NULL; + unsigned int nsize, ssize, namelen = 0; + unsigned int i; + size_t written; + DEF_MP_INT(rsa_s); + DEF_MP_INT(rsa_tmp1); + DEF_MP_INT(rsa_tmp2); + DEF_MP_INT(rsa_tmp3); + + TRACE(("enter buf_put_rsa_sign")) + dropbear_assert(key != NULL); + + m_mp_init_multi(&rsa_s, &rsa_tmp1, &rsa_tmp2, &rsa_tmp3, NULL); + + rsa_pad_em(key, data_buf, &rsa_tmp1, sigtype); + + /* the actual signing of the padded data */ + +#if DROPBEAR_RSA_BLINDING + + /* With blinding, s = (r^(-1))((em)*r^e)^d mod n */ + + /* generate the r blinding value */ + /* rsa_tmp2 is r */ + gen_random_mpint(key->n, &rsa_tmp2); + + /* rsa_tmp1 is em */ + /* em' = em * r^e mod n */ + + /* rsa_s used as a temp var*/ + if (mp_exptmod(&rsa_tmp2, key->e, key->n, &rsa_s) != MP_OKAY) { + dropbear_exit("RSA error"); + } + if (mp_invmod(&rsa_tmp2, key->n, &rsa_tmp3) != MP_OKAY) { + dropbear_exit("RSA error"); + } + if (mp_mulmod(&rsa_tmp1, &rsa_s, key->n, &rsa_tmp2) != MP_OKAY) { + dropbear_exit("RSA error"); + } + + /* rsa_tmp2 is em' */ + /* s' = (em')^d mod n */ + if (mp_exptmod(&rsa_tmp2, key->d, key->n, &rsa_tmp1) != MP_OKAY) { + dropbear_exit("RSA error"); + } + + /* rsa_tmp1 is s' */ + /* rsa_tmp3 is r^(-1) mod n */ + /* s = (s')r^(-1) mod n */ + if (mp_mulmod(&rsa_tmp1, &rsa_tmp3, key->n, &rsa_s) != MP_OKAY) { + dropbear_exit("RSA error"); + } + +#else + + /* s = em^d mod n */ + /* rsa_tmp1 is em */ + if (mp_exptmod(&rsa_tmp1, key->d, key->n, &rsa_s) != MP_OKAY) { + dropbear_exit("RSA error"); + } + +#endif /* DROPBEAR_RSA_BLINDING */ + + mp_clear_multi(&rsa_tmp1, &rsa_tmp2, &rsa_tmp3, NULL); + + /* create the signature to return */ + name = signature_name_from_type(sigtype, &namelen); + buf_putstring(buf, name, namelen); + + nsize = mp_ubin_size(key->n); + + /* string rsa_signature_blob length */ + buf_putint(buf, nsize); + /* pad out s to same length as n */ + ssize = mp_ubin_size(&rsa_s); + dropbear_assert(ssize <= nsize); + for (i = 0; i < nsize-ssize; i++) { + buf_putbyte(buf, 0x00); + } + + if (mp_to_ubin(&rsa_s, buf_getwriteptr(buf, ssize), ssize, &written) != MP_OKAY) { + dropbear_exit("RSA error"); + } + buf_incrwritepos(buf, written); + mp_clear(&rsa_s); + +#if defined(DEBUG_RSA) && DEBUG_TRACE + if (!debug_trace) { + printhex("RSA sig", buf->data, buf->len); + } +#endif + + + TRACE(("leave buf_put_rsa_sign")) +} + +/* Creates the message value as expected by PKCS, + see rfc8017 section 9.2 */ +static void rsa_pad_em(const dropbear_rsa_key * key, + const buffer *data_buf, mp_int * rsa_em, enum signature_type sigtype) { + /* EM = 0x00 || 0x01 || PS || 0x00 || T + PS is padding of 0xff to make EM the size of key->n + + T is the DER encoding of the hash alg (sha1 or sha256) + */ + + /* From rfc8017 page 46 */ +#if DROPBEAR_RSA_SHA1 + const unsigned char T_sha1[] = + {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, + 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}; +#endif +#if DROPBEAR_RSA_SHA256 + const unsigned char T_sha256[] = + {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}; +#endif + + int Tlen = 0; + const unsigned char *T = NULL; + const struct ltc_hash_descriptor *hash_desc = NULL; + buffer * rsa_EM = NULL; + hash_state hs; + unsigned int nsize; + + switch (sigtype) { +#if DROPBEAR_RSA_SHA1 + case DROPBEAR_SIGNATURE_RSA_SHA1: + Tlen = sizeof(T_sha1); + T = T_sha1; + hash_desc = &sha1_desc; + break; +#endif +#if DROPBEAR_RSA_SHA256 + case DROPBEAR_SIGNATURE_RSA_SHA256: + Tlen = sizeof(T_sha256); + T = T_sha256; + hash_desc = &sha256_desc; + break; +#endif + default: + assert(0); + } + + + nsize = mp_ubin_size(key->n); + + rsa_EM = buf_new(nsize); + /* type byte */ + buf_putbyte(rsa_EM, 0x00); + buf_putbyte(rsa_EM, 0x01); + /* Padding with PS 0xFF bytes */ + while(rsa_EM->pos != rsa_EM->size - (1 + Tlen + hash_desc->hashsize)) { + buf_putbyte(rsa_EM, 0xff); + } + buf_putbyte(rsa_EM, 0x00); + /* Magic ASN1 stuff */ + buf_putbytes(rsa_EM, T, Tlen); + + /* The hash of the data */ + hash_desc->init(&hs); + hash_desc->process(&hs, data_buf->data, data_buf->len); + hash_desc->done(&hs, buf_getwriteptr(rsa_EM, hash_desc->hashsize)); + buf_incrwritepos(rsa_EM, hash_desc->hashsize); + + dropbear_assert(rsa_EM->pos == rsa_EM->size); + + /* Create the mp_int from the encoded bytes */ + buf_setpos(rsa_EM, 0); + bytes_to_mp(rsa_em, buf_getptr(rsa_EM, rsa_EM->size), + rsa_EM->size); + buf_free(rsa_EM); +} + +#endif /* DROPBEAR_RSA */ diff --git a/src/rsa.h b/src/rsa.h new file mode 100644 index 0000000..a8bbf41 --- /dev/null +++ b/src/rsa.h @@ -0,0 +1,59 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_RSA_H_ +#define DROPBEAR_RSA_H_ + +#include "includes.h" +#include "signkey.h" +#include "buffer.h" + +#if DROPBEAR_RSA + +typedef struct dropbear_RSA_Key { + + mp_int* n; + mp_int* e; + /* d, p, and q are private parts */ + mp_int* d; + mp_int* p; + mp_int* q; + +} dropbear_rsa_key; + +void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, + enum signature_type sigtype, const buffer *data_buf); +#if DROPBEAR_SIGNKEY_VERIFY +int buf_rsa_verify(buffer * buf, const dropbear_rsa_key *key, + enum signature_type sigtype, const buffer *data_buf); +#endif +int buf_get_rsa_pub_key(buffer* buf, dropbear_rsa_key *key); +int buf_get_rsa_priv_key(buffer* buf, dropbear_rsa_key *key); +void buf_put_rsa_pub_key(buffer* buf, const dropbear_rsa_key *key); +void buf_put_rsa_priv_key(buffer* buf, const dropbear_rsa_key *key); +void rsa_key_free(dropbear_rsa_key *key); + +#endif /* DROPBEAR_RSA */ + +#endif /* DROPBEAR_RSA_H_ */ diff --git a/src/runopts.h b/src/runopts.h new file mode 100644 index 0000000..d44283d --- /dev/null +++ b/src/runopts.h @@ -0,0 +1,208 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_RUNOPTS_H_ +#define DROPBEAR_RUNOPTS_H_ + +#include "includes.h" +#include "signkey.h" +#include "buffer.h" +#include "auth.h" +#include "tcpfwd.h" + +typedef struct runopts { + + int disable_ip_tos; +#if DROPBEAR_SVR_REMOTETCPFWD || DROPBEAR_CLI_LOCALTCPFWD \ + || DROPBEAR_CLI_REMOTETCPFWD + int listen_fwd_all; +#endif + unsigned int recv_window; + long keepalive_secs; /* Time between sending keepalives. 0 is off */ + long idle_timeout_secs; /* Exit if no traffic is sent/received in this time */ + int usingsyslog; + +#ifndef DISABLE_ZLIB + /* TODO: add a commandline flag. Currently this is on by default if compression + * is compiled in, but disabled for a client's non-final multihop stages. (The + * intermediate stages are compressed streams, so are uncompressible. */ + enum { + DROPBEAR_COMPRESS_DELAYED, /* Server only */ + DROPBEAR_COMPRESS_ON, + DROPBEAR_COMPRESS_OFF, + } compress_mode; +#endif + +#if DROPBEAR_USER_ALGO_LIST + char *cipher_list; + char *mac_list; +#endif + +} runopts; + +extern runopts opts; + +int readhostkey(const char * filename, sign_key * hostkey, + enum signkey_type *type); +void load_all_hostkeys(void); + +typedef struct svr_runopts { + + char * bannerfile; + + int forkbg; + + /* ports and addresses are arrays of the portcount + listening ports. strings are malloced. */ + char *ports[DROPBEAR_MAX_PORTS]; + unsigned int portcount; + char *addresses[DROPBEAR_MAX_PORTS]; + + int inetdmode; + /* Hidden "-2 childpipe_fd" flag indicates it's re-executing itself, + stores the childpipe preauth file descriptor. Set to -1 otherwise. */ + int reexec_childpipe; + + /* Flags indicating whether to use ipv4 and ipv6 */ + /* not used yet + int ipv4; + int ipv6; + */ + +#if DO_MOTD + /* whether to print the MOTD */ + int domotd; +#endif + int norootlogin; + +#ifdef HAVE_GETGROUPLIST + /* restrict_group is the group name if group restriction was enabled, + NULL otherwise */ + char *restrict_group; + /* restrict_group_gid is only valid if restrict_group is set */ + gid_t restrict_group_gid; +#endif + + int noauthpass; + int norootpass; + int allowblankpass; + int multiauthmethod; + unsigned int maxauthtries; + +#if DROPBEAR_SVR_REMOTETCPFWD + int noremotetcp; +#endif +#if DROPBEAR_SVR_LOCALTCPFWD + int nolocaltcp; +#endif + + sign_key *hostkey; + + int delay_hostkey; + + char *hostkey_files[MAX_HOSTKEYS]; + int num_hostkey_files; + + buffer * banner; + char * pidfile; + + char * forced_command; + +#if DROPBEAR_PLUGIN + /* malloced */ + char *pubkey_plugin; + /* points into pubkey_plugin */ + char *pubkey_plugin_options; +#endif + + int pass_on_env; + +} svr_runopts; + +extern svr_runopts svr_opts; + +void svr_getopts(int argc, char ** argv); +void loadhostkeys(void); + +typedef struct cli_runopts { + + char *progname; + char *remotehost; + const char *remoteport; + + char *own_user; + char *username; + + char *cmd; + int wantpty; + int always_accept_key; + int no_hostkey_check; + int no_cmd; + int quiet; + int backgrounded; + int is_subsystem; +#if DROPBEAR_CLI_PUBKEY_AUTH + m_list *privkeys; /* Keys to use for public-key auth */ +#endif +#if DROPBEAR_CLI_ANYTCPFWD + int exit_on_fwd_failure; +#endif + int disable_trivial_auth; +#if DROPBEAR_CLI_REMOTETCPFWD + m_list * remotefwds; +#endif +#if DROPBEAR_CLI_LOCALTCPFWD + m_list * localfwds; +#endif +#if DROPBEAR_CLI_AGENTFWD + int agent_fwd; + int agent_keys_loaded; /* whether pubkeys has been populated with a + list of keys held by the agent */ + int agent_fd; /* The agent fd is only set during authentication. Forwarded + agent sessions have their own file descriptors */ +#endif + +#if DROPBEAR_CLI_NETCAT + char *netcat_host; + unsigned int netcat_port; +#endif +#if DROPBEAR_CLI_PROXYCMD + char *proxycmd; +#endif + char *bind_address; + char *bind_port; +} cli_runopts; + +extern cli_runopts cli_opts; +void cli_getopts(int argc, char ** argv); + +#if DROPBEAR_USER_ALGO_LIST +void parse_ciphers_macs(void); +#endif + +void print_version(void); +void parse_recv_window(const char* recv_window_arg); +int split_address_port(const char* spec, char **first, char ** second); + +#endif /* DROPBEAR_RUNOPTS_H_ */ diff --git a/src/scp.c b/src/scp.c new file mode 100644 index 0000000..72e04f8 --- /dev/null +++ b/src/scp.c @@ -0,0 +1,1259 @@ +/* Dropbear Note: This file is based on OpenSSH 4.3p2. Avoid unnecessary + changes to simplify future updates */ + +/* + * scp - secure remote copy. This is basically patched BSD rcp which + * uses ssh to do the data transfer (instead of using rcmd). + * + * NOTE: This version should NOT be suid root. (This uses ssh to + * do the transfer and ssh has the necessary privileges.) + * + * 1995 Timo Rinne <tri@iki.fi>, Tatu Ylonen <ylo@cs.hut.fi> + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ +/* + * Copyright (c) 1999 Theo de Raadt. All rights reserved. + * Copyright (c) 1999 Aaron Campbell. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Parts from: + * + * Copyright (c) 1983, 1990, 1992, 1993, 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include "includes.h" +/*RCSID("$OpenBSD: scp.c,v 1.130 2006/01/31 10:35:43 djm Exp $");*/ + +#include "atomicio.h" +#include "compat.h" +#include "scpmisc.h" +#include "progressmeter.h" + +void bwlimit(int); + +/* Struct for addargs */ +arglist args; + +/* Bandwidth limit */ +off_t limit_rate = 0; + +/* Name of current file being transferred. */ +char *curfile; + +/* This is set to non-zero to enable verbose mode. */ +int verbose_mode = 0; + +/* This is set to zero if the progressmeter is not desired. */ +int showprogress = 1; + +/* This is the program to execute for the secured connection. ("ssh" or -S) */ +char *ssh_program = DROPBEAR_PATH_SSH_PROGRAM; + +/* This is used to store the pid of ssh_program */ +pid_t do_cmd_pid = -1; + +static void +killchild(int signo) +{ + if (do_cmd_pid > 1) { + kill(do_cmd_pid, signo ? signo : SIGTERM); + waitpid(do_cmd_pid, NULL, 0); + } + + if (signo) + _exit(1); + exit(1); +} + +static int +do_local_cmd(arglist *a) +{ + u_int i; + int status; + pid_t pid; + + if (a->num == 0) + fatal("do_local_cmd: no arguments"); + + if (verbose_mode) { + fprintf(stderr, "Executing:"); + for (i = 0; i < a->num; i++) + fprintf(stderr, " %s", a->list[i]); + fprintf(stderr, "\n"); + } +#if DROPBEAR_VFORK + pid = vfork(); +#else + pid = fork(); +#endif + if (pid == -1) + fatal("do_local_cmd: fork: %s", strerror(errno)); + + if (pid == 0) { + execvp(a->list[0], a->list); + perror(a->list[0]); +#if DROPBEAR_VFORK + _exit(1); +#else + exit(1); +#endif + } + + do_cmd_pid = pid; + signal(SIGTERM, killchild); + signal(SIGINT, killchild); + signal(SIGHUP, killchild); + + while (waitpid(pid, &status, 0) == -1) + if (errno != EINTR) + fatal("do_local_cmd: waitpid: %s", strerror(errno)); + + do_cmd_pid = -1; + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + return (-1); + + return (0); +} + +/* + * This function executes the given command as the specified user on the + * given host. This returns < 0 if execution fails, and >= 0 otherwise. This + * assigns the input and output file descriptors on success. + */ + +static void +arg_setup(char *host, char *remuser, char *cmd) +{ + replacearg(&args, 0, "%s", ssh_program); + if (remuser != NULL) + addargs(&args, "-l%s", remuser); + addargs(&args, "%s", host); + addargs(&args, "%s", cmd); +} + +int +do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) +{ + int pin[2], pout[2], reserved[2]; + + if (verbose_mode) + fprintf(stderr, + "Executing: program %s host %s, user %s, command %s\n", + ssh_program, host, + remuser ? remuser : "(unspecified)", cmd); + + /* + * Reserve two descriptors so that the real pipes won't get + * descriptors 0 and 1 because that will screw up dup2 below. + */ + pipe(reserved); + + /* Create a socket pair for communicating with ssh. */ + if (pipe(pin) < 0) + fatal("pipe: %s", strerror(errno)); + if (pipe(pout) < 0) + fatal("pipe: %s", strerror(errno)); + + /* Free the reserved descriptors. */ + close(reserved[0]); + close(reserved[1]); + + /* uClinux needs to build the args here before vforking, + otherwise we do it later on. */ +#if DROPBEAR_VFORK + arg_setup(host, remuser, cmd); +#endif + + /* Fork a child to execute the command on the remote host using ssh. */ +#if DROPBEAR_VFORK + do_cmd_pid = vfork(); +#else + do_cmd_pid = fork(); +#endif + + if (do_cmd_pid == 0) { + /* Child. */ + close(pin[1]); + close(pout[0]); + dup2(pin[0], 0); + dup2(pout[1], 1); + close(pin[0]); + close(pout[1]); + +#if !DROPBEAR_VFORK + arg_setup(host, remuser, cmd); +#endif + + execvp(ssh_program, args.list); + perror(ssh_program); +#if DROPBEAR_VFORK + _exit(1); +#else + exit(1); +#endif + } else if (do_cmd_pid == -1) { + fatal("fork: %s", strerror(errno)); + } + +#if DROPBEAR_VFORK + /* clean up command */ + /* pop cmd */ + xfree(args.list[args.num-1]); + args.list[args.num-1]=NULL; + args.num--; + /* pop host */ + xfree(args.list[args.num-1]); + args.list[args.num-1]=NULL; + args.num--; + /* pop user */ + if (remuser != NULL) { + xfree(args.list[args.num-1]); + args.list[args.num-1]=NULL; + args.num--; + } +#endif + + /* Parent. Close the other side, and return the local side. */ + close(pin[0]); + *fdout = pin[1]; + close(pout[1]); + *fdin = pout[0]; + signal(SIGTERM, killchild); + signal(SIGINT, killchild); + signal(SIGHUP, killchild); + return 0; +} + +typedef struct { + size_t cnt; + char *buf; +} BUF; + +BUF *allocbuf(BUF *, int, int); +void lostconn(int); +void nospace(void); +int okname(char *); +void run_err(const char *,...); +void verifydir(char *); + +uid_t userid; +int errs, remin, remout; +int pflag, iamremote, iamrecursive, targetshouldbedirectory; + +#define CMDNEEDS 64 +char cmd[CMDNEEDS]; /* must hold "rcp -r -p -d\0" */ + +int response(void); +void rsource(char *, struct stat *); +void sink(int, char *[]); +void source(int, char *[]); +void tolocal(int, char *[]); +void toremote(char *, int, char *[]); +void usage(void); + +#if defined(DBMULTI_scp) || !DROPBEAR_MULTI +#if defined(DBMULTI_scp) && DROPBEAR_MULTI +int scp_main(int argc, char **argv) +#else +int +main(int argc, char **argv) +#endif +{ + int ch, fflag, tflag, status; + double speed; + char *targ, *endp; + extern char *optarg; + extern int optind; + + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ + sanitise_stdfd(); + + memset(&args, '\0', sizeof(args)); + args.list = NULL; + addargs(&args, "%s", ssh_program); + + fflag = tflag = 0; + while ((ch = getopt(argc, argv, "dfl:prtvBCc:i:P:q1246S:o:F:")) != -1) + switch (ch) { + /* User-visible flags. */ + case '1': + case '2': + case '4': + case '6': + case 'C': + addargs(&args, "-%c", ch); + break; + case 'o': + case 'c': + case 'i': + case 'F': + addargs(&args, "-%c%s", ch, optarg); + break; + case 'P': + addargs(&args, "-p%s", optarg); + break; + case 'B': + fprintf(stderr, "Note: -B option is disabled in this version of scp"); + break; + case 'l': + speed = strtod(optarg, &endp); + if (speed <= 0 || *endp != '\0') + usage(); + limit_rate = speed * 1024; + break; + case 'p': + pflag = 1; + break; + case 'r': + iamrecursive = 1; + break; + case 'S': + ssh_program = xstrdup(optarg); + break; + case 'v': + addargs(&args, "-v"); + verbose_mode = 1; + break; + case 'q': +#ifdef PROGRESS_METER + addargs(&args, "-q"); + showprogress = 0; +#endif + break; + + /* Server options. */ + case 'd': + targetshouldbedirectory = 1; + break; + case 'f': /* "from" */ + iamremote = 1; + fflag = 1; + break; + case 't': /* "to" */ + iamremote = 1; + tflag = 1; +#ifdef HAVE_CYGWIN + setmode(0, O_BINARY); +#endif + break; + default: + usage(); + } + argc -= optind; + argv += optind; + + if (!isatty(STDERR_FILENO)) + showprogress = 0; + + remin = STDIN_FILENO; + remout = STDOUT_FILENO; + + if (fflag) { + /* Follow "protocol", send data. */ + (void) response(); + source(argc, argv); + exit(errs != 0); + } + if (tflag) { + /* Receive data. */ + sink(argc, argv); + exit(errs != 0); + } + if (argc < 2) + usage(); + if (argc > 2) + targetshouldbedirectory = 1; + + remin = remout = -1; + do_cmd_pid = -1; + /* Command to be executed on remote system using "ssh". */ + (void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s", + verbose_mode ? " -v" : "", + iamrecursive ? " -r" : "", pflag ? " -p" : "", + targetshouldbedirectory ? " -d" : ""); + + (void) signal(SIGPIPE, lostconn); + + if ((targ = colon(argv[argc - 1]))) /* Dest is remote host. */ + toremote(targ, argc, argv); + else { + if (targetshouldbedirectory) + verifydir(argv[argc - 1]); + tolocal(argc, argv); /* Dest is local host. */ + } + /* + * Finally check the exit status of the ssh process, if one was forked + * and no error has occurred yet + */ + if (do_cmd_pid != -1 && errs == 0) { + if (remin != -1) + (void) close(remin); + if (remout != -1) + (void) close(remout); + if (waitpid(do_cmd_pid, &status, 0) == -1) + errs = 1; + else { + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + errs = 1; + } + } + exit(errs != 0); +} +#endif /* DBMULTI_scp stuff */ + +void +toremote(char *targ, int argc, char **argv) +{ + int i, len; + char *bp, *host, *src, *suser, *thost, *tuser, *arg; + arglist alist; + + memset(&alist, '\0', sizeof(alist)); + alist.list = NULL; + + *targ++ = 0; + if (*targ == 0) + targ = "."; + + arg = xstrdup(argv[argc - 1]); + if ((thost = strrchr(arg, '@'))) { + /* user@host */ + *thost++ = 0; + tuser = arg; + if (*tuser == '\0') + tuser = NULL; + } else { + thost = arg; + tuser = NULL; + } + + if (tuser != NULL && !okname(tuser)) { + xfree(arg); + return; + } + + for (i = 0; i < argc - 1; i++) { + src = colon(argv[i]); + if (src) { /* remote to remote */ + freeargs(&alist); + addargs(&alist, "%s", ssh_program); + if (verbose_mode) + addargs(&alist, "-v"); +#if 0 + /* Disabled since dbclient won't understand them + and scp works fine without them. */ + addargs(&alist, "-x"); + addargs(&alist, "-oClearAllForwardings yes"); + addargs(&alist, "-n"); +#endif + + *src++ = 0; + if (*src == 0) + src = "."; + host = strrchr(argv[i], '@'); + + if (host) { + *host++ = 0; + host = cleanhostname(host); + suser = argv[i]; + if (*suser == '\0') + continue; /* pretend there wasn't any @ at all */ + else if (!okname(suser)) + continue; + addargs(&alist, "-l"); + addargs(&alist, "%s", suser); + } else { + host = cleanhostname(argv[i]); + } + addargs(&alist, "%s", host); + addargs(&alist, "%s", cmd); + addargs(&alist, "%s", src); + addargs(&alist, "%s%s%s:%s", + tuser ? tuser : "", tuser ? "@" : "", + thost, targ); + if (do_local_cmd(&alist) != 0) + errs = 1; + } else { /* local to remote */ + if (remin == -1) { + len = strlen(targ) + CMDNEEDS + 20; + bp = xmalloc(len); + (void) snprintf(bp, len, "%s -t %s", cmd, targ); + host = cleanhostname(thost); + if (do_cmd(host, tuser, bp, &remin, &remout) < 0) + exit(1); + if (response() < 0) + exit(1); + (void) xfree(bp); + } + source(1, argv + i); + } + } +} + +void +tolocal(int argc, char **argv) +{ + int i, len; + char *bp, *host, *src, *suser; + arglist alist; + + memset(&alist, '\0', sizeof(alist)); + alist.list = NULL; + + for (i = 0; i < argc - 1; i++) { + if (!(src = colon(argv[i]))) { /* Local to local. */ + freeargs(&alist); + addargs(&alist, "%s", _PATH_CP); + if (iamrecursive) + addargs(&alist, "-r"); + if (pflag) + addargs(&alist, "-p"); + addargs(&alist, "%s", argv[i]); + addargs(&alist, "%s", argv[argc-1]); + if (do_local_cmd(&alist)) + ++errs; + continue; + } + *src++ = 0; + if (*src == 0) + src = "."; + if ((host = strrchr(argv[i], '@')) == NULL) { + host = argv[i]; + suser = NULL; + } else { + *host++ = 0; + suser = argv[i]; + if (*suser == '\0') + suser = NULL; + } + host = cleanhostname(host); + len = strlen(src) + CMDNEEDS + 20; + bp = xmalloc(len); + (void) snprintf(bp, len, "%s -f %s", cmd, src); + if (do_cmd(host, suser, bp, &remin, &remout) < 0) { + (void) xfree(bp); + ++errs; + continue; + } + xfree(bp); + sink(1, argv + argc - 1); + (void) close(remin); + remin = remout = -1; + } +} + +void +source(int argc, char **argv) +{ + struct stat stb; + static BUF buffer; + BUF *bp; + off_t i, amt, statbytes; + size_t result; + int fd = -1, haderr, indx; + char *last, *name, buf[2048]; + int len; + + for (indx = 0; indx < argc; ++indx) { + name = argv[indx]; + statbytes = 0; + len = strlen(name); + while (len > 1 && name[len-1] == '/') + name[--len] = '\0'; + if (strchr(name, '\n') != NULL) { + run_err("%s: skipping, filename contains a newline", + name); + goto next; + } + if ((fd = open(name, O_RDONLY, 0)) < 0) + goto syserr; + if (fstat(fd, &stb) < 0) { +syserr: run_err("%s: %s", name, strerror(errno)); + goto next; + } + switch (stb.st_mode & S_IFMT) { + case S_IFREG: + break; + case S_IFDIR: + if (iamrecursive) { + rsource(name, &stb); + goto next; + } + /* FALLTHROUGH */ + default: + run_err("%s: not a regular file", name); + goto next; + } + if ((last = strrchr(name, '/')) == NULL) + last = name; + else + ++last; + curfile = last; + if (pflag) { + /* + * Make it compatible with possible future + * versions expecting microseconds. + */ + (void) snprintf(buf, sizeof buf, "T%lu 0 %lu 0\n", + (u_long) stb.st_mtime, + (u_long) stb.st_atime); + (void) atomicio(vwrite, remout, buf, strlen(buf)); + if (response() < 0) + goto next; + } +#define FILEMODEMASK (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO) + snprintf(buf, sizeof buf, "C%04o %lld %s\n", + (u_int) (stb.st_mode & FILEMODEMASK), + (long long)stb.st_size, last); + if (verbose_mode) { + fprintf(stderr, "Sending file modes: %s", buf); + } + (void) atomicio(vwrite, remout, buf, strlen(buf)); + if (response() < 0) + goto next; + if ((bp = allocbuf(&buffer, fd, 2048)) == NULL) { +next: if (fd != -1) { + (void) close(fd); + fd = -1; + } + continue; + } +#ifdef PROGRESS_METER + if (showprogress) + start_progress_meter(curfile, stb.st_size, &statbytes); +#endif + /* Keep writing after an error so that we stay sync'd up. */ + for (haderr = i = 0; i < stb.st_size; i += bp->cnt) { + amt = bp->cnt; + if (i + amt > stb.st_size) + amt = stb.st_size - i; + if (!haderr) { + result = atomicio(read, fd, bp->buf, amt); + if (result != amt) + haderr = errno; + } + if (haderr) + (void) atomicio(vwrite, remout, bp->buf, amt); + else { + result = atomicio(vwrite, remout, bp->buf, amt); + if (result != amt) + haderr = errno; + statbytes += result; + } + if (limit_rate) + bwlimit(amt); + } +#ifdef PROGRESS_METER + if (showprogress) + stop_progress_meter(); +#endif + + if (fd != -1) { + if (close(fd) < 0 && !haderr) + haderr = errno; + fd = -1; + } + if (!haderr) + (void) atomicio(vwrite, remout, "", 1); + else + run_err("%s: %s", name, strerror(haderr)); + (void) response(); + } +} + +void +rsource(char *name, struct stat *statp) +{ + DIR *dirp; + struct dirent *dp; + char *last, *vect[1], path[1100]; + + if (!(dirp = opendir(name))) { + run_err("%s: %s", name, strerror(errno)); + return; + } + last = strrchr(name, '/'); + if (last == 0) + last = name; + else + last++; + if (pflag) { + (void) snprintf(path, sizeof(path), "T%lu 0 %lu 0\n", + (u_long) statp->st_mtime, + (u_long) statp->st_atime); + (void) atomicio(vwrite, remout, path, strlen(path)); + if (response() < 0) { + closedir(dirp); + return; + } + } + (void) snprintf(path, sizeof path, "D%04o %d %.1024s\n", + (u_int) (statp->st_mode & FILEMODEMASK), 0, last); + if (verbose_mode) + fprintf(stderr, "Entering directory: %s", path); + (void) atomicio(vwrite, remout, path, strlen(path)); + if (response() < 0) { + closedir(dirp); + return; + } + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_ino == 0) + continue; + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + if (strlen(name) + 1 + strlen(dp->d_name) >= sizeof(path) - 1) { + run_err("%s/%s: name too long", name, dp->d_name); + continue; + } + (void) snprintf(path, sizeof path, "%s/%s", name, dp->d_name); + vect[0] = path; + source(1, vect); + } + (void) closedir(dirp); + (void) atomicio(vwrite, remout, "E\n", 2); + (void) response(); +} + +void +bwlimit(int amount) +{ + static struct timeval bwstart, bwend; + static int lamt = 0, thresh = 16384; + uint64_t waitlen; + struct timespec ts, rm; + + if (!timerisset(&bwstart)) { + gettimeofday(&bwstart, NULL); + return; + } + + lamt += amount; + if (lamt < thresh) + return; + + gettimeofday(&bwend, NULL); + timersub(&bwend, &bwstart, &bwend); + if (!timerisset(&bwend)) + return; + + lamt *= 8; + waitlen = (double)1000000L * lamt / limit_rate; + + bwstart.tv_sec = waitlen / 1000000L; + bwstart.tv_usec = waitlen % 1000000L; + + if (timercmp(&bwstart, &bwend, >)) { + timersub(&bwstart, &bwend, &bwend); + + /* Adjust the wait time */ + if (bwend.tv_sec) { + thresh /= 2; + if (thresh < 2048) + thresh = 2048; + } else if (bwend.tv_usec < 100) { + thresh *= 2; + if (thresh > 32768) + thresh = 32768; + } + + TIMEVAL_TO_TIMESPEC(&bwend, &ts); + while (nanosleep(&ts, &rm) == -1) { + if (errno != EINTR) + break; + ts = rm; + } + } + + lamt = 0; + gettimeofday(&bwstart, NULL); +} + +void +sink(int argc, char **argv) +{ + static BUF buffer; + struct stat stb; + enum { + YES, NO, DISPLAYED + } wrerr; + BUF *bp; + off_t i; + size_t j, count; + int amt, exists, first, mask, mode, ofd, omode; + off_t size, statbytes; + int setimes, targisdir, wrerrno = 0; + char ch, *cp, *np, *targ, *why, *vect[1], buf[2048]; + struct timeval tv[2]; + +#define atime tv[0] +#define mtime tv[1] +#define SCREWUP(str) do { why = str; goto screwup; } while (0) + + setimes = targisdir = 0; + mask = umask(0); + if (!pflag) + (void) umask(mask); + if (argc != 1) { + run_err("ambiguous target"); + exit(1); + } + targ = *argv; + if (targetshouldbedirectory) + verifydir(targ); + + (void) atomicio(vwrite, remout, "", 1); + if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode)) + targisdir = 1; + for (first = 1;; first = 0) { + cp = buf; + if (atomicio(read, remin, cp, 1) != 1) + return; + if (*cp++ == '\n') + SCREWUP("unexpected <newline>"); + do { + if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch)) + SCREWUP("lost connection"); + *cp++ = ch; + } while (cp < &buf[sizeof(buf) - 1] && ch != '\n'); + *cp = 0; + if (verbose_mode) + fprintf(stderr, "Sink: %s", buf); + + if (buf[0] == '\01' || buf[0] == '\02') { + if (iamremote == 0) + (void) atomicio(vwrite, STDERR_FILENO, + buf + 1, strlen(buf + 1)); + if (buf[0] == '\02') + exit(1); + ++errs; + continue; + } + if (buf[0] == 'E') { + (void) atomicio(vwrite, remout, "", 1); + return; + } + if (ch == '\n') + *--cp = 0; + + cp = buf; + if (*cp == 'T') { + setimes++; + cp++; + mtime.tv_sec = strtol(cp, &cp, 10); + if (!cp || *cp++ != ' ') + SCREWUP("mtime.sec not delimited"); + mtime.tv_usec = strtol(cp, &cp, 10); + if (!cp || *cp++ != ' ') + SCREWUP("mtime.usec not delimited"); + atime.tv_sec = strtol(cp, &cp, 10); + if (!cp || *cp++ != ' ') + SCREWUP("atime.sec not delimited"); + atime.tv_usec = strtol(cp, &cp, 10); + if (!cp || *cp++ != '\0') + SCREWUP("atime.usec not delimited"); + (void) atomicio(vwrite, remout, "", 1); + continue; + } + if (*cp != 'C' && *cp != 'D') { + /* + * Check for the case "rcp remote:foo\* local:bar". + * In this case, the line "No match." can be returned + * by the shell before the rcp command on the remote is + * executed so the ^Aerror_message convention isn't + * followed. + */ + if (first) { + run_err("%s", cp); + exit(1); + } + SCREWUP("expected control record"); + } + mode = 0; + for (++cp; cp < buf + 5; cp++) { + if (*cp < '0' || *cp > '7') + SCREWUP("bad mode"); + mode = (mode << 3) | (*cp - '0'); + } + if (*cp++ != ' ') + SCREWUP("mode not delimited"); + + for (size = 0; isdigit(*cp);) + size = size * 10 + (*cp++ - '0'); + if (*cp++ != ' ') + SCREWUP("size not delimited"); + if (*cp == '\0' || strchr(cp, '/') != NULL || + strcmp(cp, ".") == 0 || strcmp(cp, "..") == 0) { + run_err("error: unexpected filename: %s", cp); + exit(1); + } + if (targisdir) { + static char *namebuf = NULL; + static size_t cursize = 0; + size_t need; + + need = strlen(targ) + strlen(cp) + 250; + if (need > cursize) { + if (namebuf) + xfree(namebuf); + namebuf = xmalloc(need); + cursize = need; + } + (void) snprintf(namebuf, need, "%s%s%s", targ, + strcmp(targ, "/") ? "/" : "", cp); + np = namebuf; + } else + np = targ; + curfile = cp; + exists = stat(np, &stb) == 0; + if (buf[0] == 'D') { + int mod_flag = pflag; + if (!iamrecursive) + SCREWUP("received directory without -r"); + if (exists) { + if (!S_ISDIR(stb.st_mode)) { + errno = ENOTDIR; + goto bad; + } + if (pflag) + (void) chmod(np, mode); + } else { + /* Handle copying from a read-only + directory */ + mod_flag = 1; + if (mkdir(np, mode | S_IRWXU) < 0) + goto bad; + } + vect[0] = xstrdup(np); + sink(1, vect); + if (setimes) { + setimes = 0; + if (utimes(vect[0], tv) < 0) + run_err("%s: set times: %s", + vect[0], strerror(errno)); + } + if (mod_flag) + (void) chmod(vect[0], mode); + if (vect[0]) + xfree(vect[0]); + continue; + } + omode = mode; + mode |= S_IWUSR; + if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) { +bad: run_err("%s: %s", np, strerror(errno)); + continue; + } + (void) atomicio(vwrite, remout, "", 1); + if ((bp = allocbuf(&buffer, ofd, 4096)) == NULL) { + (void) close(ofd); + continue; + } + cp = bp->buf; + wrerr = NO; + + statbytes = 0; +#ifdef PROGRESS_METER + if (showprogress) + start_progress_meter(curfile, size, &statbytes); +#endif + for (count = i = 0; i < size; i += 4096) { + amt = 4096; + if (i + amt > size) + amt = size - i; + count += amt; + do { + j = atomicio(read, remin, cp, amt); + if (j == 0) { + run_err("%s", j ? strerror(errno) : + "dropped connection"); + exit(1); + } + amt -= j; + cp += j; + statbytes += j; + } while (amt > 0); + + if (limit_rate) + bwlimit(4096); + + if (count == bp->cnt) { + /* Keep reading so we stay sync'd up. */ + if (wrerr == NO) { + if (atomicio(vwrite, ofd, bp->buf, + count) != count) { + wrerr = YES; + wrerrno = errno; + } + } + count = 0; + cp = bp->buf; + } + } +#ifdef PROGRESS_METER + if (showprogress) + stop_progress_meter(); +#endif + if (count != 0 && wrerr == NO && + atomicio(vwrite, ofd, bp->buf, count) != count) { + wrerr = YES; + wrerrno = errno; + } + if (wrerr == NO && ftruncate(ofd, size) != 0) { + run_err("%s: truncate: %s", np, strerror(errno)); + wrerr = DISPLAYED; + } + if (pflag) { + if (exists || omode != mode) +#ifdef HAVE_FCHMOD + if (fchmod(ofd, omode)) { +#else /* HAVE_FCHMOD */ + if (chmod(np, omode)) { +#endif /* HAVE_FCHMOD */ + run_err("%s: set mode: %s", + np, strerror(errno)); + wrerr = DISPLAYED; + } + } else { + if (!exists && omode != mode) +#ifdef HAVE_FCHMOD + if (fchmod(ofd, omode & ~mask)) { +#else /* HAVE_FCHMOD */ + if (chmod(np, omode & ~mask)) { +#endif /* HAVE_FCHMOD */ + run_err("%s: set mode: %s", + np, strerror(errno)); + wrerr = DISPLAYED; + } + } + if (close(ofd) == -1) { + wrerr = YES; + wrerrno = errno; + } + (void) response(); + if (setimes && wrerr == NO) { + setimes = 0; + if (utimes(np, tv) < 0) { + run_err("%s: set times: %s", + np, strerror(errno)); + wrerr = DISPLAYED; + } + } + switch (wrerr) { + case YES: + run_err("%s: %s", np, strerror(wrerrno)); + break; + case NO: + (void) atomicio(vwrite, remout, "", 1); + break; + case DISPLAYED: + break; + } + } +screwup: + run_err("protocol error: %s", why); + exit(1); +} + +int +response(void) +{ + char ch, *cp, resp, rbuf[2048]; + + if (atomicio(read, remin, &resp, sizeof(resp)) != sizeof(resp)) + lostconn(0); + + cp = rbuf; + switch (resp) { + case 0: /* ok */ + return (0); + default: + *cp++ = resp; + /* FALLTHROUGH */ + case 1: /* error, followed by error msg */ + case 2: /* fatal error, "" */ + do { + if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch)) + lostconn(0); + *cp++ = ch; + } while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n'); + + if (!iamremote) + (void) atomicio(vwrite, STDERR_FILENO, rbuf, cp - rbuf); + ++errs; + if (resp == 1) + return (-1); + exit(1); + } + /* NOTREACHED */ +} + +void +usage(void) +{ + (void) fprintf(stderr, + "usage: scp [-1246BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n" + " [-l limit] [-P port] [-S program]\n" + " [[user@]host1:]file1 [...] [[user@]host2:]file2\n"); + exit(1); +} + +void +run_err(const char *fmt,...) +{ + static FILE *fp = NULL; + va_list ap; + + ++errs; + if (fp == NULL && !(fp = fdopen(remout, "w"))) + return; + (void) fprintf(fp, "%c", 0x01); + (void) fprintf(fp, "scp: "); + va_start(ap, fmt); + (void) vfprintf(fp, fmt, ap); + va_end(ap); + (void) fprintf(fp, "\n"); + (void) fflush(fp); + + if (!iamremote) { + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + } +} + +void +verifydir(char *cp) +{ + struct stat stb; + + if (!stat(cp, &stb)) { + if (S_ISDIR(stb.st_mode)) + return; + errno = ENOTDIR; + } + run_err("%s: %s", cp, strerror(errno)); + killchild(0); +} + +int +okname(char *cp0) +{ + int c; + char *cp; + + cp = cp0; + do { + c = (int)*cp; + if (c & 0200) + goto bad; + if (!isalpha(c) && !isdigit(c)) { + switch (c) { + case '\'': + case '"': + case '`': + case ' ': + case '#': + goto bad; + default: + break; + } + } + } while (*++cp); + return (1); + +bad: fprintf(stderr, "%s: invalid user name\n", cp0); + return (0); +} + +BUF * +allocbuf(BUF *bp, int fd, int blksize) +{ + size_t size; +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE + struct stat stb; + + if (fstat(fd, &stb) < 0) { + run_err("fstat: %s", strerror(errno)); + return (0); + } + size = roundup(stb.st_blksize, blksize); + if (size == 0) + size = blksize; +#else /* HAVE_STRUCT_STAT_ST_BLKSIZE */ + size = blksize; +#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */ + if (bp->cnt >= size) + return (bp); + if (bp->buf == NULL) + bp->buf = xmalloc(size); + else + bp->buf = xrealloc(bp->buf, size); + memset(bp->buf, 0, size); + bp->cnt = size; + return (bp); +} + +void +lostconn(int signo) +{ + if (!iamremote) + write(STDERR_FILENO, "lost connection\n", 16); + if (signo) + _exit(1); + else + exit(1); +} diff --git a/src/scpmisc.c b/src/scpmisc.c new file mode 100644 index 0000000..c2f053e --- /dev/null +++ b/src/scpmisc.c @@ -0,0 +1,253 @@ +/* Dropbear Note: This file is based on OpenSSH 4.3p2. Avoid unnecessary + changes to simplify future updates */ + +/* + * 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. + * + * 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. + */ + +/*RCSID("OpenBSD: misc.c,v 1.22 2003/09/18 08:49:45 markus Exp ");*/ + +/* For xmalloc, xfree etc: + * Author: Tatu Ylonen <ylo@cs.hut.fi> + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + * Versions of malloc and friends that check their results, and never return + * failure (they call fatal if they encounter an error). + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +/*RCSID("OpenBSD: xmalloc.c,v 1.16 2001/07/23 18:21:46 stevesk Exp ");*/ + +#define _GNU_SOURCE +#include "includes.h" +#include "scpmisc.h" + +void * +xmalloc(size_t size) +{ + void *ptr; + + if (size == 0) { + fprintf(stderr, "xmalloc: zero size\n"); + exit(EXIT_FAILURE); + } + ptr = malloc(size); + if (ptr == NULL) { + fprintf(stderr, "xmalloc: out of memory (allocating %lu bytes)\n", (u_long) size); + exit(EXIT_FAILURE); + } + return ptr; +} + +void * +xrealloc(void *ptr, size_t new_size) +{ + void *new_ptr; + + if (new_size == 0) { + fprintf(stderr, "xrealloc: zero size\n"); + exit(EXIT_FAILURE); + } + if (ptr == NULL) + new_ptr = malloc(new_size); + else + new_ptr = realloc(ptr, new_size); + if (new_ptr == NULL) { + fprintf(stderr, "xrealloc: out of memory (new_size %lu bytes)\n", (u_long) new_size); + exit(EXIT_FAILURE); + } + return new_ptr; +} + +void +xfree(void *ptr) +{ + if (ptr == NULL) { + fprintf(stderr, "xfree: NULL pointer given as argument\n"); + exit(EXIT_FAILURE); + } + free(ptr); +} + +char * +xstrdup(const char *str) +{ + size_t len; + char *cp; + + len = strlen(str) + 1; + cp = xmalloc(len); + strlcpy(cp, str, len); + return cp; +} + +char * +cleanhostname(char *host) +{ + if (*host == '[' && host[strlen(host) - 1] == ']') { + host[strlen(host) - 1] = '\0'; + return (host + 1); + } else + return host; +} + +char * +colon(char *cp) +{ + int flag = 0; + + if (*cp == ':') /* Leading colon is part of file name. */ + return (0); + if (*cp == '[') + flag = 1; + + for (; *cp; ++cp) { + if (*cp == '@' && *(cp+1) == '[') + flag = 1; + if (*cp == ']' && *(cp+1) == ':' && flag) + return (cp+1); + if (*cp == ':' && !flag) + return (cp); + if (*cp == '/') + return (0); + } + return (0); +} + +/* function to assist building execv() arguments */ +void +addargs(arglist *args, char *fmt, ...) +{ + va_list ap; + char *cp; + u_int nalloc; + int r; + + va_start(ap, fmt); + r = vasprintf(&cp, fmt, ap); + va_end(ap); + if (r == -1) + fatal("addargs: argument too long"); + + nalloc = args->nalloc; + if (args->list == NULL) { + nalloc = 32; + args->num = 0; + } else if (args->num+2 >= nalloc) + nalloc *= 2; + + args->list = xrealloc(args->list, nalloc * sizeof(char *)); + args->nalloc = nalloc; + args->list[args->num++] = cp; + args->list[args->num] = NULL; +} + +void +replacearg(arglist *args, u_int which, char *fmt, ...) +{ + va_list ap; + char *cp; + int r; + + va_start(ap, fmt); + r = vasprintf(&cp, fmt, ap); + va_end(ap); + if (r == -1) + fatal("replacearg: argument too long"); + + if (which >= args->num) + fatal("replacearg: tried to replace invalid arg %d >= %d", + which, args->num); + xfree(args->list[which]); + args->list[which] = cp; +} + +void +freeargs(arglist *args) +{ + u_int i; + + if (args->list != NULL) { + for (i = 0; i < args->num; i++) + xfree(args->list[i]); + xfree(args->list); + args->nalloc = args->num = 0; + args->list = NULL; + } +} + +/* + * NB. duplicate __progname in case it is an alias for argv[0] + * Otherwise it may get clobbered by setproctitle() + */ +char *ssh_get_progname(char *argv0) +{ + char *p; + + if (argv0 == NULL) + return ("unknown"); /* XXX */ + p = strrchr(argv0, '/'); + if (p == NULL) + p = argv0; + else + p++; + + return (xstrdup(p)); +} + +void fatal(char* fmt,...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fputc('\n', stderr); + exit(255); +} + +void +sanitise_stdfd(void) +{ + int nullfd, dupfd; + + if ((nullfd = dupfd = open(DROPBEAR_PATH_DEVNULL, O_RDWR)) == -1) { + fprintf(stderr, "Couldn't open /dev/null: %s", strerror(errno)); + exit(1); + } + while (++dupfd <= 2) { + /* Only clobber closed fds */ + if (fcntl(dupfd, F_GETFL, 0) >= 0) + continue; + if (dup2(nullfd, dupfd) == -1) { + fprintf(stderr, "dup2: %s", strerror(errno)); + exit(1); + } + } + if (nullfd > 2) + close(nullfd); +} diff --git a/src/scpmisc.h b/src/scpmisc.h new file mode 100644 index 0000000..369b327 --- /dev/null +++ b/src/scpmisc.h @@ -0,0 +1,66 @@ +/* $OpenBSD: misc.h,v 1.12 2002/03/19 10:49:35 markus Exp $ */ + +/* + * Author: Tatu Ylonen <ylo@cs.hut.fi> + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +char *chop(char *); +char *strdelim(char **); +void set_nonblock(int); +void unset_nonblock(int); +void set_nodelay(int); +int a2port(const char *); +char *cleanhostname(char *); +char *colon(char *); +long convtime(const char *); + +struct passwd *pwcopy(struct passwd *); + +typedef struct arglist arglist; +struct arglist { + char **list; + u_int num; + u_int nalloc; +}; +void addargs(arglist *, char *, ...); +void replacearg(arglist *, u_int, char *, ...); +void freeargs(arglist *); + +/* from xmalloc.h */ +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +void xfree(void *); +char *xstrdup(const char *); + +char *ssh_get_progname(char *); +void fatal(char* fmt,...); +void sanitise_stdfd(void); + +/* Required for non-BSD platforms, from OpenSSH's defines.h */ +#ifndef timersub +#define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) +#endif + +#ifndef TIMEVAL_TO_TIMESPEC +#define TIMEVAL_TO_TIMESPEC(tv, ts) { \ + (ts)->tv_sec = (tv)->tv_sec; \ + (ts)->tv_nsec = (tv)->tv_usec * 1000; \ +} +#endif + diff --git a/src/service.h b/src/service.h new file mode 100644 index 0000000..eaa7ff6 --- /dev/null +++ b/src/service.h @@ -0,0 +1,30 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_SERVICE_H_ +#define DROPBEAR_SERVICE_H_ + +void recv_msg_service_request(void); /* Server */ + +#endif /* DROPBEAR_SERVICE_H_ */ diff --git a/src/session.h b/src/session.h new file mode 100644 index 0000000..6706592 --- /dev/null +++ b/src/session.h @@ -0,0 +1,352 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_SESSION_H_ +#define DROPBEAR_SESSION_H_ + +#include "includes.h" +#include "buffer.h" +#include "signkey.h" +#include "kex.h" +#include "auth.h" +#include "channel.h" +#include "queue.h" +#include "listener.h" +#include "packet.h" +#include "tcpfwd.h" +#include "chansession.h" +#include "dbutil.h" +#include "netio.h" +#if DROPBEAR_PLUGIN +#include "pubkeyapi.h" +#endif +#include "gcm.h" +#include "chachapoly.h" + +void common_session_init(int sock_in, int sock_out); +void session_loop(void(*loophandler)(void)) ATTRIB_NORETURN; +void session_cleanup(void); +void send_session_identification(void); +void send_msg_ignore(void); +void ignore_recv_response(void); + +void update_channel_prio(void); + +const char* get_user_shell(void); +void fill_passwd(const char* username); + +/* Server */ +void svr_session(int sock, int childpipe) ATTRIB_NORETURN; +void svr_dropbear_exit(int exitcode, const char* format, va_list param) ATTRIB_NORETURN; +void svr_dropbear_log(int priority, const char* format, va_list param); + +/* Client */ +void cli_session(int sock_in, int sock_out, struct dropbear_progress_connection *progress, pid_t proxy_cmd_pid) ATTRIB_NORETURN; +void cli_connected(int result, int sock, void* userdata, const char *errstring); +void cli_dropbear_exit(int exitcode, const char* format, va_list param) ATTRIB_NORETURN; +void cli_dropbear_log(int priority, const char* format, va_list param); +void cleantext(char* dirtytext); +void kill_proxy_command(void); + +/* crypto parameters that are stored individually for transmit and receive */ +struct key_context_directional { + const struct dropbear_cipher *algo_crypt; + const struct dropbear_cipher_mode *crypt_mode; + const struct dropbear_hash *algo_mac; + int hash_index; /* lookup for libtomcrypt */ + int algo_comp; /* compression */ +#ifndef DISABLE_ZLIB + z_streamp zstream; +#endif + /* actual keys */ + union { +#if DROPBEAR_ENABLE_CBC_MODE + symmetric_CBC cbc; +#endif +#if DROPBEAR_ENABLE_CTR_MODE + symmetric_CTR ctr; +#endif +#if DROPBEAR_ENABLE_GCM_MODE + dropbear_gcm_state gcm; +#endif +#if DROPBEAR_CHACHA20POLY1305 + dropbear_chachapoly_state chachapoly; +#endif + } cipher_state; + unsigned char mackey[MAX_MAC_LEN]; + int valid; +}; + +struct key_context { + + struct key_context_directional recv; + struct key_context_directional trans; + + const struct dropbear_kex *algo_kex; + enum signkey_type algo_hostkey; /* server key type */ + enum signature_type algo_signature; /* server signature type */ + + int allow_compress; /* whether compression has started (useful in + zlib@openssh.com delayed compression case) */ +}; + +struct packetlist; +struct packetlist { + struct packetlist *next; + buffer * payload; +}; + +struct sshsession { + + /* Is it a client or server? */ + unsigned char isserver; + + time_t connect_time; /* time the connection was established + (cleared after auth once we're not + respecting AUTH_TIMEOUT any more). + A monotonic time, not realworld */ + + int sock_in; + int sock_out; + + /* remotehost will be initially NULL as we delay + * reading the remote version string. it will be set + * by the time any recv_() packet methods are called */ + char *remoteident; + + int maxfd; /* the maximum file descriptor to check with select() */ + + + /* Packet buffers/values etc */ + buffer *writepayload; /* Unencrypted payload to write - this is used + throughout the code, as handlers fill out this + buffer with the packet to send. */ + struct Queue writequeue; /* A queue of encrypted packets to send */ + unsigned int writequeue_len; /* Number of bytes pending to send in writequeue */ + buffer *readbuf; /* From the wire, decrypted in-place */ + buffer *payload; /* Post-decompression, the actual SSH packet. + May have extra data at the beginning, will be + passed to packet processing functions positioned past + that, see payload_beginning */ + unsigned int payload_beginning; + unsigned int transseq, recvseq; /* Sequence IDs */ + + /* Packet-handling flags */ + const packettype * packettypes; /* Packet handler mappings for this + session, see process-packet.c */ + + unsigned dataallowed : 1; /* whether we can send data packets or we are in + the middle of a KEX or something */ + + unsigned char requirenext; /* byte indicating what packets we require next, + or 0x00 for any. */ + + unsigned char ignorenext; /* whether to ignore the next packet, + used for kex_follows stuff */ + + unsigned char lastpacket; /* What the last received packet type was */ + + int signal_pipe[2]; /* stores endpoints of a self-pipe used for + race-free signal handling */ + int channel_signal_pending; /* Flag set when the signal pipe is triggered */ + + m_list conn_pending; + + /* time of the last packet send/receive, for keepalive. Not real-world clock */ + time_t last_packet_time_keepalive_sent; + time_t last_packet_time_keepalive_recv; + time_t last_packet_time_any_sent; + + time_t last_packet_time_idle; /* time of the last packet transmission or receive, for + idle timeout purposes so ignores SSH_MSG_IGNORE + or responses to keepalives. Not real-world clock */ + + + /* KEX/encryption related */ + struct KEXState kexstate; + struct key_context *keys; + struct key_context *newkeys; + buffer *session_id; /* this is the hash from the first kex */ + /* The below are used temporarily during kex, are freed after use */ + mp_int * dh_K; /* SSH_MSG_KEXDH_REPLY and sending SSH_MSH_NEWKEYS */ + buffer *hash; /* the session hash */ + buffer* kexhashbuf; /* session hash buffer calculated from various packets*/ + buffer* transkexinit; /* the kexinit packet we send should be kept so we + can add it to the hash when generating keys */ + + /* Enables/disables compression */ + algo_type *compress_algos; + + /* Other side allows SSH_MSG_EXT_INFO. Currently only set for server */ + int allow_ext_info; + + /* a list of queued replies that should be sent after a KEX has + concluded (ie, while dataallowed was unset)*/ + struct packetlist *reply_queue_head, *reply_queue_tail; + + void(*remoteclosed)(void); /* A callback to handle closure of the + remote connection */ + + void(*extra_session_cleanup)(void); /* client or server specific cleanup */ + void(*send_kex_first_guess)(void); + + struct AuthState authstate; /* Common amongst client and server, since most + struct elements are common */ + + /* Channel related */ + struct Channel ** channels; /* these pointers may be null */ + unsigned int chansize; /* the number of Channel*s allocated for channels */ + unsigned int chancount; /* the number of Channel*s in use */ + const struct ChanType **chantypes; /* The valid channel types */ + + /* TCP priority level for the main "port 22" tcp socket */ + enum dropbear_prio socket_prio; + + /* TCP forwarding - where manage listeners */ + struct Listener ** listeners; + unsigned int listensize; + + /* Whether to allow binding to privileged ports (<1024). This doesn't + * really belong here, but nowhere else fits nicely */ + int allowprivport; + + /* this is set when we get SIGINT or SIGTERM, the handler is in main.c */ + volatile int exitflag; + /* set once the ses structure (and cli_ses/svr_ses) have been populated to their initial state */ + int init_done; + +#if DROPBEAR_PLUGIN + struct PluginSession * plugin_session; +#endif +}; + +struct serversession { + + /* Server specific options */ + int childpipe; /* kept open until we successfully authenticate */ + /* userauth */ + + struct ChildPid * childpids; /* array of mappings childpid<->channel */ + unsigned int childpidsize; + + /* Used to avoid a race in the exit returncode handling - see + * svr-chansession.c for details */ + struct exitinfo lastexit; + + /* The numeric address they connected from, used for logging */ + char * addrstring; + + /* The resolved remote address, used for lastlog etc */ + char *remotehost; + +#if DROPBEAR_VFORK + pid_t server_pid; +#endif + +#if DROPBEAR_PLUGIN + /* The shared library handle */ + void *plugin_handle; + + /* The instance created by the plugin_new function */ + struct PluginInstance *plugin_instance; +#endif +}; + +typedef enum { + KEX_NOTHING, + KEXINIT_RCVD, + KEXDH_INIT_SENT, + KEXDONE +} cli_kex_state; + +typedef enum { + STATE_NOTHING, + USERAUTH_WAIT, + USERAUTH_REQ_SENT, + USERAUTH_FAIL_RCVD, + USERAUTH_SUCCESS_RCVD, + SESSION_RUNNING +} cli_state; + +struct clientsession { + + /* XXX - move these to kexstate? */ + struct kex_dh_param *dh_param; + struct kex_ecdh_param *ecdh_param; + struct kex_curve25519_param *curve25519_param; + const struct dropbear_kex *param_kex_algo; /* KEX algorithm corresponding to current dh_e and dh_x */ + + cli_kex_state kex_state; /* Used for progressing KEX */ + cli_state state; /* Used to progress auth/channelsession etc */ + + int tty_raw_mode; /* Whether we're in raw mode (and have to clean up) */ + struct termios saved_tio; + int stdincopy; + int stdinflags; + int stdoutcopy; + int stdoutflags; + int stderrcopy; + int stderrflags; + + /* for escape char handling */ + int last_char; + + volatile int winchange; /* Set to 1 when a windowchange signal happens */ + + int lastauthtype; /* either AUTH_TYPE_PUBKEY or AUTH_TYPE_PASSWORD, + for the last type of auth we tried */ + int is_trivial_auth; + int ignore_next_auth_response; +#if DROPBEAR_CLI_INTERACT_AUTH + int auth_interact_failed; /* flag whether interactive auth can still + be used */ + int interact_request_received; /* flag whether we've received an + info request from the server for + interactive auth.*/ +#endif + sign_key *lastprivkey; + + buffer *server_sig_algs; + + int retval; /* What the command exit status was - we emulate it */ +#if 0 + TODO + struct AgentkeyList *agentkeys; /* Keys to use for public-key auth */ +#endif + + pid_t proxy_cmd_pid; +}; + +/* Global structs storing the state */ +extern struct sshsession ses; + +#if DROPBEAR_SERVER +extern struct serversession svr_ses; +#endif /* DROPBEAR_SERVER */ + +#if DROPBEAR_CLIENT +extern struct clientsession cli_ses; +#endif /* DROPBEAR_CLIENT */ + +#endif /* DROPBEAR_SESSION_H_ */ diff --git a/src/signkey.c b/src/signkey.c new file mode 100644 index 0000000..0aacddb --- /dev/null +++ b/src/signkey.c @@ -0,0 +1,785 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "signkey.h" +#include "buffer.h" +#include "ssh.h" +#include "ecdsa.h" +#include "sk-ecdsa.h" +#include "sk-ed25519.h" +#include "rsa.h" +#include "dss.h" +#include "ed25519.h" + +static const char * const signkey_names[DROPBEAR_SIGNKEY_NUM_NAMED] = { +#if DROPBEAR_RSA + "ssh-rsa", +#endif +#if DROPBEAR_DSS + "ssh-dss", +#endif +#if DROPBEAR_ECDSA + "ecdsa-sha2-nistp256", + "ecdsa-sha2-nistp384", + "ecdsa-sha2-nistp521", +#if DROPBEAR_SK_ECDSA + "sk-ecdsa-sha2-nistp256@openssh.com", +#endif /* DROPBEAR_SK_ECDSA */ +#endif /* DROPBEAR_ECDSA */ +#if DROPBEAR_ED25519 + "ssh-ed25519", +#if DROPBEAR_SK_ED25519 + "sk-ssh-ed25519@openssh.com", +#endif /* DROPBEAR_SK_ED25519 */ +#endif /* DROPBEAR_ED25519 */ + /* "rsa-sha2-256" is special-cased below since it is only a signature name, not key type */ +}; + +/* malloc a new sign_key and set the dss and rsa keys to NULL */ +sign_key * new_sign_key() { + + sign_key * ret; + + ret = (sign_key*)m_malloc(sizeof(sign_key)); + ret->type = DROPBEAR_SIGNKEY_NONE; + ret->source = SIGNKEY_SOURCE_INVALID; + return ret; +} + +/* Returns key name corresponding to the type. Exits fatally + * if the type is invalid */ +const char* signkey_name_from_type(enum signkey_type type, unsigned int *namelen) { + if (type >= DROPBEAR_SIGNKEY_NUM_NAMED) { + dropbear_exit("Bad key type %d", type); + } + + if (namelen) { + *namelen = strlen(signkey_names[type]); + } + return signkey_names[type]; +} + +/* Returns DROPBEAR_SIGNKEY_NONE if none match */ +enum signkey_type signkey_type_from_name(const char* name, unsigned int namelen) { + int i; + for (i = 0; i < DROPBEAR_SIGNKEY_NUM_NAMED; i++) { + const char *fixed_name = signkey_names[i]; + if (namelen == strlen(fixed_name) + && memcmp(fixed_name, name, namelen) == 0) { + +#if DROPBEAR_ECDSA + /* Some of the ECDSA key sizes are defined even if they're not compiled in */ + if (0 +#if !DROPBEAR_ECC_256 + || i == DROPBEAR_SIGNKEY_ECDSA_NISTP256 +#endif +#if !DROPBEAR_ECC_384 + || i == DROPBEAR_SIGNKEY_ECDSA_NISTP384 +#endif +#if !DROPBEAR_ECC_521 + || i == DROPBEAR_SIGNKEY_ECDSA_NISTP521 +#endif + ) { + TRACE(("attempt to use ecdsa type %d not compiled in", i)) + return DROPBEAR_SIGNKEY_NONE; + } +#endif + + return (enum signkey_type)i; + } + } + + TRACE(("signkey_type_from_name unexpected key type.")) + + return DROPBEAR_SIGNKEY_NONE; +} + +/* Special case for rsa-sha2-256. This could be generalised if more + signature names are added that aren't 1-1 with public key names */ +const char* signature_name_from_type(enum signature_type type, unsigned int *namelen) { +#if DROPBEAR_RSA +#if DROPBEAR_RSA_SHA256 + if (type == DROPBEAR_SIGNATURE_RSA_SHA256) { + if (namelen) { + *namelen = strlen(SSH_SIGNATURE_RSA_SHA256); + } + return SSH_SIGNATURE_RSA_SHA256; + } +#endif +#if DROPBEAR_RSA_SHA1 + if (type == DROPBEAR_SIGNATURE_RSA_SHA1) { + if (namelen) { + *namelen = strlen(SSH_SIGNKEY_RSA); + } + return SSH_SIGNKEY_RSA; + } +#endif +#endif /* DROPBEAR_RSA */ + return signkey_name_from_type((enum signkey_type)type, namelen); +} + +/* Returns DROPBEAR_SIGNATURE_NONE if none match */ +enum signature_type signature_type_from_name(const char* name, unsigned int namelen) { +#if DROPBEAR_RSA +#if DROPBEAR_RSA_SHA256 + if (namelen == strlen(SSH_SIGNATURE_RSA_SHA256) + && memcmp(name, SSH_SIGNATURE_RSA_SHA256, namelen) == 0) { + return DROPBEAR_SIGNATURE_RSA_SHA256; + } +#endif +#if DROPBEAR_RSA_SHA1 + if (namelen == strlen(SSH_SIGNKEY_RSA) + && memcmp(name, SSH_SIGNKEY_RSA, namelen) == 0) { + return DROPBEAR_SIGNATURE_RSA_SHA1; + } +#endif +#endif /* DROPBEAR_RSA */ + return (enum signature_type)signkey_type_from_name(name, namelen); +} + +/* Returns the signature type from a key type. Must not be called + with RSA keytype */ +enum signature_type signature_type_from_signkey(enum signkey_type keytype) { +#if DROPBEAR_RSA + assert(keytype != DROPBEAR_SIGNKEY_RSA); +#endif + assert(keytype < DROPBEAR_SIGNKEY_NUM_NAMED); + return (enum signature_type)keytype; +} + +enum signkey_type signkey_type_from_signature(enum signature_type sigtype) { +#if DROPBEAR_RSA +#if DROPBEAR_RSA_SHA256 + if (sigtype == DROPBEAR_SIGNATURE_RSA_SHA256) { + return DROPBEAR_SIGNKEY_RSA; + } +#endif +#if DROPBEAR_RSA_SHA1 + if (sigtype == DROPBEAR_SIGNATURE_RSA_SHA1) { + return DROPBEAR_SIGNKEY_RSA; + } +#endif +#endif /* DROPBEAR_RSA */ + assert((int)sigtype < (int)DROPBEAR_SIGNKEY_NUM_NAMED); + return (enum signkey_type)sigtype; +} + +/* Returns a pointer to the key part specific to "type". +Be sure to check both (ret != NULL) and (*ret != NULL) */ +void ** +signkey_key_ptr(sign_key *key, enum signkey_type type) { + switch (type) { +#if DROPBEAR_ED25519 + case DROPBEAR_SIGNKEY_ED25519: +#if DROPBEAR_SK_ED25519 + case DROPBEAR_SIGNKEY_SK_ED25519: +#endif + return (void**)&key->ed25519key; +#endif +#if DROPBEAR_ECDSA +#if DROPBEAR_ECC_256 + case DROPBEAR_SIGNKEY_ECDSA_NISTP256: +#if DROPBEAR_SK_ECDSA + case DROPBEAR_SIGNKEY_SK_ECDSA_NISTP256: +#endif + return (void**)&key->ecckey256; +#endif +#if DROPBEAR_ECC_384 + case DROPBEAR_SIGNKEY_ECDSA_NISTP384: + return (void**)&key->ecckey384; +#endif +#if DROPBEAR_ECC_521 + case DROPBEAR_SIGNKEY_ECDSA_NISTP521: + return (void**)&key->ecckey521; +#endif +#endif /* DROPBEAR_ECDSA */ +#if DROPBEAR_RSA + case DROPBEAR_SIGNKEY_RSA: + return (void**)&key->rsakey; +#endif +#if DROPBEAR_DSS + case DROPBEAR_SIGNKEY_DSS: + return (void**)&key->dsskey; +#endif + default: + return NULL; + } +} + +/* returns DROPBEAR_SUCCESS on success, DROPBEAR_FAILURE on fail. + * type should be set by the caller to specify the type to read, and + * on return is set to the type read (useful when type = _ANY) */ +int buf_get_pub_key(buffer *buf, sign_key *key, enum signkey_type *type) { + + char *ident; + unsigned int len; + enum signkey_type keytype; + int ret = DROPBEAR_FAILURE; + + TRACE2(("enter buf_get_pub_key")) + + ident = buf_getstring(buf, &len); + keytype = signkey_type_from_name(ident, len); + m_free(ident); + + if (*type != DROPBEAR_SIGNKEY_ANY && *type != keytype) { + TRACE(("buf_get_pub_key bad type - got %d, expected %d", keytype, *type)) + return DROPBEAR_FAILURE; + } + + TRACE2(("buf_get_pub_key keytype is %d", keytype)) + + *type = keytype; + + /* Rewind the buffer back before "ssh-rsa" etc */ + buf_decrpos(buf, len + 4); + +#if DROPBEAR_DSS + if (keytype == DROPBEAR_SIGNKEY_DSS) { + dss_key_free(key->dsskey); + key->dsskey = m_malloc(sizeof(*key->dsskey)); + ret = buf_get_dss_pub_key(buf, key->dsskey); + if (ret == DROPBEAR_FAILURE) { + dss_key_free(key->dsskey); + key->dsskey = NULL; + } + } +#endif +#if DROPBEAR_RSA + if (keytype == DROPBEAR_SIGNKEY_RSA) { + rsa_key_free(key->rsakey); + key->rsakey = m_malloc(sizeof(*key->rsakey)); + ret = buf_get_rsa_pub_key(buf, key->rsakey); + if (ret == DROPBEAR_FAILURE) { + rsa_key_free(key->rsakey); + key->rsakey = NULL; + } + } +#endif +#if DROPBEAR_ECDSA + if (signkey_is_ecdsa(keytype) +#if DROPBEAR_SK_ECDSA + || keytype == DROPBEAR_SIGNKEY_SK_ECDSA_NISTP256 +#endif + ) { + ecc_key **eck = (ecc_key**)signkey_key_ptr(key, keytype); + if (eck) { + if (*eck) { + ecc_free(*eck); + m_free(*eck); + *eck = NULL; + } + *eck = buf_get_ecdsa_pub_key(buf); + if (*eck) { + ret = DROPBEAR_SUCCESS; + } + } + } +#endif +#if DROPBEAR_ED25519 + if (keytype == DROPBEAR_SIGNKEY_ED25519 +#if DROPBEAR_SK_ED25519 + || keytype == DROPBEAR_SIGNKEY_SK_ED25519 +#endif + ) { + ed25519_key_free(key->ed25519key); + key->ed25519key = m_malloc(sizeof(*key->ed25519key)); + ret = buf_get_ed25519_pub_key(buf, key->ed25519key, keytype); + if (ret == DROPBEAR_FAILURE) { + m_free(key->ed25519key); + key->ed25519key = NULL; + } + } +#endif + +#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 + if (0 +#if DROPBEAR_SK_ED25519 + || keytype == DROPBEAR_SIGNKEY_SK_ED25519 +#endif +#if DROPBEAR_SK_ECDSA + || keytype == DROPBEAR_SIGNKEY_SK_ECDSA_NISTP256 +#endif + ) { + key->sk_app = buf_getstring(buf, &key->sk_applen); + } +#endif + + TRACE2(("leave buf_get_pub_key")) + + return ret; +} + +/* returns DROPBEAR_SUCCESS on success, DROPBEAR_FAILURE on fail. + * type should be set by the caller to specify the type to read, and + * on return is set to the type read (useful when type = _ANY) */ +int buf_get_priv_key(buffer *buf, sign_key *key, enum signkey_type *type) { + + char *ident; + unsigned int len; + enum signkey_type keytype; + int ret = DROPBEAR_FAILURE; + + TRACE2(("enter buf_get_priv_key")) + + ident = buf_getstring(buf, &len); + keytype = signkey_type_from_name(ident, len); + m_free(ident); + + if (*type != DROPBEAR_SIGNKEY_ANY && *type != keytype) { + TRACE(("wrong key type: %d %d", *type, keytype)) + return DROPBEAR_FAILURE; + } + + *type = keytype; + + /* Rewind the buffer back before "ssh-rsa" etc */ + buf_decrpos(buf, len + 4); + +#if DROPBEAR_DSS + if (keytype == DROPBEAR_SIGNKEY_DSS) { + dss_key_free(key->dsskey); + key->dsskey = m_malloc(sizeof(*key->dsskey)); + ret = buf_get_dss_priv_key(buf, key->dsskey); + if (ret == DROPBEAR_FAILURE) { + dss_key_free(key->dsskey); + key->dsskey = NULL; + } + } +#endif +#if DROPBEAR_RSA + if (keytype == DROPBEAR_SIGNKEY_RSA) { + rsa_key_free(key->rsakey); + key->rsakey = m_malloc(sizeof(*key->rsakey)); + ret = buf_get_rsa_priv_key(buf, key->rsakey); + if (ret == DROPBEAR_FAILURE) { + rsa_key_free(key->rsakey); + key->rsakey = NULL; + } + } +#endif +#if DROPBEAR_ECDSA + if (signkey_is_ecdsa(keytype)) { + ecc_key **eck = (ecc_key**)signkey_key_ptr(key, keytype); + if (eck) { + if (*eck) { + ecc_free(*eck); + m_free(*eck); + *eck = NULL; + } + *eck = buf_get_ecdsa_priv_key(buf); + if (*eck) { + ret = DROPBEAR_SUCCESS; + } + } + } +#endif +#if DROPBEAR_ED25519 + if (keytype == DROPBEAR_SIGNKEY_ED25519) { + ed25519_key_free(key->ed25519key); + key->ed25519key = m_malloc(sizeof(*key->ed25519key)); + ret = buf_get_ed25519_priv_key(buf, key->ed25519key); + if (ret == DROPBEAR_FAILURE) { + m_free(key->ed25519key); + key->ed25519key = NULL; + } + } +#endif + + TRACE2(("leave buf_get_priv_key")) + + return ret; + +} + +/* type is either DROPBEAR_SIGNKEY_DSS or DROPBEAR_SIGNKEY_RSA */ +void buf_put_pub_key(buffer* buf, sign_key *key, enum signkey_type type) { + + buffer *pubkeys; + + TRACE2(("enter buf_put_pub_key")) + pubkeys = buf_new(MAX_PUBKEY_SIZE); + +#if DROPBEAR_DSS + if (type == DROPBEAR_SIGNKEY_DSS) { + buf_put_dss_pub_key(pubkeys, key->dsskey); + } +#endif +#if DROPBEAR_RSA + if (type == DROPBEAR_SIGNKEY_RSA) { + buf_put_rsa_pub_key(pubkeys, key->rsakey); + } +#endif +#if DROPBEAR_ECDSA + if (signkey_is_ecdsa(type)) { + ecc_key **eck = (ecc_key**)signkey_key_ptr(key, type); + if (eck && *eck) { + buf_put_ecdsa_pub_key(pubkeys, *eck); + } + } +#endif +#if DROPBEAR_ED25519 + if (type == DROPBEAR_SIGNKEY_ED25519 +#if DROPBEAR_SK_ED25519 + || type == DROPBEAR_SIGNKEY_SK_ED25519 +#endif + ) { + buf_put_ed25519_pub_key(pubkeys, key->ed25519key); + } +#endif + if (pubkeys->len == 0) { + dropbear_exit("Bad key types in buf_put_pub_key"); + } + + buf_putbufstring(buf, pubkeys); + buf_free(pubkeys); + TRACE2(("leave buf_put_pub_key")) +} + +/* type is either DROPBEAR_SIGNKEY_DSS or DROPBEAR_SIGNKEY_RSA */ +void buf_put_priv_key(buffer* buf, sign_key *key, enum signkey_type type) { + + TRACE(("enter buf_put_priv_key")) + TRACE(("type is %d", type)) + +#if DROPBEAR_DSS + if (type == DROPBEAR_SIGNKEY_DSS) { + buf_put_dss_priv_key(buf, key->dsskey); + TRACE(("leave buf_put_priv_key: dss done")) + return; + } +#endif +#if DROPBEAR_RSA + if (type == DROPBEAR_SIGNKEY_RSA) { + buf_put_rsa_priv_key(buf, key->rsakey); + TRACE(("leave buf_put_priv_key: rsa done")) + return; + } +#endif +#if DROPBEAR_ECDSA + if (signkey_is_ecdsa(type)) { + ecc_key **eck = (ecc_key**)signkey_key_ptr(key, type); + if (eck && *eck) { + buf_put_ecdsa_priv_key(buf, *eck); + TRACE(("leave buf_put_priv_key: ecdsa done")) + return; + } + } +#endif +#if DROPBEAR_ED25519 + if (type == DROPBEAR_SIGNKEY_ED25519) { + buf_put_ed25519_priv_key(buf, key->ed25519key); + TRACE(("leave buf_put_priv_key: ed25519 done")) + return; + } +#endif + dropbear_exit("Bad key types in put pub key"); +} + +void sign_key_free(sign_key *key) { + + TRACE2(("enter sign_key_free")) + +#if DROPBEAR_DSS + dss_key_free(key->dsskey); + key->dsskey = NULL; +#endif +#if DROPBEAR_RSA + rsa_key_free(key->rsakey); + key->rsakey = NULL; +#endif +#if DROPBEAR_ECDSA +#if DROPBEAR_ECC_256 + if (key->ecckey256) { + ecc_free(key->ecckey256); + m_free(key->ecckey256); + key->ecckey256 = NULL; + } +#endif +#if DROPBEAR_ECC_384 + if (key->ecckey384) { + ecc_free(key->ecckey384); + m_free(key->ecckey384); + key->ecckey384 = NULL; + } +#endif +#if DROPBEAR_ECC_521 + if (key->ecckey521) { + ecc_free(key->ecckey521); + m_free(key->ecckey521); + key->ecckey521 = NULL; + } +#endif +#endif +#if DROPBEAR_ED25519 + ed25519_key_free(key->ed25519key); + key->ed25519key = NULL; +#endif + + m_free(key->filename); +#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 + if (key->sk_app) { + m_free(key->sk_app); + } +#endif + + m_free(key); + TRACE2(("leave sign_key_free")) +} + +static char * sign_key_sha256_fingerprint(const unsigned char* keyblob, + unsigned int keybloblen) { + + char * ret; + hash_state hs; + unsigned char hash[SHA256_HASH_SIZE]; + unsigned int b64chars, start; + unsigned long b64size; + const char *prefix = "SHA256:"; + int err; + + sha256_init(&hs); + sha256_process(&hs, keyblob, keybloblen); + sha256_done(&hs, hash); + + /* eg "SHA256:P9szN0L2ls6KxkVv7Bppv3asnZCn03rY7Msm/c8+ZgA" + * 256/6 = 42.66 => 43 base64 chars. OpenSSH discards + * base64 padding output. */ + start = strlen(prefix); + b64chars = 43; + /* space for discarded b64 padding and null terminator */ + b64size = b64chars + 4; + ret = m_malloc(start + b64size); + + memcpy(ret, prefix, start); + err = base64_encode(hash, SHA256_HASH_SIZE, &ret[start], &b64size); + if (err != CRYPT_OK) { + dropbear_exit("base64 failed"); + } + ret[start + b64chars] = '\0'; + return ret; +} + +/* This will return a freshly malloced string */ +char * sign_key_fingerprint(const unsigned char* keyblob, unsigned int keybloblen) { + return sign_key_sha256_fingerprint(keyblob, keybloblen); +} + +void buf_put_sign(buffer* buf, sign_key *key, enum signature_type sigtype, + const buffer *data_buf) { + buffer *sigblob = buf_new(MAX_PUBKEY_SIZE); + enum signkey_type keytype = signkey_type_from_signature(sigtype); + +#if DEBUG_TRACE > DROPBEAR_VERBOSE_LEVEL + { + const char* signame = signature_name_from_type(sigtype, NULL); + TRACE(("buf_put_sign type %d %s", sigtype, signame)); + } +#endif + + +#if DROPBEAR_DSS + if (keytype == DROPBEAR_SIGNKEY_DSS) { + buf_put_dss_sign(sigblob, key->dsskey, data_buf); + } +#endif +#if DROPBEAR_RSA + if (keytype == DROPBEAR_SIGNKEY_RSA) { + buf_put_rsa_sign(sigblob, key->rsakey, sigtype, data_buf); + } +#endif +#if DROPBEAR_ECDSA + if (signkey_is_ecdsa(keytype)) { + ecc_key **eck = (ecc_key**)signkey_key_ptr(key, keytype); + if (eck && *eck) { + buf_put_ecdsa_sign(sigblob, *eck, data_buf); + } + } +#endif +#if DROPBEAR_ED25519 + if (keytype == DROPBEAR_SIGNKEY_ED25519) { + buf_put_ed25519_sign(sigblob, key->ed25519key, data_buf); + } +#endif + if (sigblob->len == 0) { + dropbear_exit("Non-matching signing type"); + } + buf_putbufstring(buf, sigblob); + buf_free(sigblob); + +} + +#if DROPBEAR_SIGNKEY_VERIFY + +/* Return DROPBEAR_SUCCESS or DROPBEAR_FAILURE. + * If FAILURE is returned, the position of + * buf is undefined. If SUCCESS is returned, buf will be positioned after the + * signature blob */ +int buf_verify(buffer * buf, sign_key *key, enum signature_type expect_sigtype, const buffer *data_buf) { + + char *type_name = NULL; + unsigned int type_name_len = 0; + enum signature_type sigtype; + enum signkey_type keytype; + + TRACE(("enter buf_verify")) + + buf_getint(buf); /* blob length */ + type_name = buf_getstring(buf, &type_name_len); + sigtype = signature_type_from_name(type_name, type_name_len); + m_free(type_name); + + if (expect_sigtype != sigtype) { + dropbear_exit("Non-matching signing type"); + } + + keytype = signkey_type_from_signature(sigtype); +#if DROPBEAR_DSS + if (keytype == DROPBEAR_SIGNKEY_DSS) { + if (key->dsskey == NULL) { + dropbear_exit("No DSS key to verify signature"); + } + return buf_dss_verify(buf, key->dsskey, data_buf); + } +#endif + +#if DROPBEAR_RSA + if (keytype == DROPBEAR_SIGNKEY_RSA) { + if (key->rsakey == NULL) { + dropbear_exit("No RSA key to verify signature"); + } + return buf_rsa_verify(buf, key->rsakey, sigtype, data_buf); + } +#endif +#if DROPBEAR_ECDSA + if (signkey_is_ecdsa(keytype)) { + ecc_key **eck = (ecc_key**)signkey_key_ptr(key, keytype); + if (eck && *eck) { + return buf_ecdsa_verify(buf, *eck, data_buf); + } + } +#endif +#if DROPBEAR_ED25519 + if (keytype == DROPBEAR_SIGNKEY_ED25519) { + if (key->ed25519key == NULL) { + dropbear_exit("No Ed25519 key to verify signature"); + } + return buf_ed25519_verify(buf, key->ed25519key, data_buf); + } +#endif +#if DROPBEAR_SK_ECDSA + if (keytype == DROPBEAR_SIGNKEY_SK_ECDSA_NISTP256) { + ecc_key **eck = (ecc_key**)signkey_key_ptr(key, keytype); + if (eck && *eck) { + return buf_sk_ecdsa_verify(buf, *eck, data_buf, key->sk_app, key->sk_applen, key->sk_flags_mask); + } + } +#endif +#if DROPBEAR_SK_ED25519 + if (keytype == DROPBEAR_SIGNKEY_SK_ED25519) { + dropbear_ed25519_key **eck = (dropbear_ed25519_key**)signkey_key_ptr(key, keytype); + if (eck && *eck) { + return buf_sk_ed25519_verify(buf, *eck, data_buf, key->sk_app, key->sk_applen, key->sk_flags_mask); + } + } +#endif + + dropbear_exit("Non-matching signing type"); + return DROPBEAR_FAILURE; +} +#endif /* DROPBEAR_SIGNKEY_VERIFY */ + +#if DROPBEAR_KEY_LINES /* ie we're using authorized_keys or known_hosts */ + +/* Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE when given a buffer containing + * a key, a key, and a type. The buffer is positioned at the start of the + * base64 data, and contains no trailing data */ +/* If fingerprint is non-NULL, it will be set to a malloc()ed fingerprint + of the key if it is successfully decoded */ +int cmp_base64_key(const unsigned char* keyblob, unsigned int keybloblen, + const unsigned char* algoname, unsigned int algolen, + const buffer * line, char ** fingerprint) { + + buffer * decodekey = NULL; + int ret = DROPBEAR_FAILURE; + unsigned int len, filealgolen; + unsigned long decodekeylen; + unsigned char* filealgo = NULL; + + /* now we have the actual data */ + len = line->len - line->pos; + if (len == 0) { + /* base64_decode doesn't like NULL argument */ + return DROPBEAR_FAILURE; + } + decodekeylen = len * 2; /* big to be safe */ + decodekey = buf_new(decodekeylen); + + if (base64_decode(buf_getptr(line, len), len, + buf_getwriteptr(decodekey, decodekey->size), + &decodekeylen) != CRYPT_OK) { + TRACE(("checkpubkey: base64 decode failed")) + goto out; + } + TRACE(("checkpubkey: base64_decode success")) + buf_incrlen(decodekey, decodekeylen); + + if (fingerprint) { + *fingerprint = sign_key_fingerprint(buf_getptr(decodekey, decodekeylen), + decodekeylen); + } + + /* compare the keys */ + if ( ( decodekeylen != keybloblen ) + || memcmp( buf_getptr(decodekey, decodekey->len), + keyblob, decodekey->len) != 0) { + TRACE(("checkpubkey: compare failed")) + goto out; + } + + /* ... and also check that the algo specified and the algo in the key + * itself match */ + filealgolen = buf_getint(decodekey); + filealgo = buf_getptr(decodekey, filealgolen); + if (filealgolen != algolen || memcmp(filealgo, algoname, algolen) != 0) { + TRACE(("checkpubkey: algo match failed")) + goto out; + } + + /* All checks passed */ + ret = DROPBEAR_SUCCESS; + +out: + buf_free(decodekey); + decodekey = NULL; + return ret; +} +#endif + +#if DROPBEAR_FUZZ +const char * const * fuzz_signkey_names = signkey_names; + +#endif diff --git a/src/signkey.h b/src/signkey.h new file mode 100644 index 0000000..c6829f2 --- /dev/null +++ b/src/signkey.h @@ -0,0 +1,163 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_SIGNKEY_H_ +#define DROPBEAR_SIGNKEY_H_ + +#include "buffer.h" + +/* Forward declarations */ +struct dropbear_DSS_Key; +struct dropbear_RSA_Key; +struct dropbear_ED25519_Key; + +/* Must match with signature_type below */ +enum signkey_type { +#if DROPBEAR_RSA + DROPBEAR_SIGNKEY_RSA, +#endif +#if DROPBEAR_DSS + DROPBEAR_SIGNKEY_DSS, +#endif +#if DROPBEAR_ECDSA + DROPBEAR_SIGNKEY_ECDSA_NISTP256, + DROPBEAR_SIGNKEY_ECDSA_NISTP384, + DROPBEAR_SIGNKEY_ECDSA_NISTP521, +#if DROPBEAR_SK_ECDSA + DROPBEAR_SIGNKEY_SK_ECDSA_NISTP256, +#endif /* DROPBEAR_SK_ECDSA */ +#endif /* DROPBEAR_ECDSA */ +#if DROPBEAR_ED25519 + DROPBEAR_SIGNKEY_ED25519, +#if DROPBEAR_SK_ED25519 + DROPBEAR_SIGNKEY_SK_ED25519, +#endif +#endif + DROPBEAR_SIGNKEY_NUM_NAMED, + DROPBEAR_SIGNKEY_ECDSA_KEYGEN = 70, /* just "ecdsa" for keygen */ + DROPBEAR_SIGNKEY_ANY = 80, + DROPBEAR_SIGNKEY_NONE = 90, +}; + +/* Must match with signkey_type above, apart from rsa */ +enum signature_type { +#if DROPBEAR_DSS + DROPBEAR_SIGNATURE_DSS = DROPBEAR_SIGNKEY_DSS, +#endif +#if DROPBEAR_ECDSA + DROPBEAR_SIGNATURE_ECDSA_NISTP256 = DROPBEAR_SIGNKEY_ECDSA_NISTP256, + DROPBEAR_SIGNATURE_ECDSA_NISTP384 = DROPBEAR_SIGNKEY_ECDSA_NISTP384, + DROPBEAR_SIGNATURE_ECDSA_NISTP521 = DROPBEAR_SIGNKEY_ECDSA_NISTP521, +#if DROPBEAR_SK_ECDSA + DROPBEAR_SIGNATURE_SK_ECDSA_NISTP256 = DROPBEAR_SIGNKEY_SK_ECDSA_NISTP256, +#endif /* DROPBEAR_SK_ECDSA */ +#endif /* DROPBEAR_ECDSA */ +#if DROPBEAR_ED25519 + DROPBEAR_SIGNATURE_ED25519 = DROPBEAR_SIGNKEY_ED25519, +#if DROPBEAR_SK_ED25519 + DROPBEAR_SIGNATURE_SK_ED25519 = DROPBEAR_SIGNKEY_SK_ED25519, +#endif +#endif +#if DROPBEAR_RSA +#if DROPBEAR_RSA_SHA1 + DROPBEAR_SIGNATURE_RSA_SHA1 = 100, /* ssh-rsa signature (sha1) */ +#endif +#if DROPBEAR_RSA_SHA256 + DROPBEAR_SIGNATURE_RSA_SHA256 = 101, /* rsa-sha2-256 signature. has a ssh-rsa key */ +#endif +#endif /* DROPBEAR_RSA */ + DROPBEAR_SIGNATURE_NONE = DROPBEAR_SIGNKEY_NONE, +}; + + +/* Sources for signing keys */ +typedef enum { + SIGNKEY_SOURCE_RAW_FILE, + SIGNKEY_SOURCE_AGENT, + SIGNKEY_SOURCE_INVALID, +} signkey_source; + +struct SIGN_key { + + enum signkey_type type; + signkey_source source; + char *filename; + +#if DROPBEAR_DSS + struct dropbear_DSS_Key * dsskey; +#endif +#if DROPBEAR_RSA + struct dropbear_RSA_Key * rsakey; +#endif +#if DROPBEAR_ECDSA +#if DROPBEAR_ECC_256 + ecc_key * ecckey256; +#endif +#if DROPBEAR_ECC_384 + ecc_key * ecckey384; +#endif +#if DROPBEAR_ECC_521 + ecc_key * ecckey521; +#endif +#endif +#if DROPBEAR_ED25519 + struct dropbear_ED25519_Key * ed25519key; +#endif + +#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 + /* application ID for U2F/FIDO key types, a malloced string */ + char * sk_app; + unsigned int sk_applen; + unsigned char sk_flags_mask; +#endif +}; + +typedef struct SIGN_key sign_key; + +sign_key * new_sign_key(void); +const char* signkey_name_from_type(enum signkey_type type, unsigned int *namelen); +enum signkey_type signkey_type_from_name(const char* name, unsigned int namelen); +const char* signature_name_from_type(enum signature_type type, unsigned int *namelen); +enum signature_type signature_type_from_name(const char* name, unsigned int namelen); +enum signkey_type signkey_type_from_signature(enum signature_type sigtype); +enum signature_type signature_type_from_signkey(enum signkey_type keytype); + +int buf_get_pub_key(buffer *buf, sign_key *key, enum signkey_type *type); +int buf_get_priv_key(buffer* buf, sign_key *key, enum signkey_type *type); +void buf_put_pub_key(buffer* buf, sign_key *key, enum signkey_type type); +void buf_put_priv_key(buffer* buf, sign_key *key, enum signkey_type type); +void sign_key_free(sign_key *key); +void buf_put_sign(buffer* buf, sign_key *key, enum signature_type sigtype, const buffer *data_buf); +#if DROPBEAR_SIGNKEY_VERIFY +int buf_verify(buffer * buf, sign_key *key, enum signature_type expect_sigtype, const buffer *data_buf); +int sk_buf_verify(buffer * buf, sign_key *key, enum signature_type expect_sigtype, const buffer *data_buf, char* app, unsigned int applen); +char * sign_key_fingerprint(const unsigned char* keyblob, unsigned int keybloblen); +#endif +int cmp_base64_key(const unsigned char* keyblob, unsigned int keybloblen, + const unsigned char* algoname, unsigned int algolen, + const buffer * line, char ** fingerprint); + +void** signkey_key_ptr(sign_key *key, enum signkey_type type); + +#endif /* DROPBEAR_SIGNKEY_H_ */ diff --git a/src/signkey_ossh.c b/src/signkey_ossh.c new file mode 100644 index 0000000..59b44ad --- /dev/null +++ b/src/signkey_ossh.c @@ -0,0 +1,161 @@ +#include "includes.h" +#include "dbutil.h" +#include "ssh.h" +#include "signkey_ossh.h" +#include "bignum.h" +#include "ecdsa.h" +#include "sk-ecdsa.h" +#include "sk-ed25519.h" +#include "rsa.h" +#include "dss.h" +#include "ed25519.h" + +#if DROPBEAR_RSA +/* OpenSSH raw private RSA format is +string "ssh-rsa" +mpint n +mpint e +mpint d +mpint iqmp (q^-1) mod p +mpint p +mpint q +*/ + +void buf_put_rsa_priv_ossh(buffer *buf, const sign_key *akey) { + const dropbear_rsa_key *key = akey->rsakey; + mp_int iqmp; + + dropbear_assert(key != NULL); + if (!(key->p && key->q)) { + dropbear_exit("Pre-0.33 Dropbear keys cannot be converted to OpenSSH keys.\n"); + } + + m_mp_init(&iqmp); + /* iqmp = (q^-1) mod p */ + if (mp_invmod(key->q, key->p, &iqmp) != MP_OKAY) { + dropbear_exit("Bignum error for iqmp\n"); + } + buf_putstring(buf, SSH_SIGNKEY_RSA, SSH_SIGNKEY_RSA_LEN); + buf_putmpint(buf, key->n); + buf_putmpint(buf, key->e); + buf_putmpint(buf, key->d); + buf_putmpint(buf, &iqmp); + buf_putmpint(buf, key->p); + buf_putmpint(buf, key->q); + mp_clear(&iqmp); +} + +int buf_get_rsa_priv_ossh(buffer *buf, sign_key *akey) { + int ret = DROPBEAR_FAILURE; + dropbear_rsa_key *key = NULL; + mp_int iqmp; + + rsa_key_free(akey->rsakey); + akey->rsakey = m_malloc(sizeof(*akey->rsakey)); + key = akey->rsakey; + m_mp_alloc_init_multi(&key->e, &key->n, &key->d, &key->p, &key->q, NULL); + + buf_eatstring(buf); + m_mp_init(&iqmp); + if (buf_getmpint(buf, key->n) == DROPBEAR_SUCCESS + && buf_getmpint(buf, key->e) == DROPBEAR_SUCCESS + && buf_getmpint(buf, key->d) == DROPBEAR_SUCCESS + && buf_getmpint(buf, &iqmp) == DROPBEAR_SUCCESS + && buf_getmpint(buf, key->p) == DROPBEAR_SUCCESS + && buf_getmpint(buf, key->q) == DROPBEAR_SUCCESS) { + ret = DROPBEAR_SUCCESS; + } + mp_clear(&iqmp); + return ret; +} + +#endif /* DROPBEAR_RSA */ + +#if DROPBEAR_ED25519 +/* OpenSSH raw private ed25519 format is +string "ssh-ed25519" +uint32 32 +byte[32] pubkey +uint32 64 +byte[32] privkey +byte[32] pubkey +*/ + +void buf_put_ed25519_priv_ossh(buffer *buf, const sign_key *akey) { + const dropbear_ed25519_key *key = akey->ed25519key; + dropbear_assert(key != NULL); + buf_putstring(buf, SSH_SIGNKEY_ED25519, SSH_SIGNKEY_ED25519_LEN); + buf_putint(buf, CURVE25519_LEN); + buf_putbytes(buf, key->pub, CURVE25519_LEN); + buf_putint(buf, CURVE25519_LEN*2); + buf_putbytes(buf, key->priv, CURVE25519_LEN); + buf_putbytes(buf, key->pub, CURVE25519_LEN); +} + +int buf_get_ed25519_priv_ossh(buffer *buf, sign_key *akey) { + dropbear_ed25519_key *key = NULL; + uint32_t len; + + ed25519_key_free(akey->ed25519key); + akey->ed25519key = m_malloc(sizeof(*akey->ed25519key)); + key = akey->ed25519key; + + /* Parse past the first string and pubkey */ + if (buf_get_ed25519_pub_key(buf, key, DROPBEAR_SIGNKEY_ED25519) + == DROPBEAR_FAILURE) { + dropbear_log(LOG_ERR, "Error parsing ed25519 key, pubkey"); + return DROPBEAR_FAILURE; + } + len = buf_getint(buf); + if (len != 2*CURVE25519_LEN) { + dropbear_log(LOG_ERR, "Error parsing ed25519 key, bad length"); + return DROPBEAR_FAILURE; + } + memcpy(key->priv, buf_getptr(buf, CURVE25519_LEN), CURVE25519_LEN); + buf_incrpos(buf, CURVE25519_LEN); + + /* Sanity check */ + if (memcmp(buf_getptr(buf, CURVE25519_LEN), key->pub, + CURVE25519_LEN) != 0) { + dropbear_log(LOG_ERR, "Error parsing ed25519 key, mismatch pubkey"); + return DROPBEAR_FAILURE; + } + return DROPBEAR_SUCCESS; +} +#endif /* DROPBEAR_ED255219 */ + +#if DROPBEAR_ECDSA +/* OpenSSH raw private ecdsa format is the same as Dropbear's. +# First part is the same as the SSH wire pubkey format +string "ecdsa-sha2-[identifier]" +string [identifier] +string Q +# With private part appended +mpint d +*/ + +void buf_put_ecdsa_priv_ossh(buffer *buf, const sign_key *key) { + ecc_key **eck = (ecc_key**)signkey_key_ptr((sign_key*)key, key->type); + if (eck && *eck) { + buf_put_ecdsa_priv_key(buf, *eck); + return; + } + dropbear_exit("ecdsa key is not set"); +} + +int buf_get_ecdsa_priv_ossh(buffer *buf, sign_key *key) { + ecc_key **eck = (ecc_key**)signkey_key_ptr(key, key->type); + if (eck) { + if (*eck) { + ecc_free(*eck); + m_free(*eck); + *eck = NULL; + } + *eck = buf_get_ecdsa_priv_key(buf); + if (*eck) { + return DROPBEAR_SUCCESS; + } + } + return DROPBEAR_FAILURE; +} +#endif /* DROPBEAR_ECDSA */ diff --git a/src/signkey_ossh.h b/src/signkey_ossh.h new file mode 100644 index 0000000..080372c --- /dev/null +++ b/src/signkey_ossh.h @@ -0,0 +1,15 @@ +#ifndef DROPBEAR_SIGNKEY_OSSH_H_ +#define DROPBEAR_SIGNKEY_OSSH_H_ + +#include "signkey.h" + +/* Helpers for OpenSSH format keys in dropbearconvert */ + +void buf_put_rsa_priv_ossh(buffer *buf, const sign_key *akey); +int buf_get_rsa_priv_ossh(buffer *buf, sign_key *akey); +void buf_put_ed25519_priv_ossh(buffer *buf, const sign_key *akey); +int buf_get_ed25519_priv_ossh(buffer *buf, sign_key *akey); +void buf_put_ecdsa_priv_ossh(buffer *buf, const sign_key *akey); +int buf_get_ecdsa_priv_ossh(buffer *buf, sign_key *akey); + +#endif /* DROPBEAR_SIGNKEY_OSSH_H_ */ diff --git a/src/sk-ecdsa.c b/src/sk-ecdsa.c new file mode 100644 index 0000000..bed7e50 --- /dev/null +++ b/src/sk-ecdsa.c @@ -0,0 +1,63 @@ +#include "includes.h" + +#if DROPBEAR_SK_ECDSA + +#include "dbutil.h" +#include "ecc.h" +#include "ecdsa.h" +#include "sk-ecdsa.h" +#include "ssh.h" + +int buf_sk_ecdsa_verify(buffer *buf, const ecc_key *key, const buffer *data_buf, + const char* app, unsigned int applen, + unsigned char sk_flags_mask) { + hash_state hs; + unsigned char subhash[SHA256_HASH_SIZE]; + buffer *sk_buffer = NULL, *sig_buffer = NULL; + unsigned char flags; + unsigned int counter; + int ret; + + TRACE(("buf_sk_ecdsa_verify")) + + /* from https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.u2f */ + /* ecdsa signature to verify (r, s) */ + sig_buffer = buf_getbuf(buf); + + flags = buf_getbyte (buf); + counter = buf_getint (buf); + /* create the message to be signed */ + sk_buffer = buf_new (2*SHA256_HASH_SIZE+5); + sha256_init (&hs); + sha256_process (&hs, app, applen); + sha256_done (&hs, subhash); + buf_putbytes (sk_buffer, subhash, sizeof (subhash)); + buf_putbyte (sk_buffer, flags); + buf_putint (sk_buffer, counter); + sha256_init (&hs); + sha256_process (&hs, data_buf->data, data_buf->len); + sha256_done (&hs, subhash); + buf_putbytes (sk_buffer, subhash, sizeof (subhash)); + + ret = buf_ecdsa_verify(sig_buffer, key, sk_buffer); + buf_free(sk_buffer); + buf_free(sig_buffer); + + if (~flags & sk_flags_mask & SSH_SK_USER_PRESENCE_REQD) { + if (ret == DROPBEAR_SUCCESS) { + dropbear_log(LOG_WARNING, "Rejecting, user-presence not set"); + } + ret = DROPBEAR_FAILURE; + } + if (~flags & sk_flags_mask & SSH_SK_USER_VERIFICATION_REQD) { + if (ret == DROPBEAR_SUCCESS) { + dropbear_log(LOG_WARNING, "Rejecting, user-verification not set"); + } + ret = DROPBEAR_FAILURE; + } + + TRACE(("leave buf_sk_ecdsa_verify, ret=%d", ret)) + return ret; +} + +#endif /* DROPBEAR_SK_ECDSA */ diff --git a/src/sk-ecdsa.h b/src/sk-ecdsa.h new file mode 100644 index 0000000..e883bf8 --- /dev/null +++ b/src/sk-ecdsa.h @@ -0,0 +1,17 @@ +#ifndef DROPBEAR_SK_ECDSA_H_ +#define DROPBEAR_SK_ECDSA_H_ + +#include "includes.h" + +#if DROPBEAR_SK_ECDSA + +#include "buffer.h" +#include "signkey.h" + +int buf_sk_ecdsa_verify(buffer *buf, const ecc_key *key, const buffer *data_buf, + const char* app, unsigned int applen, + unsigned char sk_flags_mask); + +#endif + +#endif /* DROPBEAR_SK_ECDSA_H_ */ diff --git a/src/sk-ed25519.c b/src/sk-ed25519.c new file mode 100644 index 0000000..b4827e3 --- /dev/null +++ b/src/sk-ed25519.c @@ -0,0 +1,75 @@ +#include "includes.h" + +#if DROPBEAR_SK_ED25519 + +#include "dbutil.h" +#include "buffer.h" +#include "curve25519.h" +#include "ed25519.h" +#include "ssh.h" + +int buf_sk_ed25519_verify(buffer *buf, const dropbear_ed25519_key *key, const buffer *data_buf, + const char* app, unsigned int applen, + unsigned char sk_flags_mask) { + + int ret = DROPBEAR_FAILURE; + unsigned char *s; + unsigned long slen; + hash_state hs; + unsigned char hash[SHA256_HASH_SIZE]; + buffer *sk_buffer = NULL; + unsigned char flags; + unsigned int counter; + + TRACE(("enter buf_sk_ed25519_verify")) + dropbear_assert(key != NULL); + + slen = buf_getint(buf); + if (slen != 64 || buf->len - buf->pos < slen) { + TRACE(("leave buf_sk_ed25519_verify: bad size")) + goto out; + } + s = buf_getptr(buf, slen); + buf_incrpos(buf, slen); + + flags = buf_getbyte (buf); + counter = buf_getint (buf); + /* create the message to be signed */ + sk_buffer = buf_new (2*SHA256_HASH_SIZE+5); + sha256_init (&hs); + sha256_process (&hs, app, applen); + sha256_done (&hs, hash); + buf_putbytes (sk_buffer, hash, sizeof (hash)); + buf_putbyte (sk_buffer, flags); + buf_putint (sk_buffer, counter); + sha256_init (&hs); + sha256_process (&hs, data_buf->data, data_buf->len); + sha256_done (&hs, hash); + buf_putbytes (sk_buffer, hash, sizeof (hash)); + + if (dropbear_ed25519_verify(sk_buffer->data, sk_buffer->len, + s, slen, key->pub) == 0) { + /* signature is valid */ + TRACE(("leave buf_sk_ed25519_verify: success!")) + ret = DROPBEAR_SUCCESS; + } + + if (~flags & sk_flags_mask & SSH_SK_USER_PRESENCE_REQD) { + if (ret == DROPBEAR_SUCCESS) { + dropbear_log(LOG_WARNING, "Rejecting, user-presence not set"); + } + ret = DROPBEAR_FAILURE; + } + if (~flags & sk_flags_mask & SSH_SK_USER_VERIFICATION_REQD) { + if (ret == DROPBEAR_SUCCESS) { + dropbear_log(LOG_WARNING, "Rejecting, user-verification not set"); + } + ret = DROPBEAR_FAILURE; + } +out: + buf_free(sk_buffer); + TRACE(("leave buf_sk_ed25519_verify: ret %d", ret)) + return ret; +} + +#endif /* DROPBEAR_SK_ED25519 */ diff --git a/src/sk-ed25519.h b/src/sk-ed25519.h new file mode 100644 index 0000000..74ab7c8 --- /dev/null +++ b/src/sk-ed25519.h @@ -0,0 +1,17 @@ +#ifndef DROPBEAR_SK_ED25519_H_ +#define DROPBEAR_SK_ED25519_H_ + +#include "includes.h" + +#if DROPBEAR_SK_ED25519 + +#include "buffer.h" +#include "ed25519.h" + +int buf_sk_ed25519_verify(buffer *buf, const dropbear_ed25519_key *key, const buffer *data_buf, + const char* app, unsigned int applen, + unsigned char sk_flags_mask); + +#endif + +#endif /* DROPBEAR_SK_ED25519_H_ */ diff --git a/src/ssh.h b/src/ssh.h new file mode 100644 index 0000000..1b4fec6 --- /dev/null +++ b/src/ssh.h @@ -0,0 +1,133 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* This file contains the various numbers in the protocol */ + + +/* message numbers */ +#define SSH_MSG_DISCONNECT 1 +#define SSH_MSG_IGNORE 2 +#define SSH_MSG_UNIMPLEMENTED 3 +#define SSH_MSG_DEBUG 4 +#define SSH_MSG_SERVICE_REQUEST 5 +#define SSH_MSG_SERVICE_ACCEPT 6 +#define SSH_MSG_EXT_INFO 7 +#define SSH_MSG_KEXINIT 20 +#define SSH_MSG_NEWKEYS 21 +#define SSH_MSG_KEXDH_INIT 30 +#define SSH_MSG_KEXDH_REPLY 31 + +/* userauth message numbers */ +#define SSH_MSG_USERAUTH_REQUEST 50 +#define SSH_MSG_USERAUTH_FAILURE 51 +#define SSH_MSG_USERAUTH_SUCCESS 52 +#define SSH_MSG_USERAUTH_BANNER 53 + +/* packets 60-79 are method-specific, aren't one-one mapping */ +#define SSH_MSG_USERAUTH_SPECIFIC_60 60 + +#define SSH_MSG_USERAUTH_PASSWD_CHANGEREQ 60 + +#define SSH_MSG_USERAUTH_PK_OK 60 + +/* keyboard interactive auth */ +#define SSH_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH_MSG_USERAUTH_INFO_RESPONSE 61 + + +/* If adding numbers here, check MAX_UNAUTH_PACKET_TYPE in process-packet.c + * is still valid */ + +/* connect message numbers */ +#define SSH_MSG_GLOBAL_REQUEST 80 +#define SSH_MSG_REQUEST_SUCCESS 81 +#define SSH_MSG_REQUEST_FAILURE 82 +#define SSH_MSG_CHANNEL_OPEN 90 +#define SSH_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH_MSG_CHANNEL_DATA 94 +#define SSH_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH_MSG_CHANNEL_EOF 96 +#define SSH_MSG_CHANNEL_CLOSE 97 +#define SSH_MSG_CHANNEL_REQUEST 98 +#define SSH_MSG_CHANNEL_SUCCESS 99 +#define SSH_MSG_CHANNEL_FAILURE 100 + +/* extended data types */ +#define SSH_EXTENDED_DATA_STDERR 1 + +/* disconnect codes */ +#define SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH_DISCONNECT_RESERVED 4 +#define SSH_DISCONNECT_MAC_ERROR 5 +#define SSH_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH_DISCONNECT_CONNECTION_LOST 10 +#define SSH_DISCONNECT_BY_APPLICATION 11 +#define SSH_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH_DISCONNECT_ILLEGAL_USER_NAME 15 + +/* rfc8308 */ +#define SSH_EXT_INFO_S "ext-info-s" +#define SSH_EXT_INFO_C "ext-info-c" +#define SSH_SERVER_SIG_ALGS "server-sig-algs" + +/* service types */ +#define SSH_SERVICE_USERAUTH "ssh-userauth" +#define SSH_SERVICE_USERAUTH_LEN 12 +#define SSH_SERVICE_CONNECTION "ssh-connection" +#define SSH_SERVICE_CONNECTION_LEN 14 + +/* public/signature key types */ +#define SSH_SIGNKEY_DSS "ssh-dss" +#define SSH_SIGNKEY_DSS_LEN 7 +#define SSH_SIGNKEY_RSA "ssh-rsa" +#define SSH_SIGNKEY_RSA_LEN 7 +#define SSH_SIGNKEY_ED25519 "ssh-ed25519" +#define SSH_SIGNKEY_ED25519_LEN 11 +/* signature type */ +#define SSH_SIGNATURE_RSA_SHA256 "rsa-sha2-256" + +/* Agent commands. These aren't part of the spec, and are defined + * only on the openssh implementation. */ +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 +#define SSH2_AGENTC_REQUEST_IDENTITIES 11 +#define SSH2_AGENT_IDENTITIES_ANSWER 12 +#define SSH2_AGENTC_SIGN_REQUEST 13 +#define SSH2_AGENT_SIGN_RESPONSE 14 + +#define SSH2_AGENT_FAILURE 30 + +/* Flags defined by OpenSSH U2F key/signature format */ +#define SSH_SK_USER_PRESENCE_REQD 0x01 +#define SSH_SK_USER_VERIFICATION_REQD 0x04 +#define SSH_SK_RESIDENT_KEY 0x20 diff --git a/src/sshpty.c b/src/sshpty.c new file mode 100644 index 0000000..9f12d67 --- /dev/null +++ b/src/sshpty.c @@ -0,0 +1,414 @@ +/* + * Dropbear - a SSH2 server + * + * Copied from OpenSSH-3.5p1 source, modified by Matt Johnston 2003 + * + * Author: Tatu Ylonen <ylo@cs.hut.fi> + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + * Allocating a pseudo-terminal, and making it the controlling tty. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +/*RCSID("OpenBSD: sshpty.c,v 1.7 2002/06/24 17:57:20 deraadt Exp ");*/ + +#include "includes.h" +#include "dbutil.h" +#include "errno.h" +#include "sshpty.h" + +/* Pty allocated with _getpty gets broken if we do I_PUSH:es to it. */ +#if defined(HAVE__GETPTY) || defined(HAVE_OPENPTY) +#undef HAVE_DEV_PTMX +#endif + +#ifdef HAVE_PTY_H +# include <pty.h> +#endif +#if defined(USE_DEV_PTMX) && defined(HAVE_STROPTS_H) +# include <stropts.h> +#endif + +#ifndef O_NOCTTY +#define O_NOCTTY 0 +#endif + +/* + * Allocates and opens a pty. Returns 0 if no pty could be allocated, or + * nonzero if a pty was successfully allocated. On success, open file + * descriptors for the pty and tty sides and the name of the tty side are + * returned (the buffer must be able to hold at least 64 characters). + */ + +int +pty_allocate(int *ptyfd, int *ttyfd, char *namebuf, int namebuflen) +{ +#if defined(HAVE_OPENPTY) + /* exists in recent (4.4) BSDs and OSF/1 */ + char *name; + int i; + + i = openpty(ptyfd, ttyfd, NULL, NULL, NULL); + if (i < 0) { + dropbear_log(LOG_WARNING, + "pty_allocate: openpty: %.100s", strerror(errno)); + return 0; + } + name = ttyname(*ttyfd); + if (!name) { + dropbear_exit("ttyname fails for openpty device"); + } + + strlcpy(namebuf, name, namebuflen); /* possible truncation */ + return 1; +#else /* HAVE_OPENPTY */ +#ifdef HAVE__GETPTY + /* + * _getpty(3) exists in SGI Irix 4.x, 5.x & 6.x -- it generates more + * pty's automagically when needed + */ + char *slave; + + slave = _getpty(ptyfd, O_RDWR, 0622, 0); + if (slave == NULL) { + dropbear_log(LOG_WARNING, + "pty_allocate: _getpty: %.100s", strerror(errno)); + return 0; + } + strlcpy(namebuf, slave, namebuflen); + /* Open the slave side. */ + *ttyfd = open(namebuf, O_RDWR | O_NOCTTY); + if (*ttyfd < 0) { + dropbear_log(LOG_WARNING, + "pty_allocate error: ttyftd open error"); + close(*ptyfd); + return 0; + } + return 1; +#else /* HAVE__GETPTY */ +#if defined(USE_DEV_PTMX) + /* + * This code is used e.g. on Solaris 2.x. (Note that Solaris 2.3 + * also has bsd-style ptys, but they simply do not work.) + * + * Linux systems may have the /dev/ptmx device, but this code won't work. + */ + int ptm; + char *pts; + + ptm = open("/dev/ptmx", O_RDWR | O_NOCTTY); + if (ptm < 0) { + dropbear_log(LOG_WARNING, + "pty_allocate: /dev/ptmx: %.100s", strerror(errno)); + return 0; + } + if (grantpt(ptm) < 0) { + dropbear_log(LOG_WARNING, + "grantpt: %.100s", strerror(errno)); + return 0; + } + if (unlockpt(ptm) < 0) { + dropbear_log(LOG_WARNING, + "unlockpt: %.100s", strerror(errno)); + return 0; + } + pts = ptsname(ptm); + if (pts == NULL) { + dropbear_log(LOG_WARNING, + "Slave pty side name could not be obtained."); + } + strlcpy(namebuf, pts, namebuflen); + *ptyfd = ptm; + + /* Open the slave side. */ + *ttyfd = open(namebuf, O_RDWR | O_NOCTTY); + if (*ttyfd < 0) { + dropbear_log(LOG_ERR, + "error opening pts %.100s: %.100s", namebuf, strerror(errno)); + close(*ptyfd); + return 0; + } +#if !defined(HAVE_CYGWIN) && defined(I_PUSH) + /* + * Push the appropriate streams modules, as described in Solaris pts(7). + * HP-UX pts(7) doesn't have ttcompat module. + */ + if (ioctl(*ttyfd, I_PUSH, "ptem") < 0) { + dropbear_log(LOG_WARNING, + "ioctl I_PUSH ptem: %.100s", strerror(errno)); + } + if (ioctl(*ttyfd, I_PUSH, "ldterm") < 0) { + dropbear_log(LOG_WARNING, + "ioctl I_PUSH ldterm: %.100s", strerror(errno)); + } +#ifndef __hpux + if (ioctl(*ttyfd, I_PUSH, "ttcompat") < 0) { + dropbear_log(LOG_WARNING, + "ioctl I_PUSH ttcompat: %.100s", strerror(errno)); + } +#endif +#endif + return 1; +#else /* USE_DEV_PTMX */ +#ifdef HAVE_DEV_PTS_AND_PTC + /* AIX-style pty code. */ + const char *name; + + *ptyfd = open("/dev/ptc", O_RDWR | O_NOCTTY); + if (*ptyfd < 0) { + dropbear_log(LOG_ERR, + "Could not open /dev/ptc: %.100s", strerror(errno)); + return 0; + } + name = ttyname(*ptyfd); + if (!name) { + dropbear_exit("ttyname fails for /dev/ptc device"); + } + strlcpy(namebuf, name, namebuflen); + *ttyfd = open(name, O_RDWR | O_NOCTTY); + if (*ttyfd < 0) { + dropbear_log(LOG_ERR, + "Could not open pty slave side %.100s: %.100s", + name, strerror(errno)); + close(*ptyfd); + return 0; + } + return 1; +#else /* HAVE_DEV_PTS_AND_PTC */ + + /* BSD-style pty code. */ + char buf[64]; + int i; + const char *ptymajors = "pqrstuvwxyzabcdefghijklmnoABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const char *ptyminors = "0123456789abcdef"; + int num_minors = strlen(ptyminors); + int num_ptys = strlen(ptymajors) * num_minors; + struct termios tio; + + for (i = 0; i < num_ptys; i++) { + snprintf(buf, sizeof buf, "/dev/pty%c%c", ptymajors[i / num_minors], + ptyminors[i % num_minors]); + snprintf(namebuf, namebuflen, "/dev/tty%c%c", + ptymajors[i / num_minors], ptyminors[i % num_minors]); + + *ptyfd = open(buf, O_RDWR | O_NOCTTY); + if (*ptyfd < 0) { + /* Try SCO style naming */ + snprintf(buf, sizeof buf, "/dev/ptyp%d", i); + snprintf(namebuf, namebuflen, "/dev/ttyp%d", i); + *ptyfd = open(buf, O_RDWR | O_NOCTTY); + if (*ptyfd < 0) { + continue; + } + } + + /* Open the slave side. */ + *ttyfd = open(namebuf, O_RDWR | O_NOCTTY); + if (*ttyfd < 0) { + dropbear_log(LOG_ERR, + "pty_allocate: %.100s: %.100s", namebuf, strerror(errno)); + close(*ptyfd); + return 0; + } + /* set tty modes to a sane state for broken clients */ + if (tcgetattr(*ptyfd, &tio) < 0) { + dropbear_log(LOG_WARNING, + "ptyallocate: tty modes failed: %.100s", strerror(errno)); + } else { + tio.c_lflag |= (ECHO | ISIG | ICANON); + tio.c_oflag |= (OPOST | ONLCR); + tio.c_iflag |= ICRNL; + + /* Set the new modes for the terminal. */ + if (tcsetattr(*ptyfd, TCSANOW, &tio) < 0) { + dropbear_log(LOG_WARNING, + "Setting tty modes for pty failed: %.100s", + strerror(errno)); + } + } + + return 1; + } + dropbear_log(LOG_WARNING, "Failed to open any /dev/pty?? devices"); + return 0; +#endif /* HAVE_DEV_PTS_AND_PTC */ +#endif /* USE_DEV_PTMX */ +#endif /* HAVE__GETPTY */ +#endif /* HAVE_OPENPTY */ +} + +/* Releases the tty. Its ownership is returned to root, and permissions to 0666. */ + +void +pty_release(const char *tty_name) +{ + if (chown(tty_name, (uid_t) 0, (gid_t) 0) < 0 + && (errno != ENOENT)) { + dropbear_log(LOG_ERR, + "chown %.100s 0 0 failed: %.100s", tty_name, strerror(errno)); + } + if (chmod(tty_name, (mode_t) 0666) < 0 + && (errno != ENOENT)) { + dropbear_log(LOG_ERR, + "chmod %.100s 0666 failed: %.100s", tty_name, strerror(errno)); + } +} + +/* Makes the tty the processes controlling tty and sets it to sane modes. */ + +void +pty_make_controlling_tty(int *ttyfd, const char *tty_name) +{ + int fd; +#ifdef USE_VHANGUP + void *old; +#endif /* USE_VHANGUP */ + + /* Solaris has a problem with TIOCNOTTY for a bg process, so + * we disable the signal which would STOP the process - matt */ + signal(SIGTTOU, SIG_IGN); + + /* First disconnect from the old controlling tty. */ +#ifdef TIOCNOTTY + fd = open(_PATH_TTY, O_RDWR | O_NOCTTY); + if (fd >= 0) { + (void) ioctl(fd, TIOCNOTTY, NULL); + close(fd); + } +#endif /* TIOCNOTTY */ + if (setsid() < 0) { + dropbear_log(LOG_ERR, + "setsid: %.100s", strerror(errno)); + } + + /* + * Verify that we are successfully disconnected from the controlling + * tty. + */ + fd = open(_PATH_TTY, O_RDWR | O_NOCTTY); + if (fd >= 0) { + dropbear_log(LOG_ERR, + "Failed to disconnect from controlling tty.\n"); + close(fd); + } + /* Make it our controlling tty. */ +#ifdef TIOCSCTTY + if (ioctl(*ttyfd, TIOCSCTTY, NULL) < 0) { + dropbear_log(LOG_ERR, + "ioctl(TIOCSCTTY): %.100s", strerror(errno)); + } +#endif /* TIOCSCTTY */ +#ifdef HAVE_NEWS4 + if (setpgrp(0,0) < 0) { + dropbear_log(LOG_ERR, + error("SETPGRP %s",strerror(errno))); + } +#endif /* HAVE_NEWS4 */ +#ifdef USE_VHANGUP + old = mysignal(SIGHUP, SIG_IGN); + vhangup(); + mysignal(SIGHUP, old); +#endif /* USE_VHANGUP */ + fd = open(tty_name, O_RDWR); + if (fd < 0) { + dropbear_log(LOG_ERR, + "%.100s: %.100s", tty_name, strerror(errno)); + } else { +#ifdef USE_VHANGUP + close(*ttyfd); + *ttyfd = fd; +#else /* USE_VHANGUP */ + close(fd); +#endif /* USE_VHANGUP */ + } + /* Verify that we now have a controlling tty. */ + fd = open(_PATH_TTY, O_WRONLY); + if (fd < 0) { + dropbear_log(LOG_ERR, + "open /dev/tty failed - could not set controlling tty: %.100s", + strerror(errno)); + } else { + close(fd); + } +} + +/* Changes the window size associated with the pty. */ + +void +pty_change_window_size(int ptyfd, int row, int col, + int xpixel, int ypixel) +{ + struct winsize w; + + w.ws_row = row; + w.ws_col = col; + w.ws_xpixel = xpixel; + w.ws_ypixel = ypixel; + (void) ioctl(ptyfd, TIOCSWINSZ, &w); +} + +void +pty_setowner(struct passwd *pw, const char *tty_name) +{ + struct group *grp; + gid_t gid; + mode_t mode; + struct stat st; + + /* Determine the group to make the owner of the tty. */ + grp = getgrnam("tty"); + if (grp) { + gid = grp->gr_gid; + mode = S_IRUSR | S_IWUSR | S_IWGRP; + } else { + gid = pw->pw_gid; + mode = S_IRUSR | S_IWUSR | S_IWGRP | S_IWOTH; + } + + /* + * Change owner and mode of the tty as required. + * Warn but continue if filesystem is read-only and the uids match/ + * tty is owned by root. + */ + if (stat(tty_name, &st)) { + dropbear_exit("pty_setowner: stat(%.101s) failed: %.100s", + tty_name, strerror(errno)); + } + + /* Allow either "tty" gid or user's own gid. On Linux with openpty() + * this varies depending on the devpts mount options */ + if (st.st_uid != pw->pw_uid || !(st.st_gid == gid || st.st_gid == pw->pw_gid)) { + if (chown(tty_name, pw->pw_uid, gid) < 0) { + if (errno == EROFS && + (st.st_uid == pw->pw_uid || st.st_uid == 0)) { + dropbear_log(LOG_ERR, + "chown(%.100s, %u, %u) failed: %.100s", + tty_name, (unsigned int)pw->pw_uid, (unsigned int)gid, + strerror(errno)); + } else { + dropbear_exit("chown(%.100s, %u, %u) failed: %.100s", + tty_name, (unsigned int)pw->pw_uid, (unsigned int)gid, + strerror(errno)); + } + } + } + + if ((st.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) != mode) { + if (chmod(tty_name, mode) < 0) { + if (errno == EROFS && + (st.st_mode & (S_IRGRP | S_IROTH)) == 0) { + dropbear_log(LOG_ERR, + "chmod(%.100s, 0%o) failed: %.100s", + tty_name, mode, strerror(errno)); + } else { + dropbear_exit("chmod(%.100s, 0%o) failed: %.100s", + tty_name, mode, strerror(errno)); + } + } + } +} diff --git a/src/sshpty.h b/src/sshpty.h new file mode 100644 index 0000000..cf72072 --- /dev/null +++ b/src/sshpty.h @@ -0,0 +1,28 @@ +/* $OpenBSD: sshpty.h,v 1.4 2002/03/04 17:27:39 stevesk Exp $ */ + +/* + * Copied from openssh-3.5p1 source by Matt Johnston 2003 + * + * Author: Tatu Ylonen <ylo@cs.hut.fi> + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + * Functions for allocating a pseudo-terminal and making it the controlling + * tty. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +#ifndef SSHPTY_H +#define SSHPTY_H + +int pty_allocate(int *, int *, char *, int); +void pty_release(const char *); +void pty_make_controlling_tty(int *, const char *); +void pty_change_window_size(int, int, int, int, int); +void pty_setowner(struct passwd *, const char *); + +#endif /* SSHPTY_H */ diff --git a/src/svr-agentfwd.c b/src/svr-agentfwd.c new file mode 100644 index 0000000..a8941ea --- /dev/null +++ b/src/svr-agentfwd.c @@ -0,0 +1,279 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* This file (agentfwd.c) handles authentication agent forwarding, for OpenSSH + * style agents. */ + +#include "includes.h" + +#if DROPBEAR_SVR_AGENTFWD + +#include "agentfwd.h" +#include "session.h" +#include "ssh.h" +#include "dbutil.h" +#include "chansession.h" +#include "channel.h" +#include "packet.h" +#include "buffer.h" +#include "dbrandom.h" +#include "listener.h" +#include "auth.h" + +#define AGENTDIRPREFIX "/tmp/dropbear-" + +static int send_msg_channel_open_agent(int fd); +static int bindagent(int fd, struct ChanSess * chansess); +static void agentaccept(const struct Listener * listener, int sock); + +/* Handles client requests to start agent forwarding, sets up listening socket. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int svr_agentreq(struct ChanSess * chansess) { + int fd = -1; + + if (!svr_pubkey_allows_agentfwd()) { + return DROPBEAR_FAILURE; + } + + if (chansess->agentlistener != NULL) { + return DROPBEAR_FAILURE; + } + + /* create listening socket */ + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + goto fail; + } + + /* create the unix socket dir and file */ + if (bindagent(fd, chansess) == DROPBEAR_FAILURE) { + goto fail; + } + + /* listen */ + if (listen(fd, 20) < 0) { + goto fail; + } + + /* set non-blocking */ + setnonblocking(fd); + + /* pass if off to listener */ + chansess->agentlistener = new_listener( &fd, 1, 0, chansess, + agentaccept, NULL); + + if (chansess->agentlistener == NULL) { + goto fail; + } + + return DROPBEAR_SUCCESS; + +fail: + m_close(fd); + /* cleanup */ + svr_agentcleanup(chansess); + + return DROPBEAR_FAILURE; +} + +/* accepts a connection on the forwarded socket and opens a new channel for it + * back to the client */ +/* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static void agentaccept(const struct Listener *UNUSED(listener), int sock) { + + int fd; + + fd = accept(sock, NULL, NULL); + if (fd < 0) { + TRACE(("accept failed")) + return; + } + + if (send_msg_channel_open_agent(fd) != DROPBEAR_SUCCESS) { + close(fd); + } + +} + +/* set up the environment variable pointing to the socket. This is called + * just before command/shell execution, after dropping privileges */ +void svr_agentset(const struct ChanSess * chansess) { + + char *path = NULL; + int len; + + if (chansess->agentlistener == NULL) { + return; + } + + /* 2 for "/" and "\0" */ + len = strlen(chansess->agentdir) + strlen(chansess->agentfile) + 2; + + path = m_malloc(len); + snprintf(path, len, "%s/%s", chansess->agentdir, chansess->agentfile); + addnewvar("SSH_AUTH_SOCK", path); + m_free(path); +} + +/* close the socket, remove the socket-file */ +void svr_agentcleanup(struct ChanSess * chansess) { + + char *path = NULL; + uid_t uid; + gid_t gid; + int len; + + if (chansess->agentlistener != NULL) { + remove_listener(chansess->agentlistener); + chansess->agentlistener = NULL; + } + + if (chansess->agentfile != NULL && chansess->agentdir != NULL) { + +#if DROPBEAR_SVR_MULTIUSER + /* Remove the dir as the user. That way they can't cause problems except + * for themselves */ + uid = getuid(); + gid = getgid(); + if ((setegid(ses.authstate.pw_gid)) < 0 || + (seteuid(ses.authstate.pw_uid)) < 0) { + dropbear_exit("Failed to set euid"); + } +#endif + + /* 2 for "/" and "\0" */ + len = strlen(chansess->agentdir) + strlen(chansess->agentfile) + 2; + + path = m_malloc(len); + snprintf(path, len, "%s/%s", chansess->agentdir, chansess->agentfile); + unlink(path); + m_free(path); + + rmdir(chansess->agentdir); + +#if DROPBEAR_SVR_MULTIUSER + if ((seteuid(uid)) < 0 || + (setegid(gid)) < 0) { + dropbear_exit("Failed to revert euid"); + } +#endif + + m_free(chansess->agentfile); + m_free(chansess->agentdir); + } + +} + +static const struct ChanType chan_svr_agent = { + "auth-agent@openssh.com", + NULL, + NULL, + NULL, + NULL, + NULL +}; + + +/* helper for accepting an agent request */ +static int send_msg_channel_open_agent(int fd) { + + if (send_msg_channel_open_init(fd, &chan_svr_agent) == DROPBEAR_SUCCESS) { + encrypt_packet(); + return DROPBEAR_SUCCESS; + } else { + return DROPBEAR_FAILURE; + } +} + +/* helper for creating the agent socket-file + returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int bindagent(int fd, struct ChanSess * chansess) { + + struct sockaddr_un addr; + unsigned int prefix; + char path[(sizeof(addr.sun_path)-1)/2], sockfile[(sizeof(addr.sun_path)-1)/2]; + mode_t mode; + int i; + uid_t uid; + gid_t gid; + int ret = DROPBEAR_FAILURE; + +#if DROPBEAR_SVR_MULTIUSER + /* drop to user privs to make the dir/file */ + uid = getuid(); + gid = getgid(); + if ((setegid(ses.authstate.pw_gid)) < 0 || + (seteuid(ses.authstate.pw_uid)) < 0) { + dropbear_exit("Failed to set euid"); + } +#endif + + memset((void*)&addr, 0x0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + mode = S_IRWXU; + + for (i = 0; i < 20; i++) { + genrandom((unsigned char*)&prefix, sizeof(prefix)); + /* we want 32 bits (8 hex digits) - "/tmp/dropbear-f19c62c0" */ + snprintf(path, sizeof(path), AGENTDIRPREFIX "%.8x", prefix); + + if (mkdir(path, mode) == 0) { + goto bindsocket; + } + if (errno != EEXIST) { + break; + } + } + /* couldn't make a dir */ + goto out; + +bindsocket: + /* Format is "/tmp/dropbear-0246dead/auth-d00f7654-23". + * The "23" is the file desc, the random data is to avoid collisions + * between subsequent user processes reusing socket fds (odds are now + * 1/(2^64) */ + genrandom((unsigned char*)&prefix, sizeof(prefix)); + snprintf(sockfile, sizeof(sockfile), "auth-%.8x-%d", prefix, fd); + + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", path, sockfile); + + if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) { + chansess->agentdir = m_strdup(path); + chansess->agentfile = m_strdup(sockfile); + ret = DROPBEAR_SUCCESS; + } + + +out: +#if DROPBEAR_SVR_MULTIUSER + if ((seteuid(uid)) < 0 || + (setegid(gid)) < 0) { + dropbear_exit("Failed to revert euid"); + } +#endif + return ret; +} + +#endif diff --git a/src/svr-auth.c b/src/svr-auth.c new file mode 100644 index 0000000..10131f1 --- /dev/null +++ b/src/svr-auth.c @@ -0,0 +1,472 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* This file (auth.c) handles authentication requests, passing it to the + * particular type (auth-passwd, auth-pubkey). */ + + +#include "includes.h" +#include "dbutil.h" +#include "session.h" +#include "buffer.h" +#include "ssh.h" +#include "packet.h" +#include "auth.h" +#include "runopts.h" +#include "dbrandom.h" + +static int checkusername(const char *username, unsigned int userlen); + +/* initialise the first time for a session, resetting all parameters */ +void svr_authinitialise() { + memset(&ses.authstate, 0, sizeof(ses.authstate)); +#if DROPBEAR_SVR_PUBKEY_AUTH + ses.authstate.authtypes |= AUTH_TYPE_PUBKEY; +#endif +#if DROPBEAR_SVR_PASSWORD_AUTH || DROPBEAR_SVR_PAM_AUTH + if (!svr_opts.noauthpass) { + ses.authstate.authtypes |= AUTH_TYPE_PASSWORD; + } +#endif +} + +/* Send a banner message if specified to the client. The client might + * ignore this, but possibly serves as a legal "no trespassing" sign */ +void send_msg_userauth_banner(const buffer *banner) { + + TRACE(("enter send_msg_userauth_banner")) + + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_BANNER); + buf_putbufstring(ses.writepayload, banner); + buf_putstring(ses.writepayload, "en", 2); + + encrypt_packet(); + + TRACE(("leave send_msg_userauth_banner")) +} + +/* handle a userauth request, check validity, pass to password or pubkey + * checking, and handle success or failure */ +void recv_msg_userauth_request() { + + char *username = NULL, *servicename = NULL, *methodname = NULL; + unsigned int userlen, servicelen, methodlen; + int valid_user = 0; + + TRACE(("enter recv_msg_userauth_request")) + + /* for compensating failure delay */ + gettime_wrapper(&ses.authstate.auth_starttime); + + /* ignore packets if auth is already done */ + if (ses.authstate.authdone == 1) { + TRACE(("leave recv_msg_userauth_request: authdone already")) + return; + } + + /* send the banner if it exists, it will only exist once */ + if (svr_opts.banner) { + send_msg_userauth_banner(svr_opts.banner); + buf_free(svr_opts.banner); + svr_opts.banner = NULL; + } + + username = buf_getstring(ses.payload, &userlen); + servicename = buf_getstring(ses.payload, &servicelen); + methodname = buf_getstring(ses.payload, &methodlen); + + /* only handle 'ssh-connection' currently */ + if (servicelen != SSH_SERVICE_CONNECTION_LEN + && (strncmp(servicename, SSH_SERVICE_CONNECTION, + SSH_SERVICE_CONNECTION_LEN) != 0)) { + + /* TODO - disconnect here */ + m_free(username); + m_free(servicename); + m_free(methodname); + dropbear_exit("unknown service in auth"); + } + + /* check username is good before continuing. + * the 'incrfail' varies depending on the auth method to + * avoid giving away which users exist on the system through + * the time delay. */ + if (checkusername(username, userlen) == DROPBEAR_SUCCESS) { + valid_user = 1; + } + + /* user wants to know what methods are supported */ + if (methodlen == AUTH_METHOD_NONE_LEN && + strncmp(methodname, AUTH_METHOD_NONE, + AUTH_METHOD_NONE_LEN) == 0) { + TRACE(("recv_msg_userauth_request: 'none' request")) + if (valid_user + && svr_opts.allowblankpass + && !svr_opts.noauthpass + && !(svr_opts.norootpass && ses.authstate.pw_uid == 0) + && ses.authstate.pw_passwd[0] == '\0') + { + dropbear_log(LOG_NOTICE, + "Auth succeeded with blank password for '%s' from %s", + ses.authstate.pw_name, + svr_ses.addrstring); + send_msg_userauth_success(); + goto out; + } + else + { + /* 'none' has no failure delay */ + send_msg_userauth_failure(0, 0); + goto out; + } + } + +#if DROPBEAR_SVR_PASSWORD_AUTH + if (!svr_opts.noauthpass && + !(svr_opts.norootpass && ses.authstate.pw_uid == 0) ) { + /* user wants to try password auth */ + if (methodlen == AUTH_METHOD_PASSWORD_LEN && + strncmp(methodname, AUTH_METHOD_PASSWORD, + AUTH_METHOD_PASSWORD_LEN) == 0) { + svr_auth_password(valid_user); + goto out; + } + } +#endif + +#if DROPBEAR_SVR_PAM_AUTH + if (!svr_opts.noauthpass && + !(svr_opts.norootpass && ses.authstate.pw_uid == 0) ) { + /* user wants to try password auth */ + if (methodlen == AUTH_METHOD_PASSWORD_LEN && + strncmp(methodname, AUTH_METHOD_PASSWORD, + AUTH_METHOD_PASSWORD_LEN) == 0) { + svr_auth_pam(valid_user); + goto out; + } + } +#endif + +#if DROPBEAR_SVR_PUBKEY_AUTH + /* user wants to try pubkey auth */ + if (methodlen == AUTH_METHOD_PUBKEY_LEN && + strncmp(methodname, AUTH_METHOD_PUBKEY, + AUTH_METHOD_PUBKEY_LEN) == 0) { + svr_auth_pubkey(valid_user); + goto out; + } +#endif + + /* nothing matched, we just fail with a delay */ + send_msg_userauth_failure(0, 1); + +out: + + m_free(username); + m_free(servicename); + m_free(methodname); +} + +#ifdef HAVE_GETGROUPLIST +/* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int check_group_membership(gid_t check_gid, const char* username, gid_t user_gid) { + int ngroups, i, ret; + gid_t *grouplist = NULL; + int match = DROPBEAR_FAILURE; + + for (ngroups = 32; ngroups <= DROPBEAR_NGROUP_MAX; ngroups *= 2) { + grouplist = m_malloc(sizeof(gid_t) * ngroups); + + /* BSD returns ret==0 on success. Linux returns ret==ngroups on success */ + ret = getgrouplist(username, user_gid, grouplist, &ngroups); + if (ret >= 0) { + break; + } + m_free(grouplist); + grouplist = NULL; + } + + if (!grouplist) { + dropbear_log(LOG_ERR, "Too many groups for user '%s'", username); + return DROPBEAR_FAILURE; + } + + for (i = 0; i < ngroups; i++) { + if (grouplist[i] == check_gid) { + match = DROPBEAR_SUCCESS; + break; + } + } + m_free(grouplist); + + return match; +} +#endif + +/* Check that the username exists and isn't disallowed (root), and has a valid shell. + * returns DROPBEAR_SUCCESS on valid username, DROPBEAR_FAILURE on failure */ +static int checkusername(const char *username, unsigned int userlen) { + + char* listshell = NULL; + char* usershell = NULL; + uid_t uid; + + TRACE(("enter checkusername")) + if (userlen > MAX_USERNAME_LEN) { + return DROPBEAR_FAILURE; + } + + if (strlen(username) != userlen) { + dropbear_exit("Attempted username with a null byte"); + } + + if (ses.authstate.username == NULL) { + /* first request */ + fill_passwd(username); + ses.authstate.username = m_strdup(username); + } else { + /* check username hasn't changed */ + if (strcmp(username, ses.authstate.username) != 0) { + dropbear_exit("Client trying multiple usernames"); + } + } + + /* avoids cluttering logs with repeated failure messages from + consecutive authentication requests in a sesssion */ + if (ses.authstate.checkusername_failed) { + TRACE(("checkusername: returning cached failure")) + return DROPBEAR_FAILURE; + } + + /* check that user exists */ + if (!ses.authstate.pw_name) { + TRACE(("leave checkusername: user '%s' doesn't exist", username)) + dropbear_log(LOG_WARNING, + "Login attempt for nonexistent user"); + ses.authstate.checkusername_failed = 1; + return DROPBEAR_FAILURE; + } + + /* check if we are running as non-root, and login user is different from the server */ + uid = geteuid(); + if (!(DROPBEAR_SVR_MULTIUSER && uid == 0) && uid != ses.authstate.pw_uid) { + TRACE(("running as nonroot, only server uid is allowed")) + dropbear_log(LOG_WARNING, + "Login attempt with wrong user %s", + ses.authstate.pw_name); + ses.authstate.checkusername_failed = 1; + return DROPBEAR_FAILURE; + } + + /* check for non-root if desired */ + if (svr_opts.norootlogin && ses.authstate.pw_uid == 0) { + TRACE(("leave checkusername: root login disabled")) + dropbear_log(LOG_WARNING, "root login rejected"); + ses.authstate.checkusername_failed = 1; + return DROPBEAR_FAILURE; + } + + /* check for login restricted to certain group if desired */ +#ifdef HAVE_GETGROUPLIST + if (svr_opts.restrict_group) { + if (check_group_membership(svr_opts.restrict_group_gid, + ses.authstate.pw_name, ses.authstate.pw_gid) == DROPBEAR_FAILURE) { + dropbear_log(LOG_WARNING, + "Logins are restricted to the group %s but user '%s' is not a member", + svr_opts.restrict_group, ses.authstate.pw_name); + ses.authstate.checkusername_failed = 1; + return DROPBEAR_FAILURE; + } + } +#endif /* HAVE_GETGROUPLIST */ + + TRACE(("shell is %s", ses.authstate.pw_shell)) + + /* check that the shell is set */ + usershell = ses.authstate.pw_shell; + if (usershell[0] == '\0') { + /* empty shell in /etc/passwd means /bin/sh according to passwd(5) */ + usershell = "/bin/sh"; + } + + /* check the shell is valid. If /etc/shells doesn't exist, getusershell() + * should return some standard shells like "/bin/sh" and "/bin/csh" (this + * is platform-specific) */ + setusershell(); + while ((listshell = getusershell()) != NULL) { + TRACE(("test shell is '%s'", listshell)) + if (strcmp(listshell, usershell) == 0) { + /* have a match */ + goto goodshell; + } + } + /* no matching shell */ + endusershell(); + TRACE(("no matching shell")) + ses.authstate.checkusername_failed = 1; + dropbear_log(LOG_WARNING, "User '%s' has invalid shell, rejected", + ses.authstate.pw_name); + return DROPBEAR_FAILURE; + +goodshell: + endusershell(); + TRACE(("matching shell")) + + TRACE(("uid = %d", ses.authstate.pw_uid)) + TRACE(("leave checkusername")) + return DROPBEAR_SUCCESS; +} + +/* Send a failure message to the client, in responds to a userauth_request. + * Partial indicates whether to set the "partial success" flag, + * incrfail is whether to count this failure in the failure count (which + * is limited. This function also handles disconnection after too many + * failures */ +void send_msg_userauth_failure(int partial, int incrfail) { + + buffer *typebuf = NULL; + + TRACE(("enter send_msg_userauth_failure")) + + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_FAILURE); + + /* put a list of allowed types */ + typebuf = buf_new(30); /* long enough for PUBKEY and PASSWORD */ + + if (ses.authstate.authtypes & AUTH_TYPE_PUBKEY) { + buf_putbytes(typebuf, (const unsigned char *)AUTH_METHOD_PUBKEY, AUTH_METHOD_PUBKEY_LEN); + if (ses.authstate.authtypes & AUTH_TYPE_PASSWORD) { + buf_putbyte(typebuf, ','); + } + } + + if (ses.authstate.authtypes & AUTH_TYPE_PASSWORD) { + buf_putbytes(typebuf, (const unsigned char *)AUTH_METHOD_PASSWORD, AUTH_METHOD_PASSWORD_LEN); + } + + buf_putbufstring(ses.writepayload, typebuf); + + TRACE(("auth fail: methods %d, '%.*s'", ses.authstate.authtypes, + typebuf->len, typebuf->data)) + + buf_free(typebuf); + + buf_putbyte(ses.writepayload, partial ? 1 : 0); + encrypt_packet(); + + if (incrfail) { + /* The SSH_MSG_AUTH_FAILURE response is delayed to attempt to + avoid user enumeration and slow brute force attempts. + The delay is adjusted by the time already spent in processing + authentication (ses.authstate.auth_starttime timestamp). */ + + /* Desired total delay 300ms +-50ms (in nanoseconds). + Beware of integer overflow if increasing these values */ + const unsigned int mindelay = 250000000; + const unsigned int vardelay = 100000000; + suseconds_t rand_delay; + struct timespec delay; + + gettime_wrapper(&delay); + delay.tv_sec -= ses.authstate.auth_starttime.tv_sec; + delay.tv_nsec -= ses.authstate.auth_starttime.tv_nsec; + + /* carry */ + if (delay.tv_nsec < 0) { + delay.tv_nsec += 1000000000; + delay.tv_sec -= 1; + } + + genrandom((unsigned char*)&rand_delay, sizeof(rand_delay)); + rand_delay = mindelay + (rand_delay % vardelay); + + if (delay.tv_sec == 0 && delay.tv_nsec <= mindelay) { + /* Compensate for elapsed time */ + delay.tv_nsec = rand_delay - delay.tv_nsec; + } else { + /* No time left or time went backwards, just delay anyway */ + delay.tv_sec = 0; + delay.tv_nsec = rand_delay; + } + + +#if DROPBEAR_FUZZ + if (!fuzz.fuzzing) +#endif + { + while (nanosleep(&delay, &delay) == -1 && errno == EINTR) { /* Go back to sleep */ } + } + + ses.authstate.failcount++; + } + + if (ses.authstate.failcount >= svr_opts.maxauthtries) { + char * userstr; + /* XXX - send disconnect ? */ + TRACE(("Max auth tries reached, exiting")) + + if (ses.authstate.pw_name == NULL) { + userstr = "is invalid"; + } else { + userstr = ses.authstate.pw_name; + } + dropbear_exit("Max auth tries reached - user '%s'", + userstr); + } + + TRACE(("leave send_msg_userauth_failure")) +} + +/* Send a success message to the user, and set the "authdone" flag */ +void send_msg_userauth_success() { + + TRACE(("enter send_msg_userauth_success")) + + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_SUCCESS); + encrypt_packet(); + + /* authdone must be set after encrypt_packet() for + * delayed-zlib mode */ + ses.authstate.authdone = 1; + ses.connect_time = 0; + + + if (ses.authstate.pw_uid == 0) { + ses.allowprivport = 1; + } + + /* Remove from the list of pre-auth sockets. Should be m_close(), since if + * we fail, we might end up leaking connection slots, and disallow new + * logins - a nasty situation. */ + m_close(svr_ses.childpipe); + + TRACE(("leave send_msg_userauth_success")) + +} diff --git a/src/svr-authpam.c b/src/svr-authpam.c new file mode 100644 index 0000000..ec14632 --- /dev/null +++ b/src/svr-authpam.c @@ -0,0 +1,309 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2004 Martin Carlsson + * Portions (c) 2004 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* Validates a user password using PAM */ + +#include "includes.h" +#include "session.h" +#include "buffer.h" +#include "dbutil.h" +#include "auth.h" +#include "runopts.h" + +#if DROPBEAR_SVR_PAM_AUTH + +#if defined(HAVE_SECURITY_PAM_APPL_H) +#include <security/pam_appl.h> +#elif defined (HAVE_PAM_PAM_APPL_H) +#include <pam/pam_appl.h> +#endif + +struct UserDataS { + char* user; + char* passwd; +}; + +/* PAM conversation function - for now we only handle one message */ +int +pamConvFunc(int num_msg, + const struct pam_message **msg, + struct pam_response **respp, + void *appdata_ptr) { + + int rc = PAM_SUCCESS; + struct pam_response* resp = NULL; + struct UserDataS* userDatap = (struct UserDataS*) appdata_ptr; + unsigned int msg_len = 0; + unsigned int i = 0; + char * compare_message = NULL; + + TRACE(("enter pamConvFunc")) + + if (num_msg != 1) { + /* If you're getting here - Dropbear probably can't support your pam + * modules. This whole file is a bit of a hack around lack of + * asynchronocity in PAM anyway. */ + dropbear_log(LOG_INFO, "pamConvFunc() called with >1 messages: not supported."); + return PAM_CONV_ERR; + } + + /* make a copy we can strip */ + compare_message = m_strdup((*msg)->msg); + + /* Make the string lowercase. */ + msg_len = strlen(compare_message); + for (i = 0; i < msg_len; i++) { + compare_message[i] = tolower(compare_message[i]); + } + + /* If the string ends with ": ", remove the space. + ie "login: " vs "login:" */ + if (msg_len > 2 + && compare_message[msg_len-2] == ':' + && compare_message[msg_len-1] == ' ') { + compare_message[msg_len-1] = '\0'; + } + + switch((*msg)->msg_style) { + + case PAM_PROMPT_ECHO_OFF: + + if (!(strcmp(compare_message, "password:") == 0)) { + /* We don't recognise the prompt as asking for a password, + so can't handle it. Add more above as required for + different pam modules/implementations. If you need + to add an entry here please mail the Dropbear developer */ + dropbear_log(LOG_NOTICE, "PAM unknown prompt '%s' (no echo)", + compare_message); + rc = PAM_CONV_ERR; + break; + } + + /* You have to read the PAM module-writers' docs (do we look like + * module writers? no.) to find out that the module will + * free the pam_response and its resp element - ie we _must_ malloc + * it here */ + resp = (struct pam_response*) m_malloc(sizeof(struct pam_response)); + memset(resp, 0, sizeof(struct pam_response)); + + resp->resp = m_strdup(userDatap->passwd); + m_burn(userDatap->passwd, strlen(userDatap->passwd)); + (*respp) = resp; + break; + + + case PAM_PROMPT_ECHO_ON: + + if (!( + (strcmp(compare_message, "login:" ) == 0) + || (strcmp(compare_message, "please enter username:") == 0) + || (strcmp(compare_message, "username:") == 0) + )) { + /* We don't recognise the prompt as asking for a username, + so can't handle it. Add more above as required for + different pam modules/implementations. If you need + to add an entry here please mail the Dropbear developer */ + dropbear_log(LOG_NOTICE, "PAM unknown prompt '%s' (with echo)", + compare_message); + rc = PAM_CONV_ERR; + break; + } + + /* You have to read the PAM module-writers' docs (do we look like + * module writers? no.) to find out that the module will + * free the pam_response and its resp element - ie we _must_ malloc + * it here */ + resp = (struct pam_response*) m_malloc(sizeof(struct pam_response)); + memset(resp, 0, sizeof(struct pam_response)); + + resp->resp = m_strdup(userDatap->user); + TRACE(("userDatap->user='%s'", userDatap->user)) + (*respp) = resp; + break; + + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + + if (msg_len > 0) { + buffer * pam_err = buf_new(msg_len + 4); + buf_setpos(pam_err, 0); + buf_putbytes(pam_err, "\r\n", 2); + buf_putbytes(pam_err, (*msg)->msg, msg_len); + buf_putbytes(pam_err, "\r\n", 2); + buf_setpos(pam_err, 0); + + send_msg_userauth_banner(pam_err); + buf_free(pam_err); + } + break; + + default: + TRACE(("Unknown message type")) + rc = PAM_CONV_ERR; + break; + } + + m_free(compare_message); + TRACE(("leave pamConvFunc, rc %d", rc)) + + return rc; +} + +/* Process a password auth request, sending success or failure messages as + * appropriate. To the client it looks like it's doing normal password auth (as + * opposed to keyboard-interactive or something), so the pam module has to be + * fairly standard (ie just "what's your username, what's your password, OK"). + * + * Keyboard interactive would be a lot nicer, but since PAM is synchronous, it + * gets very messy trying to send the interactive challenges, and read the + * interactive responses, over the network. */ +void svr_auth_pam(int valid_user) { + + struct UserDataS userData = {NULL, NULL}; + struct pam_conv pamConv = { + pamConvFunc, + &userData /* submitted to pamvConvFunc as appdata_ptr */ + }; + const char* printable_user = NULL; + + pam_handle_t* pamHandlep = NULL; + + char * password = NULL; + unsigned int passwordlen; + + int rc = PAM_SUCCESS; + unsigned char changepw; + + /* check if client wants to change password */ + changepw = buf_getbool(ses.payload); + if (changepw) { + /* not implemented by this server */ + send_msg_userauth_failure(0, 1); + goto cleanup; + } + + password = buf_getstring(ses.payload, &passwordlen); + + /* We run the PAM conversation regardless of whether the username is valid + in case the conversation function has an inherent delay. + Use ses.authstate.username rather than ses.authstate.pw_name. + After PAM succeeds we then check the valid_user flag too */ + + /* used to pass data to the PAM conversation function - don't bother with + * strdup() etc since these are touched only by our own conversation + * function (above) which takes care of it */ + userData.user = ses.authstate.username; + userData.passwd = password; + + if (ses.authstate.pw_name) { + printable_user = ses.authstate.pw_name; + } else { + printable_user = "<invalid username>"; + } + + /* Init pam */ + if ((rc = pam_start("sshd", NULL, &pamConv, &pamHandlep)) != PAM_SUCCESS) { + dropbear_log(LOG_WARNING, "pam_start() failed, rc=%d, %s", + rc, pam_strerror(pamHandlep, rc)); + goto cleanup; + } + + /* just to set it to something */ + if ((rc = pam_set_item(pamHandlep, PAM_TTY, "ssh")) != PAM_SUCCESS) { + dropbear_log(LOG_WARNING, "pam_set_item() failed, rc=%d, %s", + rc, pam_strerror(pamHandlep, rc)); + goto cleanup; + } + + if ((rc = pam_set_item(pamHandlep, PAM_RHOST, svr_ses.remotehost)) != PAM_SUCCESS) { + dropbear_log(LOG_WARNING, "pam_set_item() failed, rc=%d, %s", + rc, pam_strerror(pamHandlep, rc)); + goto cleanup; + } + +#ifdef HAVE_PAM_FAIL_DELAY + /* We have our own random delay code already, disable PAM's */ + (void) pam_fail_delay(pamHandlep, 0 /* musec_delay */); +#endif + + /* (void) pam_set_item(pamHandlep, PAM_FAIL_DELAY, (void*) pamDelayFunc); */ + + if ((rc = pam_authenticate(pamHandlep, 0)) != PAM_SUCCESS) { + dropbear_log(LOG_WARNING, "pam_authenticate() failed, rc=%d, %s", + rc, pam_strerror(pamHandlep, rc)); + dropbear_log(LOG_WARNING, + "Bad PAM password attempt for '%s' from %s", + printable_user, + svr_ses.addrstring); + send_msg_userauth_failure(0, 1); + goto cleanup; + } + + if ((rc = pam_acct_mgmt(pamHandlep, 0)) != PAM_SUCCESS) { + dropbear_log(LOG_WARNING, "pam_acct_mgmt() failed, rc=%d, %s", + rc, pam_strerror(pamHandlep, rc)); + dropbear_log(LOG_WARNING, + "Bad PAM password attempt for '%s' from %s", + printable_user, + svr_ses.addrstring); + send_msg_userauth_failure(0, 1); + goto cleanup; + } + + if (!valid_user) { + /* PAM auth succeeded but the username isn't allowed in for another reason + (checkusername() failed) */ + send_msg_userauth_failure(0, 1); + goto cleanup; + } + + if (svr_opts.multiauthmethod && (ses.authstate.authtypes & ~AUTH_TYPE_PASSWORD)) { + /* successful PAM password authentication, but extra auth required */ + dropbear_log(LOG_NOTICE, + "PAM password auth succeeded for '%s' from %s, extra auth required", + ses.authstate.pw_name, + svr_ses.addrstring); + ses.authstate.authtypes &= ~AUTH_TYPE_PASSWORD; /* PAM password auth ok, delete the method flag */ + send_msg_userauth_failure(1, 0); /* Send partial success */ + } else { + /* successful authentication */ + dropbear_log(LOG_NOTICE, "PAM password auth succeeded for '%s' from %s", + ses.authstate.pw_name, + svr_ses.addrstring); + send_msg_userauth_success(); + } + +cleanup: + if (password != NULL) { + m_burn(password, passwordlen); + m_free(password); + } + if (pamHandlep != NULL) { + TRACE(("pam_end")) + (void) pam_end(pamHandlep, 0 /* pam_status */); + } +} + +#endif /* DROPBEAR_SVR_PAM_AUTH */ diff --git a/src/svr-authpasswd.c b/src/svr-authpasswd.c new file mode 100644 index 0000000..899a8ab --- /dev/null +++ b/src/svr-authpasswd.c @@ -0,0 +1,134 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +/* Validates a user password */ + +#include "includes.h" +#include "session.h" +#include "buffer.h" +#include "dbutil.h" +#include "auth.h" +#include "runopts.h" + +#if DROPBEAR_SVR_PASSWORD_AUTH + +/* not constant time when strings are differing lengths. + string content isn't leaked, and crypt hashes are predictable length. */ +static int constant_time_strcmp(const char* a, const char* b) { + size_t la = strlen(a); + size_t lb = strlen(b); + + if (la != lb) { + return 1; + } + + return constant_time_memcmp(a, b, la); +} + +/* Process a password auth request, sending success or failure messages as + * appropriate */ +void svr_auth_password(int valid_user) { + + char * passwdcrypt = NULL; /* the crypt from /etc/passwd or /etc/shadow */ + char * testcrypt = NULL; /* crypt generated from the user's password sent */ + char * password = NULL; + unsigned int passwordlen; + unsigned int changepw; + + /* check if client wants to change password */ + changepw = buf_getbool(ses.payload); + if (changepw) { + /* not implemented by this server */ + send_msg_userauth_failure(0, 1); + return; + } + + password = buf_getstring(ses.payload, &passwordlen); + if (valid_user && passwordlen <= DROPBEAR_MAX_PASSWORD_LEN) { + /* the first bytes of passwdcrypt are the salt */ + passwdcrypt = ses.authstate.pw_passwd; + testcrypt = crypt(password, passwdcrypt); + } + m_burn(password, passwordlen); + m_free(password); + + /* After we have got the payload contents we can exit if the username + is invalid. Invalid users have already been logged. */ + if (!valid_user) { + send_msg_userauth_failure(0, 1); + return; + } + + if (passwordlen > DROPBEAR_MAX_PASSWORD_LEN) { + dropbear_log(LOG_WARNING, + "Too-long password attempt for '%s' from %s", + ses.authstate.pw_name, + svr_ses.addrstring); + send_msg_userauth_failure(0, 1); + return; + } + + if (testcrypt == NULL) { + /* crypt() with an invalid salt like "!!" */ + dropbear_log(LOG_WARNING, "User account '%s' is locked", + ses.authstate.pw_name); + send_msg_userauth_failure(0, 1); + return; + } + + /* check for empty password */ + if (passwdcrypt[0] == '\0') { + dropbear_log(LOG_WARNING, "User '%s' has blank password, rejected", + ses.authstate.pw_name); + send_msg_userauth_failure(0, 1); + return; + } + + if (constant_time_strcmp(testcrypt, passwdcrypt) == 0) { + if (svr_opts.multiauthmethod && (ses.authstate.authtypes & ~AUTH_TYPE_PASSWORD)) { + /* successful password authentication, but extra auth required */ + dropbear_log(LOG_NOTICE, + "Password auth succeeded for '%s' from %s, extra auth required", + ses.authstate.pw_name, + svr_ses.addrstring); + ses.authstate.authtypes &= ~AUTH_TYPE_PASSWORD; /* password auth ok, delete the method flag */ + send_msg_userauth_failure(1, 0); /* Send partial success */ + } else { + /* successful authentication */ + dropbear_log(LOG_NOTICE, + "Password auth succeeded for '%s' from %s", + ses.authstate.pw_name, + svr_ses.addrstring); + send_msg_userauth_success(); + } + } else { + dropbear_log(LOG_WARNING, + "Bad password attempt for '%s' from %s", + ses.authstate.pw_name, + svr_ses.addrstring); + send_msg_userauth_failure(0, 1); + } +} + +#endif diff --git a/src/svr-authpubkey.c b/src/svr-authpubkey.c new file mode 100644 index 0000000..5d298cb --- /dev/null +++ b/src/svr-authpubkey.c @@ -0,0 +1,624 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ +/* + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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. + * + * 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. + * + * This copyright and permission notice applies to the code parsing public keys + * options string which can also be found in OpenSSH auth2-pubkey.c file + * (user_key_allowed2). It has been adapted to work with buffers. + * + */ + +/* Process a pubkey auth request */ + +#include "includes.h" +#include "session.h" +#include "dbutil.h" +#include "buffer.h" +#include "signkey.h" +#include "auth.h" +#include "ssh.h" +#include "packet.h" +#include "algo.h" +#include "runopts.h" + +#if DROPBEAR_SVR_PUBKEY_AUTH + +#define MIN_AUTHKEYS_LINE 10 /* "ssh-rsa AB" - short but doesn't matter */ +#define MAX_AUTHKEYS_LINE 4200 /* max length of a line in authkeys */ + +static int checkpubkey(const char* keyalgo, unsigned int keyalgolen, + const unsigned char* keyblob, unsigned int keybloblen); +static int checkpubkeyperms(void); +static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen, + const unsigned char* keyblob, unsigned int keybloblen); +static int checkfileperm(char * filename); + +/* process a pubkey auth request, sending success or failure message as + * appropriate */ +void svr_auth_pubkey(int valid_user) { + + unsigned char testkey; /* whether we're just checking if a key is usable */ + char* sigalgo = NULL; + unsigned int sigalgolen; + const char* keyalgo; + unsigned int keyalgolen; + unsigned char* keyblob = NULL; + unsigned int keybloblen; + unsigned int sign_payload_length; + buffer * signbuf = NULL; + sign_key * key = NULL; + char* fp = NULL; + enum signature_type sigtype; + enum signkey_type keytype; + int auth_failure = 1; + + TRACE(("enter pubkeyauth")) + + /* 0 indicates user just wants to check if key can be used, 1 is an + * actual attempt*/ + testkey = (buf_getbool(ses.payload) == 0); + + sigalgo = buf_getstring(ses.payload, &sigalgolen); + keybloblen = buf_getint(ses.payload); + keyblob = buf_getptr(ses.payload, keybloblen); + + if (!valid_user) { + /* Return failure once we have read the contents of the packet + required to validate a public key. + Avoids blind user enumeration though it isn't possible to prevent + testing for user existence if the public key is known */ + send_msg_userauth_failure(0, 0); + goto out; + } + + sigtype = signature_type_from_name(sigalgo, sigalgolen); + if (sigtype == DROPBEAR_SIGNATURE_NONE) { + send_msg_userauth_failure(0, 0); + goto out; + } + + keytype = signkey_type_from_signature(sigtype); + keyalgo = signkey_name_from_type(keytype, &keyalgolen); + +#if DROPBEAR_PLUGIN + if (svr_ses.plugin_instance != NULL) { + char *options_buf; + if (svr_ses.plugin_instance->checkpubkey( + svr_ses.plugin_instance, + &ses.plugin_session, + keyalgo, + keyalgolen, + keyblob, + keybloblen, + ses.authstate.username) == DROPBEAR_SUCCESS) { + /* Success */ + auth_failure = 0; + + /* Options provided? */ + options_buf = ses.plugin_session->get_options(ses.plugin_session); + if (options_buf) { + struct buf temp_buf = { + .data = (unsigned char *)options_buf, + .len = strlen(options_buf), + .pos = 0, + .size = 0 + }; + int ret = svr_add_pubkey_options(&temp_buf, 0, "N/A"); + if (ret == DROPBEAR_FAILURE) { + /* Fail immediately as the plugin provided wrong options */ + send_msg_userauth_failure(0, 0); + goto out; + } + } + } + } +#endif + /* check if the key is valid */ + if (auth_failure) { + auth_failure = checkpubkey(keyalgo, keyalgolen, keyblob, keybloblen) == DROPBEAR_FAILURE; + } + + if (auth_failure) { + send_msg_userauth_failure(0, 0); + goto out; + } + + /* let them know that the key is ok to use */ + if (testkey) { + send_msg_userauth_pk_ok(sigalgo, sigalgolen, keyblob, keybloblen); + goto out; + } + + /* now we can actually verify the signature */ + + /* get the key */ + key = new_sign_key(); + if (buf_get_pub_key(ses.payload, key, &keytype) == DROPBEAR_FAILURE) { + send_msg_userauth_failure(0, 1); + goto out; + } + +#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 + key->sk_flags_mask = SSH_SK_USER_PRESENCE_REQD; + if (ses.authstate.pubkey_options && ses.authstate.pubkey_options->no_touch_required_flag) { + key->sk_flags_mask &= ~SSH_SK_USER_PRESENCE_REQD; + } + if (ses.authstate.pubkey_options && ses.authstate.pubkey_options->verify_required_flag) { + key->sk_flags_mask |= SSH_SK_USER_VERIFICATION_REQD; + } +#endif + + /* create the data which has been signed - this a string containing + * session_id, concatenated with the payload packet up to the signature */ + assert(ses.payload_beginning <= ses.payload->pos); + sign_payload_length = ses.payload->pos - ses.payload_beginning; + signbuf = buf_new(ses.payload->pos + 4 + ses.session_id->len); + buf_putbufstring(signbuf, ses.session_id); + + /* The entire contents of the payload prior. */ + buf_setpos(ses.payload, ses.payload_beginning); + buf_putbytes(signbuf, + buf_getptr(ses.payload, sign_payload_length), + sign_payload_length); + buf_incrpos(ses.payload, sign_payload_length); + + buf_setpos(signbuf, 0); + + /* ... and finally verify the signature */ + fp = sign_key_fingerprint(keyblob, keybloblen); + if (buf_verify(ses.payload, key, sigtype, signbuf) == DROPBEAR_SUCCESS) { + if (svr_opts.multiauthmethod && (ses.authstate.authtypes & ~AUTH_TYPE_PUBKEY)) { + /* successful pubkey authentication, but extra auth required */ + dropbear_log(LOG_NOTICE, + "Pubkey auth succeeded for '%s' with %s key %s from %s, extra auth required", + ses.authstate.pw_name, + signkey_name_from_type(keytype, NULL), fp, + svr_ses.addrstring); + ses.authstate.authtypes &= ~AUTH_TYPE_PUBKEY; /* pubkey auth ok, delete the method flag */ + send_msg_userauth_failure(1, 0); /* Send partial success */ + } else { + /* successful authentication */ + dropbear_log(LOG_NOTICE, + "Pubkey auth succeeded for '%s' with %s key %s from %s", + ses.authstate.pw_name, + signkey_name_from_type(keytype, NULL), fp, + svr_ses.addrstring); + send_msg_userauth_success(); + } +#if DROPBEAR_PLUGIN + if ((ses.plugin_session != NULL) && (svr_ses.plugin_instance->auth_success != NULL)) { + /* Was authenticated through the external plugin. tell plugin that signature verification was ok */ + svr_ses.plugin_instance->auth_success(ses.plugin_session); + } +#endif + } else { + dropbear_log(LOG_WARNING, + "Pubkey auth bad signature for '%s' with key %s from %s", + ses.authstate.pw_name, fp, svr_ses.addrstring); + send_msg_userauth_failure(0, 1); + } + m_free(fp); + +out: + /* cleanup stuff */ + if (signbuf) { + buf_free(signbuf); + } + if (sigalgo) { + m_free(sigalgo); + } + if (key) { + sign_key_free(key); + key = NULL; + } + /* Retain pubkey options only if auth succeeded */ + if (!ses.authstate.authdone) { + svr_pubkey_options_cleanup(); + } + TRACE(("leave pubkeyauth")) +} + +/* Reply that the key is valid for auth, this is sent when the user sends + * a straight copy of their pubkey to test, to avoid having to perform + * expensive signing operations with a worthless key */ +static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen, + const unsigned char* keyblob, unsigned int keybloblen) { + + TRACE(("enter send_msg_userauth_pk_ok")) + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_PK_OK); + buf_putstring(ses.writepayload, sigalgo, sigalgolen); + buf_putstring(ses.writepayload, (const char*)keyblob, keybloblen); + + encrypt_packet(); + TRACE(("leave send_msg_userauth_pk_ok")) + +} + +/* Content for SSH_PUBKEYINFO is optionally returned malloced in ret_info (will be + freed if already set */ +static int checkpubkey_line(buffer* line, int line_num, const char* filename, + const char* algo, unsigned int algolen, + const unsigned char* keyblob, unsigned int keybloblen, + char ** ret_info) { + buffer *options_buf = NULL; + char *info_str = NULL; + unsigned int pos, len, infopos, infolen; + int ret = DROPBEAR_FAILURE; + + if (line->len < MIN_AUTHKEYS_LINE || line->len > MAX_AUTHKEYS_LINE) { + TRACE(("checkpubkey_line: bad line length %d", line->len)) + goto out; + } + + if (memchr(line->data, 0x0, line->len) != NULL) { + TRACE(("checkpubkey_line: bad line has null char")) + goto out; + } + + /* compare the algorithm. +3 so we have enough bytes to read a space and some base64 characters too. */ + if (line->pos + algolen+3 > line->len) { + goto out; + } + /* check the key type */ + if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) { + int is_comment = 0; + unsigned char *options_start = NULL; + int options_len = 0; + int escape, quoted; + + /* skip over any comments or leading whitespace */ + while (line->pos < line->len) { + const char c = buf_getbyte(line); + if (c == ' ' || c == '\t') { + continue; + } else if (c == '#') { + is_comment = 1; + break; + } + buf_decrpos(line, 1); + break; + } + if (is_comment) { + /* next line */ + goto out; + } + + /* remember start of options */ + options_start = buf_getptr(line, 1); + quoted = 0; + escape = 0; + options_len = 0; + + /* figure out where the options are */ + while (line->pos < line->len) { + const char c = buf_getbyte(line); + if (!quoted && (c == ' ' || c == '\t')) { + break; + } + escape = (!escape && c == '\\'); + if (!escape && c == '"') { + quoted = !quoted; + } + options_len++; + } + options_buf = buf_new(options_len); + buf_putbytes(options_buf, options_start, options_len); + + /* compare the algorithm. +3 so we have enough bytes to read a space and some base64 characters too. */ + if (line->pos + algolen+3 > line->len) { + goto out; + } + if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) { + goto out; + } + } + buf_incrpos(line, algolen); + + /* check for space (' ') character */ + if (buf_getbyte(line) != ' ') { + TRACE(("checkpubkey_line: space character expected, isn't there")) + goto out; + } + + /* find the length of base64 data */ + pos = line->pos; + for (len = 0; line->pos < line->len; len++) { + if (buf_getbyte(line) == ' ') { + break; + } + } + + /* find out the length of the public key info, stop at the first space */ + infopos = line->pos; + for (infolen = 0; line->pos < line->len; infolen++) { + const char c = buf_getbyte(line); + if (c == ' ') { + break; + } + /* We have an allowlist - authorized_keys lines can't be fully trusted, + some shell scripts may do unsafe things with env var values */ + if (!(isalnum(c) || strchr(".,_-+@", c))) { + TRACE(("Not setting SSH_PUBKEYINFO, special characters")) + infolen = 0; + break; + } + } + if (infolen > 0) { + info_str = m_malloc(infolen + 1); + buf_setpos(line, infopos); + strncpy(info_str, buf_getptr(line, infolen), infolen); + } + + /* truncate to base64 data length */ + buf_setpos(line, pos); + buf_setlen(line, line->pos + len); + + TRACE(("checkpubkey_line: line pos = %d len = %d", line->pos, line->len)) + + ret = cmp_base64_key(keyblob, keybloblen, (const unsigned char *) algo, algolen, line, NULL); + + /* free pubkey_info if it is filled */ + if (ret_info && *ret_info) { + m_free(*ret_info); + *ret_info = NULL; + } + + if (ret == DROPBEAR_SUCCESS) { + if (options_buf) { + ret = svr_add_pubkey_options(options_buf, line_num, filename); + } + if (ret_info) { + /* take the (optional) public key information */ + *ret_info = info_str; + info_str = NULL; + } + } + +out: + if (options_buf) { + buf_free(options_buf); + } + if (info_str) { + m_free(info_str); + } + return ret; +} + + +/* Checks whether a specified publickey (and associated algorithm) is an + * acceptable key for authentication */ +/* Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */ +static int checkpubkey(const char* keyalgo, unsigned int keyalgolen, + const unsigned char* keyblob, unsigned int keybloblen) { + + FILE * authfile = NULL; + char * filename = NULL; + int ret = DROPBEAR_FAILURE; + buffer * line = NULL; + unsigned int len; + int line_num; + uid_t origuid; + gid_t origgid; + + TRACE(("enter checkpubkey")) + +#if DROPBEAR_SVR_MULTIUSER + /* access the file as the authenticating user. */ + origuid = getuid(); + origgid = getgid(); + if ((setegid(ses.authstate.pw_gid)) < 0 || + (seteuid(ses.authstate.pw_uid)) < 0) { + dropbear_exit("Failed to set euid"); + } +#endif + /* check file permissions, also whether file exists */ + if (checkpubkeyperms() == DROPBEAR_FAILURE) { + TRACE(("bad authorized_keys permissions, or file doesn't exist")) + } else { + /* we don't need to check pw and pw_dir for validity, since + * its been done in checkpubkeyperms. */ + len = strlen(ses.authstate.pw_dir); + /* allocate max required pathname storage, + * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */ + filename = m_malloc(len + 22); + snprintf(filename, len + 22, "%s/.ssh/authorized_keys", + ses.authstate.pw_dir); + + authfile = fopen(filename, "r"); + if (!authfile) { + TRACE(("checkpubkey: failed opening %s: %s", filename, strerror(errno))) + } + } +#if DROPBEAR_SVR_MULTIUSER + if ((seteuid(origuid)) < 0 || + (setegid(origgid)) < 0) { + dropbear_exit("Failed to revert euid"); + } +#endif + + if (authfile == NULL) { + goto out; + } + TRACE(("checkpubkey: opened authorized_keys OK")) + + line = buf_new(MAX_AUTHKEYS_LINE); + line_num = 0; + + /* iterate through the lines */ + do { + if (buf_getline(line, authfile) == DROPBEAR_FAILURE) { + /* EOF reached */ + TRACE(("checkpubkey: authorized_keys EOF reached")) + break; + } + line_num++; + + ret = checkpubkey_line(line, line_num, filename, keyalgo, keyalgolen, + keyblob, keybloblen, &ses.authstate.pubkey_info); + if (ret == DROPBEAR_SUCCESS) { + break; + } + + /* We continue to the next line otherwise */ + } while (1); + +out: + if (authfile) { + fclose(authfile); + } + if (line) { + buf_free(line); + } + m_free(filename); + TRACE(("leave checkpubkey: ret=%d", ret)) + return ret; +} + + +/* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok, + * DROPBEAR_FAILURE otherwise. + * Checks that the user's homedir, ~/.ssh, and + * ~/.ssh/authorized_keys are all owned by either root or the user, and are + * g-w, o-w */ +static int checkpubkeyperms() { + + char* filename = NULL; + int ret = DROPBEAR_FAILURE; + unsigned int len; + + TRACE(("enter checkpubkeyperms")) + + if (ses.authstate.pw_dir == NULL) { + goto out; + } + + if ((len = strlen(ses.authstate.pw_dir)) == 0) { + goto out; + } + + /* allocate max required pathname storage, + * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */ + len += 22; + filename = m_malloc(len); + strlcpy(filename, ses.authstate.pw_dir, len); + + /* check ~ */ + if (checkfileperm(filename) != DROPBEAR_SUCCESS) { + goto out; + } + + /* check ~/.ssh */ + strlcat(filename, "/.ssh", len); + if (checkfileperm(filename) != DROPBEAR_SUCCESS) { + goto out; + } + + /* now check ~/.ssh/authorized_keys */ + strlcat(filename, "/authorized_keys", len); + if (checkfileperm(filename) != DROPBEAR_SUCCESS) { + goto out; + } + + /* file looks ok, return success */ + ret = DROPBEAR_SUCCESS; + +out: + m_free(filename); + + TRACE(("leave checkpubkeyperms")) + return ret; +} + +/* Checks that a file is owned by the user or root, and isn't writable by + * group or other */ +/* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int checkfileperm(char * filename) { + struct stat filestat; + int badperm = 0; + + TRACE(("enter checkfileperm(%s)", filename)) + + if (stat(filename, &filestat) != 0) { + TRACE(("leave checkfileperm: stat() != 0")) + return DROPBEAR_FAILURE; + } + /* check ownership - user or root only*/ + if (filestat.st_uid != ses.authstate.pw_uid + && filestat.st_uid != 0) { + badperm = 1; + TRACE(("wrong ownership")) + } + /* check permissions - don't want group or others +w */ + if (filestat.st_mode & (S_IWGRP | S_IWOTH)) { + badperm = 1; + TRACE(("wrong perms")) + } + if (badperm) { + if (!ses.authstate.perm_warn) { + ses.authstate.perm_warn = 1; + dropbear_log(LOG_INFO, "%s must be owned by user or root, and not writable by group or others", filename); + } + TRACE(("leave checkfileperm: failure perms/owner")) + return DROPBEAR_FAILURE; + } + + TRACE(("leave checkfileperm: success")) + return DROPBEAR_SUCCESS; +} + +#if DROPBEAR_FUZZ +int fuzz_checkpubkey_line(buffer* line, int line_num, char* filename, + const char* algo, unsigned int algolen, + const unsigned char* keyblob, unsigned int keybloblen) { + return checkpubkey_line(line, line_num, filename, algo, algolen, keyblob, keybloblen, NULL); +} +#endif + +#endif diff --git a/src/svr-authpubkeyoptions.c b/src/svr-authpubkeyoptions.c new file mode 100644 index 0000000..df9a7df --- /dev/null +++ b/src/svr-authpubkeyoptions.c @@ -0,0 +1,331 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2008 Frederic Moulins + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Author: Tatu Ylonen <ylo@cs.hut.fi> + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + * + * This copyright and permission notice applies to the code parsing public keys + * options string which can also be found in OpenSSH auth-options.c file + * (auth_parse_options). + * + */ + +/* Process pubkey options during a pubkey auth request */ +#include "includes.h" +#include "session.h" +#include "dbutil.h" +#include "signkey.h" +#include "auth.h" +#include "runopts.h" + +#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT + +/* Returns 1 if pubkey allows agent forwarding, + * 0 otherwise */ +int svr_pubkey_allows_agentfwd() { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->no_agent_forwarding_flag) { + return 0; + } + return 1; +} + +/* Returns 1 if pubkey allows tcp forwarding, + * 0 otherwise */ +int svr_pubkey_allows_tcpfwd() { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->no_port_forwarding_flag) { + return 0; + } + return 1; +} + +/* Returns 1 if pubkey allows x11 forwarding, + * 0 otherwise */ +int svr_pubkey_allows_x11fwd() { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->no_x11_forwarding_flag) { + return 0; + } + return 1; +} + +/* Returns 1 if pubkey allows pty, 0 otherwise */ +int svr_pubkey_allows_pty() { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->no_pty_flag) { + return 0; + } + return 1; +} + +/* Returns 1 if pubkey allows local tcp fowarding to the provided destination, + * 0 otherwise */ +int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) { + if (ses.authstate.pubkey_options + && ses.authstate.pubkey_options->permit_open_destinations) { + m_list_elem *iter = ses.authstate.pubkey_options->permit_open_destinations->first; + while (iter) { + struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)iter->item; + if (strcmp(entry->host, host) == 0) { + if ((entry->port == PUBKEY_OPTIONS_ANY_PORT) || (entry->port == port)) { + return 1; + } + } + + iter = iter->next; + } + + return 0; + } + + return 1; +} + +/* Set chansession command to the one forced + * by any 'command' public key option. */ +void svr_pubkey_set_forced_command(struct ChanSess *chansess) { + if (ses.authstate.pubkey_options && ses.authstate.pubkey_options->forced_command) { + TRACE(("Forced command '%s'", ses.authstate.pubkey_options->forced_command)) + if (chansess->cmd) { + /* original_command takes ownership */ + chansess->original_command = chansess->cmd; + chansess->cmd = NULL; + } else { + chansess->original_command = m_strdup(""); + } + chansess->cmd = m_strdup(ses.authstate.pubkey_options->forced_command); +#if LOG_COMMANDS + dropbear_log(LOG_INFO, "Command forced to '%s'", chansess->original_command); +#endif + } +} + +/* Free potential public key options */ +void svr_pubkey_options_cleanup() { + if (ses.authstate.pubkey_options) { + if (ses.authstate.pubkey_options->forced_command) { + m_free(ses.authstate.pubkey_options->forced_command); + } + if (ses.authstate.pubkey_options->permit_open_destinations) { + m_list_elem *iter = ses.authstate.pubkey_options->permit_open_destinations->first; + while (iter) { + struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)list_remove(iter); + m_free(entry->host); + m_free(entry); + iter = ses.authstate.pubkey_options->permit_open_destinations->first; + } + m_free(ses.authstate.pubkey_options->permit_open_destinations); + } + m_free(ses.authstate.pubkey_options); + } + if (ses.authstate.pubkey_info) { + m_free(ses.authstate.pubkey_info); + } +} + +/* helper for svr_add_pubkey_options. returns DROPBEAR_SUCCESS if the option is matched, + and increments the options_buf */ +static int match_option(buffer *options_buf, const char *opt_name) { + const unsigned int len = strlen(opt_name); + if (options_buf->len - options_buf->pos < len) { + return DROPBEAR_FAILURE; + } + if (strncasecmp((const char *) buf_getptr(options_buf, len), opt_name, len) == 0) { + buf_incrpos(options_buf, len); + return DROPBEAR_SUCCESS; + } + return DROPBEAR_FAILURE; +} + +/* Parse pubkey options and set ses.authstate.pubkey_options accordingly. + * Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */ +int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filename) { + int ret = DROPBEAR_FAILURE; + + TRACE(("enter addpubkeyoptions")) + + ses.authstate.pubkey_options = (struct PubKeyOptions*)m_malloc(sizeof( struct PubKeyOptions )); + + buf_setpos(options_buf, 0); + while (options_buf->pos < options_buf->len) { + if (match_option(options_buf, "no-port-forwarding") == DROPBEAR_SUCCESS) { + dropbear_log(LOG_WARNING, "Port forwarding disabled."); + ses.authstate.pubkey_options->no_port_forwarding_flag = 1; + goto next_option; + } + if (match_option(options_buf, "no-agent-forwarding") == DROPBEAR_SUCCESS) { +#if DROPBEAR_SVR_AGENTFWD + dropbear_log(LOG_WARNING, "Agent forwarding disabled."); + ses.authstate.pubkey_options->no_agent_forwarding_flag = 1; +#endif + goto next_option; + } + if (match_option(options_buf, "no-X11-forwarding") == DROPBEAR_SUCCESS) { +#if DROPBEAR_X11FWD + dropbear_log(LOG_WARNING, "X11 forwarding disabled."); + ses.authstate.pubkey_options->no_x11_forwarding_flag = 1; +#endif + goto next_option; + } + if (match_option(options_buf, "no-pty") == DROPBEAR_SUCCESS) { + dropbear_log(LOG_WARNING, "Pty allocation disabled."); + ses.authstate.pubkey_options->no_pty_flag = 1; + goto next_option; + } + if (match_option(options_buf, "restrict") == DROPBEAR_SUCCESS) { + dropbear_log(LOG_WARNING, "Restrict option set"); + ses.authstate.pubkey_options->no_port_forwarding_flag = 1; +#if DROPBEAR_SVR_AGENTFWD + ses.authstate.pubkey_options->no_agent_forwarding_flag = 1; +#endif +#if DROPBEAR_X11FWD + ses.authstate.pubkey_options->no_x11_forwarding_flag = 1; +#endif + ses.authstate.pubkey_options->no_pty_flag = 1; + goto next_option; + } + if (match_option(options_buf, "command=\"") == DROPBEAR_SUCCESS) { + int escaped = 0; + const unsigned char* command_start = buf_getptr(options_buf, 0); + + if (ses.authstate.pubkey_options->forced_command) { + /* multiple command= options */ + goto bad_option; + } + + while (options_buf->pos < options_buf->len) { + const char c = buf_getbyte(options_buf); + if (!escaped && c == '"') { + const int command_len = buf_getptr(options_buf, 0) - command_start; + ses.authstate.pubkey_options->forced_command = m_malloc(command_len); + memcpy(ses.authstate.pubkey_options->forced_command, + command_start, command_len-1); + ses.authstate.pubkey_options->forced_command[command_len-1] = '\0'; + goto next_option; + } + escaped = (!escaped && c == '\\'); + } + dropbear_log(LOG_WARNING, "Badly formatted command= authorized_keys option"); + goto bad_option; + } + + if (match_option(options_buf, "permitopen=\"") == DROPBEAR_SUCCESS) { + int valid_option = 0; + const unsigned char* permitopen_start = buf_getptr(options_buf, 0); + + if (!ses.authstate.pubkey_options->permit_open_destinations) { + ses.authstate.pubkey_options->permit_open_destinations = list_new(); + } + + while (options_buf->pos < options_buf->len) { + const char c = buf_getbyte(options_buf); + if (c == '"') { + char *spec = NULL; + char *portstring = NULL; + const int permitopen_len = buf_getptr(options_buf, 0) - permitopen_start; + struct PermitTCPFwdEntry *entry = + (struct PermitTCPFwdEntry*)m_malloc(sizeof(struct PermitTCPFwdEntry)); + + list_append(ses.authstate.pubkey_options->permit_open_destinations, entry); + spec = m_malloc(permitopen_len); + memcpy(spec, permitopen_start, permitopen_len - 1); + spec[permitopen_len - 1] = '\0'; + if ((split_address_port(spec, &entry->host, &portstring) == DROPBEAR_SUCCESS) + && entry->host && portstring) { + if (strcmp(portstring, "*") == 0) { + valid_option = 1; + entry->port = PUBKEY_OPTIONS_ANY_PORT; + TRACE(("local port forwarding allowed to host '%s'", entry->host)); + } else if (m_str_to_uint(portstring, &entry->port) == DROPBEAR_SUCCESS) { + valid_option = 1; + TRACE(("local port forwarding allowed to host '%s' and port '%u'", + entry->host, entry->port)); + } + } + + m_free(spec); + m_free(portstring); + break; + } + } + + if (valid_option) { + goto next_option; + } else { + dropbear_log(LOG_WARNING, "Badly formatted permitopen= authorized_keys option"); + goto bad_option; + } + } + + if (match_option(options_buf, "no-touch-required") == DROPBEAR_SUCCESS) { +#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 + dropbear_log(LOG_WARNING, "No user presence check required for U2F/FIDO key."); + ses.authstate.pubkey_options->no_touch_required_flag = 1; +#endif + goto next_option; + } + if (match_option(options_buf, "verify-required") == DROPBEAR_SUCCESS) { +#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 + dropbear_log(LOG_WARNING, "User verification required for U2F/FIDO key."); + ses.authstate.pubkey_options->verify_required_flag = 1; +#endif + goto next_option; + } + +next_option: + /* + * Skip the comma, and move to the next option + * (or break out if there are no more). + */ + if (options_buf->pos < options_buf->len + && buf_getbyte(options_buf) != ',') { + goto bad_option; + } + /* Process the next option. */ + } + /* parsed all options with no problem */ + ret = DROPBEAR_SUCCESS; + goto end; + +bad_option: + ret = DROPBEAR_FAILURE; + svr_pubkey_options_cleanup(); + dropbear_log(LOG_WARNING, "Bad public key options at %s:%d", filename, line_num); + +end: + TRACE(("leave addpubkeyoptions")) + return ret; +} + +#endif diff --git a/src/svr-chansession.c b/src/svr-chansession.c new file mode 100644 index 0000000..656a968 --- /dev/null +++ b/src/svr-chansession.c @@ -0,0 +1,1114 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "packet.h" +#include "buffer.h" +#include "session.h" +#include "dbutil.h" +#include "channel.h" +#include "chansession.h" +#include "sshpty.h" +#include "termcodes.h" +#include "ssh.h" +#include "dbrandom.h" +#include "x11fwd.h" +#include "agentfwd.h" +#include "runopts.h" +#include "auth.h" + +/* Handles sessions (either shells or programs) requested by the client */ + +static int sessioncommand(struct Channel *channel, struct ChanSess *chansess, + int iscmd, int issubsys); +static int sessionpty(struct ChanSess * chansess); +static int sessionsignal(const struct ChanSess *chansess); +static int noptycommand(struct Channel *channel, struct ChanSess *chansess); +static int ptycommand(struct Channel *channel, struct ChanSess *chansess); +static int sessionwinchange(const struct ChanSess *chansess); +static void execchild(const void *user_data_chansess); +static void addchildpid(struct ChanSess *chansess, pid_t pid); +static void sesssigchild_handler(int val); +static void closechansess(const struct Channel *channel); +static void cleanupchansess(const struct Channel *channel); +static int newchansess(struct Channel *channel); +static void chansessionrequest(struct Channel *channel); +static int sesscheckclose(struct Channel *channel); + +static void send_exitsignalstatus(const struct Channel *channel); +static void send_msg_chansess_exitstatus(const struct Channel * channel, + const struct ChanSess * chansess); +static void send_msg_chansess_exitsignal(const struct Channel * channel, + const struct ChanSess * chansess); +static void get_termmodes(const struct ChanSess *chansess); + +const struct ChanType svrchansess = { + "session", /* name */ + newchansess, /* inithandler */ + sesscheckclose, /* checkclosehandler */ + chansessionrequest, /* reqhandler */ + closechansess, /* closehandler */ + cleanupchansess /* cleanup */ +}; + +/* Returns whether the channel is ready to close. The child process + must not be running (has never started, or has exited) */ +static int sesscheckclose(struct Channel *channel) { + struct ChanSess *chansess = (struct ChanSess*)channel->typedata; + TRACE(("sesscheckclose, pid %d, exitpid %d", chansess->pid, chansess->exit.exitpid)) + + if (chansess->exit.exitpid != -1) { + channel->flushing = 1; + } + return chansess->pid == 0 || chansess->exit.exitpid != -1; +} + +/* Handler for childs exiting, store the state for return to the client */ + +/* There's a particular race we have to watch out for: if the forked child + * executes, exits, and this signal-handler is called, all before the parent + * gets to run, then the childpids[] array won't have the pid in it. Hence we + * use the svr_ses.lastexit struct to hold the exit, which is then compared by + * the parent when it runs. This work correctly at least in the case of a + * single shell spawned (ie the usual case) */ +void svr_chansess_checksignal(void) { + int status; + pid_t pid; + + if (!ses.channel_signal_pending) { + return; + } + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + unsigned int i; + struct exitinfo *ex = NULL; + TRACE(("svr_chansess_checksignal : pid %d", pid)) + + ex = NULL; + /* find the corresponding chansess */ + for (i = 0; i < svr_ses.childpidsize; i++) { + if (svr_ses.childpids[i].pid == pid) { + TRACE(("found match session")); + ex = &svr_ses.childpids[i].chansess->exit; + break; + } + } + + /* If the pid wasn't matched, then we might have hit the race mentioned + * above. So we just store the info for the parent to deal with */ + if (ex == NULL) { + TRACE(("using lastexit")); + ex = &svr_ses.lastexit; + } + + ex->exitpid = pid; + if (WIFEXITED(status)) { + ex->exitstatus = WEXITSTATUS(status); + } + if (WIFSIGNALED(status)) { + ex->exitsignal = WTERMSIG(status); +#if !defined(AIX) && defined(WCOREDUMP) + ex->exitcore = WCOREDUMP(status); +#else + ex->exitcore = 0; +#endif + } else { + /* we use this to determine how pid exited */ + ex->exitsignal = -1; + } + } +} + +static void sesssigchild_handler(int UNUSED(dummy)) { + struct sigaction sa_chld; + + const int saved_errno = errno; + + TRACE(("enter sigchld handler")) + + /* Make sure that the main select() loop wakes up */ + while (1) { + /* isserver is just a random byte to write. We can't do anything + about an error so should just ignore it */ + if (write(ses.signal_pipe[1], &ses.isserver, 1) == 1 + || errno != EINTR) { + break; + } + } + + sa_chld.sa_handler = sesssigchild_handler; + sa_chld.sa_flags = SA_NOCLDSTOP; + sigemptyset(&sa_chld.sa_mask); + sigaction(SIGCHLD, &sa_chld, NULL); + TRACE(("leave sigchld handler")) + + errno = saved_errno; +} + +/* send the exit status or the signal causing termination for a session */ +static void send_exitsignalstatus(const struct Channel *channel) { + + struct ChanSess *chansess = (struct ChanSess*)channel->typedata; + + if (chansess->exit.exitpid >= 0) { + if (chansess->exit.exitsignal > 0) { + send_msg_chansess_exitsignal(channel, chansess); + } else { + send_msg_chansess_exitstatus(channel, chansess); + } + } +} + +/* send the exitstatus to the client */ +static void send_msg_chansess_exitstatus(const struct Channel * channel, + const struct ChanSess * chansess) { + + dropbear_assert(chansess->exit.exitpid != -1); + dropbear_assert(chansess->exit.exitsignal == -1); + + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST); + buf_putint(ses.writepayload, channel->remotechan); + buf_putstring(ses.writepayload, "exit-status", 11); + buf_putbyte(ses.writepayload, 0); /* boolean FALSE */ + buf_putint(ses.writepayload, chansess->exit.exitstatus); + + encrypt_packet(); + +} + +/* send the signal causing the exit to the client */ +static void send_msg_chansess_exitsignal(const struct Channel * channel, + const struct ChanSess * chansess) { + + int i; + char* signame = NULL; + dropbear_assert(chansess->exit.exitpid != -1); + dropbear_assert(chansess->exit.exitsignal > 0); + + TRACE(("send_msg_chansess_exitsignal %d", chansess->exit.exitsignal)) + + CHECKCLEARTOWRITE(); + + /* we check that we can match a signal name, otherwise + * don't send anything */ + for (i = 0; signames[i].name != NULL; i++) { + if (signames[i].signal == chansess->exit.exitsignal) { + signame = signames[i].name; + break; + } + } + + if (signame == NULL) { + return; + } + + buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST); + buf_putint(ses.writepayload, channel->remotechan); + buf_putstring(ses.writepayload, "exit-signal", 11); + buf_putbyte(ses.writepayload, 0); /* boolean FALSE */ + buf_putstring(ses.writepayload, signame, strlen(signame)); + buf_putbyte(ses.writepayload, chansess->exit.exitcore); + buf_putstring(ses.writepayload, "", 0); /* error msg */ + buf_putstring(ses.writepayload, "", 0); /* lang */ + + encrypt_packet(); +} + +/* set up a session channel */ +static int newchansess(struct Channel *channel) { + + struct ChanSess *chansess; + + TRACE(("new chansess %p", (void*)channel)) + + dropbear_assert(channel->typedata == NULL); + + chansess = (struct ChanSess*)m_malloc(sizeof(struct ChanSess)); + chansess->cmd = NULL; + chansess->connection_string = NULL; + chansess->client_string = NULL; + chansess->pid = 0; + + /* pty details */ + chansess->master = -1; + chansess->slave = -1; + chansess->tty = NULL; + chansess->term = NULL; + + chansess->exit.exitpid = -1; + + channel->typedata = chansess; + +#if DROPBEAR_X11FWD + chansess->x11listener = NULL; + chansess->x11authprot = NULL; + chansess->x11authcookie = NULL; +#endif + +#if DROPBEAR_SVR_AGENTFWD + chansess->agentlistener = NULL; + chansess->agentfile = NULL; + chansess->agentdir = NULL; +#endif + + /* Will drop to DROPBEAR_PRIO_NORMAL if a non-tty command starts */ + channel->prio = DROPBEAR_PRIO_LOWDELAY; + + return 0; + +} + +static struct logininfo* +chansess_login_alloc(const struct ChanSess *chansess) { + struct logininfo * li; + li = login_alloc_entry(chansess->pid, ses.authstate.username, + svr_ses.remotehost, chansess->tty); + return li; +} + +/* send exit status message before the channel is closed */ +static void closechansess(const struct Channel *channel) { + struct ChanSess *chansess; + + TRACE(("enter closechansess")) + + chansess = (struct ChanSess*)channel->typedata; + + if (chansess == NULL) { + TRACE(("leave closechansess: chansess == NULL")) + return; + } + + send_exitsignalstatus(channel); + TRACE(("leave closechansess")) +} + +/* clean a session channel */ +static void cleanupchansess(const struct Channel *channel) { + + struct ChanSess *chansess; + unsigned int i; + struct logininfo *li; + + TRACE(("enter closechansess")) + + chansess = (struct ChanSess*)channel->typedata; + + if (chansess == NULL) { + TRACE(("leave closechansess: chansess == NULL")) + return; + } + + m_free(chansess->cmd); + m_free(chansess->term); + m_free(chansess->original_command); + + if (chansess->tty) { + /* write the utmp/wtmp login record */ + li = chansess_login_alloc(chansess); + login_logout(li); + login_free_entry(li); + + pty_release(chansess->tty); + m_free(chansess->tty); + } + +#if DROPBEAR_X11FWD + x11cleanup(chansess); +#endif + +#if DROPBEAR_SVR_AGENTFWD + svr_agentcleanup(chansess); +#endif + + /* clear child pid entries */ + for (i = 0; i < svr_ses.childpidsize; i++) { + if (svr_ses.childpids[i].chansess == chansess) { + dropbear_assert(svr_ses.childpids[i].pid > 0); + TRACE(("closing pid %d", svr_ses.childpids[i].pid)) + TRACE(("exitpid is %d", chansess->exit.exitpid)) + svr_ses.childpids[i].pid = -1; + svr_ses.childpids[i].chansess = NULL; + } + } + + m_free(chansess); + + TRACE(("leave closechansess")) +} + +/* Handle requests for a channel. These can be execution requests, + * or x11/authagent forwarding. These are passed to appropriate handlers */ +static void chansessionrequest(struct Channel *channel) { + + char * type = NULL; + unsigned int typelen; + unsigned char wantreply; + int ret = 1; + struct ChanSess *chansess; + + TRACE(("enter chansessionrequest")) + + type = buf_getstring(ses.payload, &typelen); + wantreply = buf_getbool(ses.payload); + + if (typelen > MAX_NAME_LEN) { + TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/ + goto out; + } + + chansess = (struct ChanSess*)channel->typedata; + dropbear_assert(chansess != NULL); + TRACE(("type is %s", type)) + + if (strcmp(type, "window-change") == 0) { + ret = sessionwinchange(chansess); + } else if (strcmp(type, "shell") == 0) { + ret = sessioncommand(channel, chansess, 0, 0); + } else if (strcmp(type, "pty-req") == 0) { + ret = sessionpty(chansess); + } else if (strcmp(type, "exec") == 0) { + ret = sessioncommand(channel, chansess, 1, 0); + } else if (strcmp(type, "subsystem") == 0) { + ret = sessioncommand(channel, chansess, 1, 1); +#if DROPBEAR_X11FWD + } else if (strcmp(type, "x11-req") == 0) { + ret = x11req(chansess); +#endif +#if DROPBEAR_SVR_AGENTFWD + } else if (strcmp(type, "auth-agent-req@openssh.com") == 0) { + ret = svr_agentreq(chansess); +#endif + } else if (strcmp(type, "signal") == 0) { + ret = sessionsignal(chansess); + } else { + /* etc, todo "env", "subsystem" */ + } + +out: + + if (wantreply) { + if (ret == DROPBEAR_SUCCESS) { + send_msg_channel_success(channel); + } else { + send_msg_channel_failure(channel); + } + } + + m_free(type); + TRACE(("leave chansessionrequest")) +} + + +/* Send a signal to a session's process as requested by the client*/ +static int sessionsignal(const struct ChanSess *chansess) { + TRACE(("sessionsignal")) + + int sig = 0; + char* signame = NULL; + int i; + + if (chansess->pid == 0) { + TRACE(("sessionsignal: done no pid")) + /* haven't got a process pid yet */ + return DROPBEAR_FAILURE; + } + + signame = buf_getstring(ses.payload, NULL); + + for (i = 0; signames[i].name != NULL; i++) { + if (strcmp(signames[i].name, signame) == 0) { + sig = signames[i].signal; + break; + } + } + + m_free(signame); + + TRACE(("sessionsignal: pid %d signal %d", (int)chansess->pid, sig)) + if (sig == 0) { + /* failed */ + return DROPBEAR_FAILURE; + } + + if (kill(chansess->pid, sig) < 0) { + TRACE(("sessionsignal: kill() errored")) + return DROPBEAR_FAILURE; + } + + return DROPBEAR_SUCCESS; +} + +/* Let the process know that the window size has changed, as notified from the + * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int sessionwinchange(const struct ChanSess *chansess) { + + int termc, termr, termw, termh; + + if (chansess->master < 0) { + /* haven't got a pty yet */ + return DROPBEAR_FAILURE; + } + + termc = buf_getint(ses.payload); + termr = buf_getint(ses.payload); + termw = buf_getint(ses.payload); + termh = buf_getint(ses.payload); + + pty_change_window_size(chansess->master, termr, termc, termw, termh); + + return DROPBEAR_SUCCESS; +} + +static void get_termmodes(const struct ChanSess *chansess) { + + struct termios termio; + unsigned char opcode; + unsigned int value; + const struct TermCode * termcode; + unsigned int len; + + TRACE(("enter get_termmodes")) + + /* Term modes */ + /* We'll ignore errors and continue if we can't set modes. + * We're ignoring baud rates since they seem evil */ + if (tcgetattr(chansess->master, &termio) == -1) { + return; + } + + len = buf_getint(ses.payload); + TRACE(("term mode str %d p->l %d p->p %d", + len, ses.payload->len , ses.payload->pos)); + if (len != ses.payload->len - ses.payload->pos) { + dropbear_exit("Bad term mode string"); + } + + if (len == 0) { + TRACE(("leave get_termmodes: empty terminal modes string")) + return; + } + + while (((opcode = buf_getbyte(ses.payload)) != 0x00) && opcode <= 159) { + + /* must be before checking type, so that value is consumed even if + * we don't use it */ + value = buf_getint(ses.payload); + + /* handle types of code */ + if (opcode > MAX_TERMCODE) { + continue; + } + termcode = &termcodes[(unsigned int)opcode]; + + + switch (termcode->type) { + + case TERMCODE_NONE: + break; + + case TERMCODE_CONTROLCHAR: + termio.c_cc[termcode->mapcode] = value; + break; + + case TERMCODE_INPUT: + if (value) { + termio.c_iflag |= termcode->mapcode; + } else { + termio.c_iflag &= ~(termcode->mapcode); + } + break; + + case TERMCODE_OUTPUT: + if (value) { + termio.c_oflag |= termcode->mapcode; + } else { + termio.c_oflag &= ~(termcode->mapcode); + } + break; + + case TERMCODE_LOCAL: + if (value) { + termio.c_lflag |= termcode->mapcode; + } else { + termio.c_lflag &= ~(termcode->mapcode); + } + break; + + case TERMCODE_CONTROL: + if (value) { + termio.c_cflag |= termcode->mapcode; + } else { + termio.c_cflag &= ~(termcode->mapcode); + } + break; + + } + } + if (tcsetattr(chansess->master, TCSANOW, &termio) < 0) { + dropbear_log(LOG_INFO, "Error setting terminal attributes"); + } + TRACE(("leave get_termmodes")) +} + +/* Set up a session pty which will be used to execute the shell or program. + * The pty is allocated now, and kept for when the shell/program executes. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int sessionpty(struct ChanSess * chansess) { + + unsigned int termlen; + char namebuf[65]; + struct passwd * pw = NULL; + + TRACE(("enter sessionpty")) + + if (!svr_pubkey_allows_pty()) { + TRACE(("leave sessionpty : pty forbidden by public key option")) + return DROPBEAR_FAILURE; + } + + chansess->term = buf_getstring(ses.payload, &termlen); + if (termlen > MAX_TERM_LEN) { + /* TODO send disconnect ? */ + TRACE(("leave sessionpty: term len too long")) + return DROPBEAR_FAILURE; + } + + /* allocate the pty */ + if (chansess->master != -1) { + dropbear_exit("Multiple pty requests"); + } + if (pty_allocate(&chansess->master, &chansess->slave, namebuf, 64) == 0) { + TRACE(("leave sessionpty: failed to allocate pty")) + return DROPBEAR_FAILURE; + } + + chansess->tty = m_strdup(namebuf); + if (!chansess->tty) { + dropbear_exit("Out of memory"); /* TODO disconnect */ + } + + pw = getpwnam(ses.authstate.pw_name); + if (!pw) + dropbear_exit("getpwnam failed after succeeding previously"); + pty_setowner(pw, chansess->tty); + + /* Set up the rows/col counts */ + sessionwinchange(chansess); + + /* Read the terminal modes */ + get_termmodes(chansess); + + TRACE(("leave sessionpty")) + return DROPBEAR_SUCCESS; +} + +#if !DROPBEAR_VFORK +static void make_connection_string(struct ChanSess *chansess) { + char *local_ip, *local_port, *remote_ip, *remote_port; + size_t len; + get_socket_address(ses.sock_in, &local_ip, &local_port, &remote_ip, &remote_port, 0); + + /* "remoteip remoteport localip localport" */ + len = strlen(local_ip) + strlen(remote_ip) + 20; + chansess->connection_string = m_malloc(len); + snprintf(chansess->connection_string, len, "%s %s %s %s", remote_ip, remote_port, local_ip, local_port); + + /* deprecated but bash only loads .bashrc if SSH_CLIENT is set */ + /* "remoteip remoteport localport" */ + len = strlen(remote_ip) + 20; + chansess->client_string = m_malloc(len); + snprintf(chansess->client_string, len, "%s %s %s", remote_ip, remote_port, local_port); + + m_free(local_ip); + m_free(local_port); + m_free(remote_ip); + m_free(remote_port); +} +#endif + +/* Handle a command request from the client. This is used for both shell + * and command-execution requests, and passes the command to + * noptycommand or ptycommand as appropriate. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int sessioncommand(struct Channel *channel, struct ChanSess *chansess, + int iscmd, int issubsys) { + + unsigned int cmdlen = 0; + int ret; + + TRACE(("enter sessioncommand %d", channel->index)) + + if (chansess->pid != 0) { + /* Note that only one command can _succeed_. The client might try + * one command (which fails), then try another. Ie fallback + * from sftp to scp */ + TRACE(("leave sessioncommand, already have a command")) + return DROPBEAR_FAILURE; + } + + if (iscmd) { + /* "exec" */ + if (chansess->cmd == NULL) { + chansess->cmd = buf_getstring(ses.payload, &cmdlen); + + if (cmdlen > MAX_CMD_LEN) { + m_free(chansess->cmd); + /* TODO - send error - too long ? */ + TRACE(("leave sessioncommand, command too long %d", cmdlen)) + return DROPBEAR_FAILURE; + } + } + if (issubsys) { +#if DROPBEAR_SFTPSERVER + if ((cmdlen == 4) && strncmp(chansess->cmd, "sftp", 4) == 0) { + char *expand_path = expand_homedir_path(SFTPSERVER_PATH); + m_free(chansess->cmd); + chansess->cmd = m_strdup(expand_path); + m_free(expand_path); + } else +#endif + { + m_free(chansess->cmd); + TRACE(("leave sessioncommand, unknown subsystem")) + return DROPBEAR_FAILURE; + } + } + } + + + /* take global command into account */ + if (svr_opts.forced_command) { + if (chansess->cmd) { + chansess->original_command = chansess->cmd; + } else { + chansess->original_command = m_strdup(""); + } + chansess->cmd = m_strdup(svr_opts.forced_command); + } else { + /* take public key option 'command' into account */ + svr_pubkey_set_forced_command(chansess); + } + + +#if LOG_COMMANDS + if (chansess->cmd) { + dropbear_log(LOG_INFO, "User %s executing '%s'", + ses.authstate.pw_name, chansess->cmd); + } else { + dropbear_log(LOG_INFO, "User %s executing login shell", + ses.authstate.pw_name); + } +#endif + + /* uClinux will vfork(), so there'll be a race as + connection_string is freed below. */ +#if !DROPBEAR_VFORK + make_connection_string(chansess); +#endif + + if (chansess->term == NULL) { + /* no pty */ + ret = noptycommand(channel, chansess); + if (ret == DROPBEAR_SUCCESS) { + channel->prio = DROPBEAR_PRIO_NORMAL; + update_channel_prio(); + } + } else { + /* want pty */ + ret = ptycommand(channel, chansess); + } + +#if !DROPBEAR_VFORK + m_free(chansess->connection_string); + m_free(chansess->client_string); +#endif + + if (ret == DROPBEAR_FAILURE) { + m_free(chansess->cmd); + } + TRACE(("leave sessioncommand, ret %d", ret)) + return ret; +} + +/* Execute a command and set up redirection of stdin/stdout/stderr without a + * pty. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int noptycommand(struct Channel *channel, struct ChanSess *chansess) { + int ret; + + TRACE(("enter noptycommand")) + ret = spawn_command(execchild, chansess, + &channel->writefd, &channel->readfd, &channel->errfd, + &chansess->pid); + + if (ret == DROPBEAR_FAILURE) { + return ret; + } + + ses.maxfd = MAX(ses.maxfd, channel->writefd); + ses.maxfd = MAX(ses.maxfd, channel->readfd); + ses.maxfd = MAX(ses.maxfd, channel->errfd); + channel->bidir_fd = 0; + + addchildpid(chansess, chansess->pid); + + if (svr_ses.lastexit.exitpid != -1) { + unsigned int i; + TRACE(("parent side: lastexitpid is %d", svr_ses.lastexit.exitpid)) + /* The child probably exited and the signal handler triggered + * possibly before we got around to adding the childpid. So we fill + * out its data manually */ + for (i = 0; i < svr_ses.childpidsize; i++) { + if (svr_ses.childpids[i].pid == svr_ses.lastexit.exitpid) { + TRACE(("found match for lastexitpid")) + svr_ses.childpids[i].chansess->exit = svr_ses.lastexit; + svr_ses.lastexit.exitpid = -1; + break; + } + } + } + + TRACE(("leave noptycommand")) + return DROPBEAR_SUCCESS; +} + +/* Execute a command or shell within a pty environment, and set up + * redirection as appropriate. + * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +static int ptycommand(struct Channel *channel, struct ChanSess *chansess) { + + pid_t pid; + struct logininfo *li = NULL; +#if DO_MOTD + buffer * motdbuf = NULL; + int len; + struct stat sb; + char *hushpath = NULL; +#endif + + TRACE(("enter ptycommand")) + + /* we need to have a pty allocated */ + if (chansess->master == -1 || chansess->tty == NULL) { + dropbear_log(LOG_WARNING, "No pty was allocated, couldn't execute"); + return DROPBEAR_FAILURE; + } + +#if DROPBEAR_VFORK + pid = vfork(); +#else + pid = fork(); +#endif + if (pid < 0) + return DROPBEAR_FAILURE; + + if (pid == 0) { + /* child */ + + TRACE(("back to normal sigchld")) + /* Revert to normal sigchld handling */ + if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) { + dropbear_exit("signal() error"); + } + + /* redirect stdin/stdout/stderr */ + close(chansess->master); + + pty_make_controlling_tty(&chansess->slave, chansess->tty); + + if ((dup2(chansess->slave, STDIN_FILENO) < 0) || + (dup2(chansess->slave, STDOUT_FILENO) < 0)) { + TRACE(("leave ptycommand: error redirecting filedesc")) + return DROPBEAR_FAILURE; + } + + /* write the utmp/wtmp login record - must be after changing the + * terminal used for stdout with the dup2 above, otherwise + * the wtmp login will not be recorded */ + li = chansess_login_alloc(chansess); + login_login(li); + login_free_entry(li); + + /* Can now dup2 stderr. Messages from login_login() have gone + to the parent stderr */ + if (dup2(chansess->slave, STDERR_FILENO) < 0) { + TRACE(("leave ptycommand: error redirecting filedesc")) + return DROPBEAR_FAILURE; + } + + close(chansess->slave); + +#if DO_MOTD + if (svr_opts.domotd && !chansess->cmd) { + /* don't show the motd if ~/.hushlogin exists */ + + /* 12 == strlen("/.hushlogin\0") */ + len = strlen(ses.authstate.pw_dir) + 12; + + hushpath = m_malloc(len); + snprintf(hushpath, len, "%s/.hushlogin", ses.authstate.pw_dir); + + if (stat(hushpath, &sb) < 0) { + char *expand_path = NULL; + /* more than a screenful is stupid IMHO */ + motdbuf = buf_new(80 * 25); + expand_path = expand_homedir_path(MOTD_FILENAME); + if (buf_readfile(motdbuf, expand_path) == DROPBEAR_SUCCESS) { + buf_setpos(motdbuf, 0); + while (motdbuf->pos != motdbuf->len) { + len = motdbuf->len - motdbuf->pos; + len = write(STDOUT_FILENO, + buf_getptr(motdbuf, len), len); + buf_incrpos(motdbuf, len); + } + } + m_free(expand_path); + buf_free(motdbuf); + + } + m_free(hushpath); + } +#endif /* DO_MOTD */ + + execchild(chansess); + /* not reached */ + + } else { + /* parent */ + TRACE(("continue ptycommand: parent")) + chansess->pid = pid; + + /* add a child pid */ + addchildpid(chansess, pid); + + close(chansess->slave); + channel->writefd = chansess->master; + channel->readfd = chansess->master; + /* don't need to set stderr here */ + ses.maxfd = MAX(ses.maxfd, chansess->master); + channel->bidir_fd = 1; + + setnonblocking(chansess->master); + + } + + TRACE(("leave ptycommand")) + return DROPBEAR_SUCCESS; +} + +/* Add the pid of a child to the list for exit-handling */ +static void addchildpid(struct ChanSess *chansess, pid_t pid) { + + unsigned int i; + for (i = 0; i < svr_ses.childpidsize; i++) { + if (svr_ses.childpids[i].pid == -1) { + break; + } + } + + /* need to increase size */ + if (i == svr_ses.childpidsize) { + svr_ses.childpids = (struct ChildPid*)m_realloc(svr_ses.childpids, + sizeof(struct ChildPid) * (svr_ses.childpidsize+1)); + svr_ses.childpidsize++; + } + + TRACE(("addchildpid %d pid %d for chansess %p", i, pid, chansess)) + svr_ses.childpids[i].pid = pid; + svr_ses.childpids[i].chansess = chansess; + +} + +/* Clean up, drop to user privileges, set up the environment and execute + * the command/shell. This function does not return. */ +static void execchild(const void *user_data) { + const struct ChanSess *chansess = user_data; + char *usershell = NULL; + char *cp = NULL; + char *envcp = getenv("LANG"); + if (envcp != NULL) { + cp = m_strdup(envcp); + } + + /* with uClinux we'll have vfork()ed, so don't want to overwrite the + * hostkey. can't think of a workaround to clear it */ +#if !DROPBEAR_VFORK + /* wipe the hostkey */ + sign_key_free(svr_opts.hostkey); + svr_opts.hostkey = NULL; + + /* overwrite the prng state */ + seedrandom(); +#endif + + /* clear environment if -e was not set */ + /* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD + * etc. This is hazardous, so should only be used for debugging. */ + if ( !svr_opts.pass_on_env) { +#ifndef DEBUG_VALGRIND +#ifdef HAVE_CLEARENV + clearenv(); +#else /* don't HAVE_CLEARENV */ + /* Yay for posix. */ + if (environ) { + environ[0] = NULL; + } +#endif /* HAVE_CLEARENV */ +#endif /* DEBUG_VALGRIND */ + } + +#if DROPBEAR_SVR_MULTIUSER + /* We can only change uid/gid as root ... */ + if (getuid() == 0) { + + if ((setgid(ses.authstate.pw_gid) < 0) || + (initgroups(ses.authstate.pw_name, + ses.authstate.pw_gid) < 0)) { + dropbear_exit("Error changing user group"); + } + if (setuid(ses.authstate.pw_uid) < 0) { + dropbear_exit("Error changing user"); + } + } else { + /* ... but if the daemon is the same uid as the requested uid, we don't + * need to */ + + /* XXX - there is a minor issue here, in that if there are multiple + * usernames with the same uid, but differing groups, then the + * differing groups won't be set (as with initgroups()). The solution + * is for the sysadmin not to give out the UID twice */ + if (getuid() != ses.authstate.pw_uid) { + dropbear_exit("Couldn't change user as non-root"); + } + } +#endif + + /* set env vars */ + addnewvar("USER", ses.authstate.pw_name); + addnewvar("LOGNAME", ses.authstate.pw_name); + addnewvar("HOME", ses.authstate.pw_dir); + addnewvar("SHELL", get_user_shell()); + if (getuid() == 0) { + addnewvar("PATH", DEFAULT_ROOT_PATH); + } else { + addnewvar("PATH", DEFAULT_PATH); + } + if (cp != NULL) { + addnewvar("LANG", cp); + m_free(cp); + } + if (chansess->term != NULL) { + addnewvar("TERM", chansess->term); + } + + if (chansess->tty) { + addnewvar("SSH_TTY", chansess->tty); + } + + if (chansess->connection_string) { + addnewvar("SSH_CONNECTION", chansess->connection_string); + } + + if (chansess->client_string) { + addnewvar("SSH_CLIENT", chansess->client_string); + } + + if (chansess->original_command) { + addnewvar("SSH_ORIGINAL_COMMAND", chansess->original_command); + } +#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT + if (ses.authstate.pubkey_info != NULL) { + addnewvar("SSH_PUBKEYINFO", ses.authstate.pubkey_info); + } +#endif + + /* change directory */ + if (chdir(ses.authstate.pw_dir) < 0) { + int e = errno; + if (chdir("/") < 0) { + dropbear_exit("chdir(\"/\") failed"); + } + fprintf(stderr, "Failed chdir '%s': %s\n", ses.authstate.pw_dir, strerror(e)); + } + + +#if DROPBEAR_X11FWD + /* set up X11 forwarding if enabled */ + x11setauth(chansess); +#endif +#if DROPBEAR_SVR_AGENTFWD + /* set up agent env variable */ + svr_agentset(chansess); +#endif + + usershell = m_strdup(get_user_shell()); + run_shell_command(chansess->cmd, ses.maxfd, usershell); + + /* only reached on error */ + dropbear_exit("Child failed"); +} + +/* Set up the general chansession environment, in particular child-exit + * handling */ +void svr_chansessinitialise() { + + struct sigaction sa_chld; + + /* single child process intially */ + svr_ses.childpids = (struct ChildPid*)m_malloc(sizeof(struct ChildPid)); + svr_ses.childpids[0].pid = -1; /* unused */ + svr_ses.childpids[0].chansess = NULL; + svr_ses.childpidsize = 1; + svr_ses.lastexit.exitpid = -1; /* Nothing has exited yet */ + sa_chld.sa_handler = sesssigchild_handler; + sa_chld.sa_flags = SA_NOCLDSTOP; + sigemptyset(&sa_chld.sa_mask); + if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) { + dropbear_exit("signal() error"); + } + +} + +/* add a new environment variable, allocating space for the entry */ +void addnewvar(const char* param, const char* var) { + + char* newvar = NULL; + int plen, vlen; + + plen = strlen(param); + vlen = strlen(var); + + newvar = m_malloc(plen + vlen + 2); /* 2 is for '=' and '\0' */ + memcpy(newvar, param, plen); + newvar[plen] = '='; + memcpy(&newvar[plen+1], var, vlen); + newvar[plen+vlen+1] = '\0'; + /* newvar is leaked here, but that's part of putenv()'s semantics */ + if (putenv(newvar) < 0) { + dropbear_exit("environ error"); + } +} diff --git a/src/svr-kex.c b/src/svr-kex.c new file mode 100644 index 0000000..7d0f12c --- /dev/null +++ b/src/svr-kex.c @@ -0,0 +1,276 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2004 by Mihnea Stoenescu + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "algo.h" +#include "buffer.h" +#include "session.h" +#include "kex.h" +#include "ssh.h" +#include "packet.h" +#include "bignum.h" +#include "dbrandom.h" +#include "runopts.h" +#include "ecc.h" +#include "gensignkey.h" + +static void send_msg_kexdh_reply(mp_int *dh_e, buffer *ecdh_qs); +#if DROPBEAR_EXT_INFO +static void send_msg_ext_info(void); +#endif + +/* Handle a diffie-hellman key exchange initialisation. This involves + * calculating a session key reply value, and corresponding hash. These + * are carried out by send_msg_kexdh_reply(). recv_msg_kexdh_init() calls + * that function, then brings the new keys into use */ +void recv_msg_kexdh_init() { + DEF_MP_INT(dh_e); + buffer *ecdh_qs = NULL; + + TRACE(("enter recv_msg_kexdh_init")) + if (!ses.kexstate.recvkexinit) { + dropbear_exit("Premature kexdh_init message received"); + } + + switch (ses.newkeys->algo_kex->mode) { +#if DROPBEAR_NORMAL_DH + case DROPBEAR_KEX_NORMAL_DH: + m_mp_init(&dh_e); + if (buf_getmpint(ses.payload, &dh_e) != DROPBEAR_SUCCESS) { + dropbear_exit("Bad kex value"); + } + break; +#endif +#if DROPBEAR_ECDH + case DROPBEAR_KEX_ECDH: +#endif +#if DROPBEAR_CURVE25519 + case DROPBEAR_KEX_CURVE25519: +#endif +#if DROPBEAR_ECDH || DROPBEAR_CURVE25519 + ecdh_qs = buf_getstringbuf(ses.payload); + break; +#endif + } + if (ses.payload->pos != ses.payload->len) { + dropbear_exit("Bad kex value"); + } + + send_msg_kexdh_reply(&dh_e, ecdh_qs); + + mp_clear(&dh_e); + if (ecdh_qs) { + buf_free(ecdh_qs); + ecdh_qs = NULL; + } + + send_msg_newkeys(); + +#if DROPBEAR_EXT_INFO + /* Only send it following the first newkeys */ + if (!ses.kexstate.donesecondkex && ses.allow_ext_info) { + send_msg_ext_info(); + } +#endif + + ses.requirenext = SSH_MSG_NEWKEYS; + TRACE(("leave recv_msg_kexdh_init")) +} + + +#if DROPBEAR_DELAY_HOSTKEY + +static void svr_ensure_hostkey() { + + const char* fn = NULL; + char *expand_fn = NULL; + enum signkey_type type = ses.newkeys->algo_hostkey; + void **hostkey = signkey_key_ptr(svr_opts.hostkey, type); + int ret = DROPBEAR_FAILURE; + + if (hostkey && *hostkey) { + return; + } + + switch (type) + { +#if DROPBEAR_RSA + case DROPBEAR_SIGNKEY_RSA: + fn = RSA_PRIV_FILENAME; + break; +#endif +#if DROPBEAR_DSS + case DROPBEAR_SIGNKEY_DSS: + fn = DSS_PRIV_FILENAME; + break; +#endif +#if DROPBEAR_ECDSA + case DROPBEAR_SIGNKEY_ECDSA_NISTP256: + case DROPBEAR_SIGNKEY_ECDSA_NISTP384: + case DROPBEAR_SIGNKEY_ECDSA_NISTP521: + fn = ECDSA_PRIV_FILENAME; + break; +#endif +#if DROPBEAR_ED25519 + case DROPBEAR_SIGNKEY_ED25519: + fn = ED25519_PRIV_FILENAME; + break; +#endif + default: + dropbear_assert(0); + } + + expand_fn = expand_homedir_path(fn); + + ret = readhostkey(expand_fn, svr_opts.hostkey, &type); + if (ret == DROPBEAR_SUCCESS) { + goto out; + } + + if (signkey_generate(type, 0, expand_fn, 1) == DROPBEAR_FAILURE) { + goto out; + } + + /* Read what we just generated (or another process raced us) */ + ret = readhostkey(expand_fn, svr_opts.hostkey, &type); + + if (ret == DROPBEAR_SUCCESS) { + char *fp = NULL; + unsigned int len; + buffer *key_buf = buf_new(MAX_PUBKEY_SIZE); + buf_put_pub_key(key_buf, svr_opts.hostkey, type); + buf_setpos(key_buf, 4); + len = key_buf->len - key_buf->pos; + fp = sign_key_fingerprint(buf_getptr(key_buf, len), len); + dropbear_log(LOG_INFO, "Generated hostkey %s, fingerprint is %s", + expand_fn, fp); + m_free(fp); + buf_free(key_buf); + } + +out: + if (ret == DROPBEAR_FAILURE) { + dropbear_exit("Couldn't read or generate hostkey %s", expand_fn); + } + m_free(expand_fn); +} +#endif + +/* Generate our side of the diffie-hellman key exchange value (dh_f), and + * calculate the session key using the diffie-hellman algorithm. Following + * that, the session hash is calculated, and signed with RSA or DSS. The + * result is sent to the client. + * + * See the transport RFC4253 section 8 for details + * or RFC5656 section 4 for elliptic curve variant. */ +static void send_msg_kexdh_reply(mp_int *dh_e, buffer *ecdh_qs) { + TRACE(("enter send_msg_kexdh_reply")) + + /* we can start creating the kexdh_reply packet */ + CHECKCLEARTOWRITE(); + +#if DROPBEAR_DELAY_HOSTKEY + if (svr_opts.delay_hostkey) + { + svr_ensure_hostkey(); + } +#endif + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing && fuzz.skip_kexmaths) { + fuzz_fake_send_kexdh_reply(); + return; + } +#endif + + buf_putbyte(ses.writepayload, SSH_MSG_KEXDH_REPLY); + buf_put_pub_key(ses.writepayload, svr_opts.hostkey, + ses.newkeys->algo_hostkey); + + switch (ses.newkeys->algo_kex->mode) { +#if DROPBEAR_NORMAL_DH + case DROPBEAR_KEX_NORMAL_DH: + { + struct kex_dh_param * dh_param = gen_kexdh_param(); + kexdh_comb_key(dh_param, dh_e, svr_opts.hostkey); + + /* put f */ + buf_putmpint(ses.writepayload, &dh_param->pub); + free_kexdh_param(dh_param); + } + break; +#endif +#if DROPBEAR_ECDH + case DROPBEAR_KEX_ECDH: + { + struct kex_ecdh_param *ecdh_param = gen_kexecdh_param(); + kexecdh_comb_key(ecdh_param, ecdh_qs, svr_opts.hostkey); + + buf_put_ecc_raw_pubkey_string(ses.writepayload, &ecdh_param->key); + free_kexecdh_param(ecdh_param); + } + break; +#endif +#if DROPBEAR_CURVE25519 + case DROPBEAR_KEX_CURVE25519: + { + struct kex_curve25519_param *param = gen_kexcurve25519_param(); + kexcurve25519_comb_key(param, ecdh_qs, svr_opts.hostkey); + + buf_putstring(ses.writepayload, param->pub, CURVE25519_LEN); + free_kexcurve25519_param(param); + } + break; +#endif + } + + /* calc the signature */ + buf_put_sign(ses.writepayload, svr_opts.hostkey, + ses.newkeys->algo_signature, ses.hash); + + /* the SSH_MSG_KEXDH_REPLY is done */ + encrypt_packet(); + + TRACE(("leave send_msg_kexdh_reply")) +} + +#if DROPBEAR_EXT_INFO +/* Only used for server-sig-algs on the server side */ +static void send_msg_ext_info(void) { + TRACE(("enter send_msg_ext_info")) + + buf_putbyte(ses.writepayload, SSH_MSG_EXT_INFO); + /* nr-extensions */ + buf_putint(ses.writepayload, 1); + + buf_putstring(ses.writepayload, SSH_SERVER_SIG_ALGS, strlen(SSH_SERVER_SIG_ALGS)); + buf_put_algolist_all(ses.writepayload, sigalgs, 1); + + encrypt_packet(); + + TRACE(("leave send_msg_ext_info")) +} +#endif diff --git a/src/svr-main.c b/src/svr-main.c new file mode 100644 index 0000000..b923e3c --- /dev/null +++ b/src/svr-main.c @@ -0,0 +1,512 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002-2006 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "session.h" +#include "buffer.h" +#include "signkey.h" +#include "runopts.h" +#include "dbrandom.h" +#include "crypto_desc.h" + +static size_t listensockets(int *sock, size_t sockcount, int *maxfd); +static void sigchld_handler(int dummy); +static void sigsegv_handler(int); +static void sigintterm_handler(int fish); +static void main_inetd(void); +static void main_noinetd(int argc, char ** argv, const char* multipath); +static void commonsetup(void); + +#if defined(DBMULTI_dropbear) || !DROPBEAR_MULTI +#if defined(DBMULTI_dropbear) && DROPBEAR_MULTI +int dropbear_main(int argc, char ** argv, const char* multipath) +#else +int main(int argc, char ** argv) +#endif +{ +#if !DROPBEAR_MULTI + const char* multipath = NULL; +#endif + + _dropbear_exit = svr_dropbear_exit; + _dropbear_log = svr_dropbear_log; + + disallow_core(); + + if (argc < 1) { + dropbear_exit("Bad argc"); + } + + /* get commandline options */ + svr_getopts(argc, argv); + +#if INETD_MODE + /* service program mode */ + if (svr_opts.inetdmode) { + main_inetd(); + /* notreached */ + } +#endif + +#if DROPBEAR_DO_REEXEC + if (svr_opts.reexec_childpipe >= 0) { +#ifdef PR_SET_NAME + /* Fix the "Name:" in /proc/pid/status, otherwise it's + a FD number from fexecve. + Failure doesn't really matter, it's mostly aesthetic */ + prctl(PR_SET_NAME, basename(argv[0]), 0, 0); +#endif + main_inetd(); + /* notreached */ + } +#endif + +#if NON_INETD_MODE + main_noinetd(argc, argv, multipath); + /* notreached */ +#endif + + dropbear_exit("Compiled without normal mode, can't run without -i\n"); + return -1; +} +#endif + +#if INETD_MODE || DROPBEAR_DO_REEXEC +static void main_inetd() { + char *host, *port = NULL; + + /* Set up handlers, syslog */ + commonsetup(); + + seedrandom(); + + if (svr_opts.reexec_childpipe < 0) { + /* In case our inetd was lax in logging source addresses */ + get_socket_address(0, NULL, NULL, &host, &port, 0); + dropbear_log(LOG_INFO, "Child connection from %s:%s", host, port); + m_free(host); + m_free(port); + + /* Don't check the return value - it may just fail since inetd has + * already done setsid() after forking (xinetd on Darwin appears to do + * this */ + setsid(); + } + + /* -1 for childpipe in the inetd case is discarded */ + svr_session(0, svr_opts.reexec_childpipe); + + /* notreached */ +} +#endif /* INETD_MODE */ + +#if NON_INETD_MODE +static void main_noinetd(int argc, char ** argv, const char* multipath) { + fd_set fds; + unsigned int i, j; + int val; + int maxsock = -1; + int listensocks[MAX_LISTEN_ADDR]; + size_t listensockcount = 0; + FILE *pidfile = NULL; + int execfd = -1; + + int childpipes[MAX_UNAUTH_CLIENTS]; + char * preauth_addrs[MAX_UNAUTH_CLIENTS]; + + int childsock; + int childpipe[2]; + + (void)argc; + (void)argv; + (void)multipath; + + /* Note: commonsetup() must happen before we daemon()ise. Otherwise + daemon() will chdir("/"), and we won't be able to find local-dir + hostkeys. */ + commonsetup(); + + /* sockets to identify pre-authenticated clients */ + for (i = 0; i < MAX_UNAUTH_CLIENTS; i++) { + childpipes[i] = -1; + } + memset(preauth_addrs, 0x0, sizeof(preauth_addrs)); + + /* Set up the listening sockets */ + listensockcount = listensockets(listensocks, MAX_LISTEN_ADDR, &maxsock); + if (listensockcount == 0) + { + dropbear_exit("No listening ports available."); + } + + for (i = 0; i < listensockcount; i++) { + FD_SET(listensocks[i], &fds); + } + +#if DROPBEAR_DO_REEXEC + if (multipath) { + execfd = open(multipath, O_CLOEXEC|O_RDONLY); + } else { + execfd = open(argv[0], O_CLOEXEC|O_RDONLY); + } + if (execfd < 0) { + /* Just fallback to straight fork */ + TRACE(("Couldn't open own binary %s, disabling re-exec: %s", argv[0], strerror(errno))) + } +#endif + + /* fork */ + if (svr_opts.forkbg) { + int closefds = 0; +#if !DEBUG_TRACE + if (!opts.usingsyslog) { + closefds = 1; + } +#endif + if (daemon(0, closefds) < 0) { + dropbear_exit("Failed to daemonize: %s", strerror(errno)); + } + } + + /* should be done after syslog is working */ + if (svr_opts.forkbg) { + dropbear_log(LOG_INFO, "Running in background"); + } else { + dropbear_log(LOG_INFO, "Not backgrounding"); + } + + /* create a PID file so that we can be killed easily */ + pidfile = fopen(svr_opts.pidfile, "w"); + if (pidfile) { + fprintf(pidfile, "%d\n", getpid()); + fclose(pidfile); + } + + /* incoming connection select loop */ + for(;;) { + + DROPBEAR_FD_ZERO(&fds); + + /* listening sockets */ + for (i = 0; i < listensockcount; i++) { + FD_SET(listensocks[i], &fds); + } + + /* pre-authentication clients */ + for (i = 0; i < MAX_UNAUTH_CLIENTS; i++) { + if (childpipes[i] >= 0) { + FD_SET(childpipes[i], &fds); + maxsock = MAX(maxsock, childpipes[i]); + } + } + + val = select(maxsock+1, &fds, NULL, NULL, NULL); + + if (ses.exitflag) { + unlink(svr_opts.pidfile); + dropbear_exit("Terminated by signal"); + } + + if (val == 0) { + /* timeout reached - shouldn't happen. eh */ + continue; + } + + if (val < 0) { + if (errno == EINTR) { + continue; + } + dropbear_exit("Listening socket error"); + } + + /* close fds which have been authed or closed - svr-auth.c handles + * closing the auth sockets on success */ + for (i = 0; i < MAX_UNAUTH_CLIENTS; i++) { + if (childpipes[i] >= 0 && FD_ISSET(childpipes[i], &fds)) { + m_close(childpipes[i]); + childpipes[i] = -1; + m_free(preauth_addrs[i]); + } + } + + /* handle each socket which has something to say */ + for (i = 0; i < listensockcount; i++) { + size_t num_unauthed_for_addr = 0; + size_t num_unauthed_total = 0; + char *remote_host = NULL, *remote_port = NULL; + pid_t fork_ret = 0; + size_t conn_idx = 0; + struct sockaddr_storage remoteaddr; + socklen_t remoteaddrlen; + + if (!FD_ISSET(listensocks[i], &fds)) + continue; + + remoteaddrlen = sizeof(remoteaddr); + childsock = accept(listensocks[i], + (struct sockaddr*)&remoteaddr, &remoteaddrlen); + + if (childsock < 0) { + /* accept failed */ + continue; + } + + /* Limit the number of unauthenticated connections per IP */ + getaddrstring(&remoteaddr, &remote_host, NULL, 0); + + num_unauthed_for_addr = 0; + num_unauthed_total = 0; + for (j = 0; j < MAX_UNAUTH_CLIENTS; j++) { + if (childpipes[j] >= 0) { + num_unauthed_total++; + if (strcmp(remote_host, preauth_addrs[j]) == 0) { + num_unauthed_for_addr++; + } + } else { + /* a free slot */ + conn_idx = j; + } + } + + if (num_unauthed_total >= MAX_UNAUTH_CLIENTS + || num_unauthed_for_addr >= MAX_UNAUTH_PER_IP) { + goto out; + } + + seedrandom(); + + if (pipe(childpipe) < 0) { + TRACE(("error creating child pipe")) + goto out; + } + +#if DEBUG_NOFORK + fork_ret = 0; +#else + fork_ret = fork(); +#endif + if (fork_ret < 0) { + dropbear_log(LOG_WARNING, "Error forking: %s", strerror(errno)); + goto out; + } + + addrandom((void*)&fork_ret, sizeof(fork_ret)); + + if (fork_ret > 0) { + + /* parent */ + childpipes[conn_idx] = childpipe[0]; + m_close(childpipe[1]); + preauth_addrs[conn_idx] = remote_host; + remote_host = NULL; + + } else { + + /* child */ + getaddrstring(&remoteaddr, NULL, &remote_port, 0); + dropbear_log(LOG_INFO, "Child connection from %s:%s", remote_host, remote_port); + m_free(remote_host); + m_free(remote_port); + +#if !DEBUG_NOFORK + if (setsid() < 0) { + dropbear_exit("setsid: %s", strerror(errno)); + } +#endif + + /* make sure we close sockets */ + for (j = 0; j < listensockcount; j++) { + m_close(listensocks[j]); + } + + m_close(childpipe[0]); + + if (execfd >= 0) { +#if DROPBEAR_DO_REEXEC + /* Add "-2 childpipe[1]" to the args and re-execute ourself. */ + char **new_argv = m_malloc(sizeof(char*) * (argc+4)); + char buf[10]; + int pos0 = 0, new_argc = argc+2; + + /* We need to specially handle "dropbearmulti dropbear". */ + if (multipath) { + new_argv[0] = (char*)multipath; + pos0 = 1; + new_argc++; + } + + memcpy(&new_argv[pos0], argv, sizeof(char*) * argc); + new_argv[new_argc-2] = "-2"; + snprintf(buf, sizeof(buf), "%d", childpipe[1]); + new_argv[new_argc-1] = buf; + new_argv[new_argc] = NULL; + + if ((dup2(childsock, STDIN_FILENO) < 0)) { + dropbear_exit("dup2 failed: %s", strerror(errno)); + } + if (fcntl(childsock, F_SETFD, FD_CLOEXEC) < 0) { + TRACE(("cloexec for childsock %d failed: %s", childsock, strerror(errno))) + } + /* Re-execute ourself */ + fexecve(execfd, new_argv, environ); + /* Not reached on success */ + + /* Fall back on plain fork otherwise. + * To be removed in future once re-exec has been well tested */ + dropbear_log(LOG_WARNING, "fexecve failed, disabling re-exec: %s", strerror(errno)); + m_close(STDIN_FILENO); + m_free(new_argv); +#endif /* DROPBEAR_DO_REEXEC */ + } + + /* start the session */ + svr_session(childsock, childpipe[1]); + /* don't return */ + dropbear_assert(0); + } + +out: + /* This section is important for the parent too */ + m_close(childsock); + if (remote_host) { + m_free(remote_host); + } + } + } /* for(;;) loop */ + + /* don't reach here */ +} +#endif /* NON_INETD_MODE */ + + +/* catch + reap zombie children */ +static void sigchld_handler(int UNUSED(unused)) { + struct sigaction sa_chld; + + const int saved_errno = errno; + + while(waitpid(-1, NULL, WNOHANG) > 0) {} + + sa_chld.sa_handler = sigchld_handler; + sa_chld.sa_flags = SA_NOCLDSTOP; + sigemptyset(&sa_chld.sa_mask); + if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) { + dropbear_exit("signal() error"); + } + errno = saved_errno; +} + +/* catch any segvs */ +static void sigsegv_handler(int UNUSED(unused)) { + int i; + const char *msg = "Aiee, segfault! You should probably report " + "this as a bug to the developer\n"; + i = write(STDERR_FILENO, msg, strlen(msg)); + /* ignore short writes */ + (void)i; + _exit(EXIT_FAILURE); +} + +/* catch ctrl-c or sigterm */ +static void sigintterm_handler(int UNUSED(unused)) { + + ses.exitflag = 1; +} + +/* Things used by inetd and non-inetd modes */ +static void commonsetup() { + + struct sigaction sa_chld; +#ifndef DISABLE_SYSLOG + if (opts.usingsyslog) { + startsyslog(PROGNAME); + } +#endif + + /* set up cleanup handler */ + if (signal(SIGINT, sigintterm_handler) == SIG_ERR || +#ifndef DEBUG_VALGRIND + signal(SIGTERM, sigintterm_handler) == SIG_ERR || +#endif + signal(SIGPIPE, SIG_IGN) == SIG_ERR) { + dropbear_exit("signal() error"); + } + + /* catch and reap zombie children */ + sa_chld.sa_handler = sigchld_handler; + sa_chld.sa_flags = SA_NOCLDSTOP; + sigemptyset(&sa_chld.sa_mask); + if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) { + dropbear_exit("signal() error"); + } + if (signal(SIGSEGV, sigsegv_handler) == SIG_ERR) { + dropbear_exit("signal() error"); + } + + crypto_init(); + + /* Now we can setup the hostkeys - needs to be after logging is on, + * otherwise we might end up blatting error messages to the socket */ + load_all_hostkeys(); +} + +/* Set up listening sockets for all the requested ports */ +static size_t listensockets(int *socks, size_t sockcount, int *maxfd) { + + unsigned int i, n; + char* errstring = NULL; + size_t sockpos = 0; + int nsock; + + TRACE(("listensockets: %d to try", svr_opts.portcount)) + + for (i = 0; i < svr_opts.portcount; i++) { + + TRACE(("listening on '%s:%s'", svr_opts.addresses[i], svr_opts.ports[i])) + + nsock = dropbear_listen(svr_opts.addresses[i], svr_opts.ports[i], &socks[sockpos], + sockcount - sockpos, + &errstring, maxfd); + + if (nsock < 0) { + dropbear_log(LOG_WARNING, "Failed listening on '%s': %s", + svr_opts.ports[i], errstring); + m_free(errstring); + continue; + } + + for (n = 0; n < (unsigned int)nsock; n++) { + int sock = socks[sockpos + n]; + set_sock_priority(sock, DROPBEAR_PRIO_LOWDELAY); +#if DROPBEAR_SERVER_TCP_FAST_OPEN + set_listen_fast_open(sock); +#endif + } + + sockpos += nsock; + + } + return sockpos; +} diff --git a/src/svr-runopts.c b/src/svr-runopts.c new file mode 100644 index 0000000..48d6cbf --- /dev/null +++ b/src/svr-runopts.c @@ -0,0 +1,704 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "runopts.h" +#include "signkey.h" +#include "buffer.h" +#include "dbutil.h" +#include "algo.h" +#include "ecdsa.h" + +#include <grp.h> + +svr_runopts svr_opts; /* GLOBAL */ + +static void printhelp(const char * progname); +static void addportandaddress(const char* spec); +static void loadhostkey(const char *keyfile, int fatal_duplicate); +static void addhostkey(const char *keyfile); + +static void printhelp(const char * progname) { + + fprintf(stderr, "Dropbear server v%s https://matt.ucc.asn.au/dropbear/dropbear.html\n" + "Usage: %s [options]\n" + "-b bannerfile Display the contents of bannerfile" + " before user login\n" + " (default: none)\n" + "-r keyfile Specify hostkeys (repeatable)\n" + " defaults: \n" +#if DROPBEAR_DSS + " - dss %s\n" +#endif +#if DROPBEAR_RSA + " - rsa %s\n" +#endif +#if DROPBEAR_ECDSA + " - ecdsa %s\n" +#endif +#if DROPBEAR_ED25519 + " - ed25519 %s\n" +#endif +#if DROPBEAR_DELAY_HOSTKEY + "-R Create hostkeys as required\n" +#endif + "-F Don't fork into background\n" + "-e Pass on server process environment to child process\n" +#ifdef DISABLE_SYSLOG + "(Syslog support not compiled in, using stderr)\n" +#else + "-E Log to stderr rather than syslog\n" +#endif +#if DO_MOTD + "-m Don't display the motd on login\n" +#endif + "-w Disallow root logins\n" +#ifdef HAVE_GETGROUPLIST + "-G Restrict logins to members of specified group\n" +#endif +#if DROPBEAR_SVR_PASSWORD_AUTH || DROPBEAR_SVR_PAM_AUTH + "-s Disable password logins\n" + "-g Disable password logins for root\n" + "-B Allow blank password logins\n" + "-t Enable two-factor authentication (both password and public key required)\n" +#endif + "-T Maximum authentication tries (default %d)\n" +#if DROPBEAR_SVR_LOCALTCPFWD + "-j Disable local port forwarding\n" +#endif +#if DROPBEAR_SVR_REMOTETCPFWD + "-k Disable remote port forwarding\n" + "-a Allow connections to forwarded ports from any host\n" + "-c command Force executed command\n" +#endif + "-p [address:]port\n" + " Listen on specified tcp port (and optionally address),\n" + " up to %d can be specified\n" + " (default port is %s if none specified)\n" + "-P PidFile Create pid file PidFile\n" + " (default %s)\n" +#if INETD_MODE + "-i Start for inetd\n" +#endif + "-W <receive_window_buffer> (default %d, larger may be faster, max 10MB)\n" + "-K <keepalive> (0 is never, default %d, in seconds)\n" + "-I <idle_timeout> (0 is never, default %d, in seconds)\n" + "-z disable QoS\n" +#if DROPBEAR_PLUGIN + "-A <authplugin>[,<options>]\n" + " Enable external public key auth through <authplugin>\n" +#endif + "-V Version\n" +#if DEBUG_TRACE + "-v verbose (repeat for more verbose)\n" +#endif + ,DROPBEAR_VERSION, progname, +#if DROPBEAR_DSS + DSS_PRIV_FILENAME, +#endif +#if DROPBEAR_RSA + RSA_PRIV_FILENAME, +#endif +#if DROPBEAR_ECDSA + ECDSA_PRIV_FILENAME, +#endif +#if DROPBEAR_ED25519 + ED25519_PRIV_FILENAME, +#endif + MAX_AUTH_TRIES, + DROPBEAR_MAX_PORTS, DROPBEAR_DEFPORT, DROPBEAR_PIDFILE, + DEFAULT_RECV_WINDOW, DEFAULT_KEEPALIVE, DEFAULT_IDLE_TIMEOUT); +} + +void svr_getopts(int argc, char ** argv) { + + unsigned int i, j; + char ** next = NULL; + int nextisport = 0; + char* recv_window_arg = NULL; + char* keepalive_arg = NULL; + char* idle_timeout_arg = NULL; + char* maxauthtries_arg = NULL; + char* reexec_fd_arg = NULL; + char* keyfile = NULL; + char c; +#if DROPBEAR_PLUGIN + char* pubkey_plugin = NULL; +#endif + + + /* see printhelp() for options */ + svr_opts.bannerfile = NULL; + svr_opts.banner = NULL; + svr_opts.forced_command = NULL; + svr_opts.forkbg = 1; + svr_opts.norootlogin = 0; +#ifdef HAVE_GETGROUPLIST + svr_opts.restrict_group = NULL; + svr_opts.restrict_group_gid = 0; +#endif + svr_opts.noauthpass = 0; + svr_opts.norootpass = 0; + svr_opts.allowblankpass = 0; + svr_opts.multiauthmethod = 0; + svr_opts.maxauthtries = MAX_AUTH_TRIES; + svr_opts.inetdmode = 0; + svr_opts.portcount = 0; + svr_opts.hostkey = NULL; + svr_opts.delay_hostkey = 0; + svr_opts.pidfile = expand_homedir_path(DROPBEAR_PIDFILE); +#if DROPBEAR_SVR_LOCALTCPFWD + svr_opts.nolocaltcp = 0; +#endif +#if DROPBEAR_SVR_REMOTETCPFWD + svr_opts.noremotetcp = 0; +#endif +#if DROPBEAR_PLUGIN + svr_opts.pubkey_plugin = NULL; + svr_opts.pubkey_plugin_options = NULL; +#endif + svr_opts.pass_on_env = 0; + svr_opts.reexec_childpipe = -1; + +#ifndef DISABLE_ZLIB + opts.compress_mode = DROPBEAR_COMPRESS_DELAYED; +#endif + + /* not yet + opts.ipv4 = 1; + opts.ipv6 = 1; + */ +#if DO_MOTD + svr_opts.domotd = 1; +#endif +#ifndef DISABLE_SYSLOG + opts.usingsyslog = 1; +#endif + opts.recv_window = DEFAULT_RECV_WINDOW; + opts.keepalive_secs = DEFAULT_KEEPALIVE; + opts.idle_timeout_secs = DEFAULT_IDLE_TIMEOUT; + +#if DROPBEAR_SVR_REMOTETCPFWD + opts.listen_fwd_all = 0; +#endif + opts.disable_ip_tos = 0; + + for (i = 1; i < (unsigned int)argc; i++) { + if (argv[i][0] != '-' || argv[i][1] == '\0') + dropbear_exit("Invalid argument: %s", argv[i]); + + for (j = 1; (c = argv[i][j]) != '\0' && !next && !nextisport; j++) { + switch (c) { + case 'b': + next = &svr_opts.bannerfile; + break; + case 'c': + next = &svr_opts.forced_command; + break; + case 'd': + case 'r': + next = &keyfile; + break; + case 'R': + svr_opts.delay_hostkey = 1; + break; + case 'F': + svr_opts.forkbg = 0; + break; +#ifndef DISABLE_SYSLOG + case 'E': + opts.usingsyslog = 0; + break; +#endif + case 'e': + svr_opts.pass_on_env = 1; + break; + +#if DROPBEAR_SVR_LOCALTCPFWD + case 'j': + svr_opts.nolocaltcp = 1; + break; +#endif +#if DROPBEAR_SVR_REMOTETCPFWD + case 'k': + svr_opts.noremotetcp = 1; + break; + case 'a': + opts.listen_fwd_all = 1; + break; +#endif +#if INETD_MODE + case 'i': + svr_opts.inetdmode = 1; + break; +#endif +#if DROPBEAR_DO_REEXEC && NON_INETD_MODE + /* For internal use by re-exec */ + case '2': + next = &reexec_fd_arg; + break; +#endif + case 'p': + nextisport = 1; + break; + case 'P': + next = &svr_opts.pidfile; + break; +#if DO_MOTD + /* motd is displayed by default, -m turns it off */ + case 'm': + svr_opts.domotd = 0; + break; +#endif + case 'w': + svr_opts.norootlogin = 1; + break; +#ifdef HAVE_GETGROUPLIST + case 'G': + next = &svr_opts.restrict_group; + break; +#endif + case 'W': + next = &recv_window_arg; + break; + case 'K': + next = &keepalive_arg; + break; + case 'I': + next = &idle_timeout_arg; + break; + case 'T': + next = &maxauthtries_arg; + break; +#if DROPBEAR_SVR_PASSWORD_AUTH || DROPBEAR_SVR_PAM_AUTH + case 's': + svr_opts.noauthpass = 1; + break; + case 'g': + svr_opts.norootpass = 1; + break; + case 'B': + svr_opts.allowblankpass = 1; + break; + case 't': + svr_opts.multiauthmethod = 1; + break; +#endif + case 'h': + printhelp(argv[0]); + exit(EXIT_SUCCESS); + break; + case 'u': + /* backwards compatibility with old urandom option */ + break; +#if DROPBEAR_PLUGIN + case 'A': + next = &pubkey_plugin; + break; +#endif +#if DEBUG_TRACE + case 'v': + debug_trace++; + break; +#endif + case 'V': + print_version(); + exit(EXIT_SUCCESS); + break; + case 'z': + opts.disable_ip_tos = 1; + break; + default: + fprintf(stderr, "Invalid option -%c\n", c); + printhelp(argv[0]); + exit(EXIT_FAILURE); + break; + } + } + + if (!next && !nextisport) + continue; + + if (c == '\0') { + i++; + j = 0; + if (!argv[i]) { + dropbear_exit("Missing argument"); + } + } + + if (nextisport) { + addportandaddress(&argv[i][j]); + nextisport = 0; + } else if (next) { + *next = &argv[i][j]; + if (*next == NULL) { + dropbear_exit("Invalid null argument"); + } + next = NULL; + + if (keyfile) { + addhostkey(keyfile); + keyfile = NULL; + } + } + } + + /* Set up listening ports */ + if (svr_opts.portcount == 0) { + svr_opts.ports[0] = m_strdup(DROPBEAR_DEFPORT); + svr_opts.addresses[0] = m_strdup(DROPBEAR_DEFADDRESS); + svr_opts.portcount = 1; + } + + if (svr_opts.bannerfile) { + struct stat buf; + if (stat(svr_opts.bannerfile, &buf) != 0) { + dropbear_exit("Error opening banner file '%s'", + svr_opts.bannerfile); + } + + if (buf.st_size > MAX_BANNER_SIZE) { + dropbear_exit("Banner file too large, max is %d bytes", + MAX_BANNER_SIZE); + } + + svr_opts.banner = buf_new(buf.st_size); + if (buf_readfile(svr_opts.banner, svr_opts.bannerfile)!=DROPBEAR_SUCCESS) { + dropbear_exit("Error reading banner file '%s'", + svr_opts.bannerfile); + } + buf_setpos(svr_opts.banner, 0); + } + +#ifdef HAVE_GETGROUPLIST + if (svr_opts.restrict_group) { + struct group *restrictedgroup = getgrnam(svr_opts.restrict_group); + + if (restrictedgroup){ + svr_opts.restrict_group_gid = restrictedgroup->gr_gid; + } else { + dropbear_exit("Cannot restrict logins to group '%s' as the group does not exist", svr_opts.restrict_group); + } + } +#endif + + if (recv_window_arg) { + parse_recv_window(recv_window_arg); + } + + if (maxauthtries_arg) { + unsigned int val = 0; + if (m_str_to_uint(maxauthtries_arg, &val) == DROPBEAR_FAILURE + || val == 0) { + dropbear_exit("Bad maxauthtries '%s'", maxauthtries_arg); + } + svr_opts.maxauthtries = val; + } + + + if (keepalive_arg) { + unsigned int val; + if (m_str_to_uint(keepalive_arg, &val) == DROPBEAR_FAILURE) { + dropbear_exit("Bad keepalive '%s'", keepalive_arg); + } + opts.keepalive_secs = val; + } + + if (idle_timeout_arg) { + unsigned int val; + if (m_str_to_uint(idle_timeout_arg, &val) == DROPBEAR_FAILURE) { + dropbear_exit("Bad idle_timeout '%s'", idle_timeout_arg); + } + opts.idle_timeout_secs = val; + } + + if (svr_opts.forced_command) { + dropbear_log(LOG_INFO, "Forced command set to '%s'", svr_opts.forced_command); + } + + if (reexec_fd_arg) { + if (m_str_to_uint(reexec_fd_arg, &svr_opts.reexec_childpipe) == DROPBEAR_FAILURE + || svr_opts.reexec_childpipe < 0) { + dropbear_exit("Bad -2"); + } + } + +#if INETD_MODE + if (svr_opts.inetdmode && ( + opts.usingsyslog == 0 +#if DEBUG_TRACE + || debug_trace +#endif + )) { + /* log output goes to stderr which would get sent over the inetd network socket */ + dropbear_exit("Dropbear inetd mode is incompatible with debug -v or non-syslog"); + } +#endif + + if (svr_opts.multiauthmethod && svr_opts.noauthpass) { + dropbear_exit("-t and -s are incompatible"); + } + +#if DROPBEAR_PLUGIN + if (pubkey_plugin) { + svr_opts.pubkey_plugin = m_strdup(pubkey_plugin); + char *args = strchr(svr_opts.pubkey_plugin, ','); + if (args) { + *args='\0'; + ++args; + } + svr_opts.pubkey_plugin_options = args; + } +#endif +} + +static void addportandaddress(const char* spec) { + char *port = NULL, *address = NULL; + + if (svr_opts.portcount >= DROPBEAR_MAX_PORTS) { + return; + } + + if (split_address_port(spec, &address, &port) == DROPBEAR_FAILURE) { + dropbear_exit("Bad -p argument"); + } + + /* A bare port */ + if (!port) { + port = address; + address = NULL; + } + + if (!address) { + /* no address given -> fill in the default address */ + address = m_strdup(DROPBEAR_DEFADDRESS); + } + + if (port[0] == '\0') { + /* empty port -> exit */ + dropbear_exit("Bad port"); + } + svr_opts.ports[svr_opts.portcount] = port; + svr_opts.addresses[svr_opts.portcount] = address; + svr_opts.portcount++; +} + +static void disablekey(enum signature_type type) { + int i; + TRACE(("Disabling key type %d", type)) + for (i = 0; sigalgs[i].name != NULL; i++) { + if ((int)sigalgs[i].val == (int)type) { + sigalgs[i].usable = 0; + break; + } + } +} + +static void loadhostkey_helper(const char *name, void** src, void** dst, int fatal_duplicate) { + if (*dst) { + if (fatal_duplicate) { + dropbear_exit("Only one %s key can be specified", name); + } + } else { + *dst = *src; + *src = NULL; + } + +} + +/* Must be called after syslog/etc is working */ +static void loadhostkey(const char *keyfile, int fatal_duplicate) { + sign_key * read_key = new_sign_key(); + char *expand_path = expand_homedir_path(keyfile); + enum signkey_type type = DROPBEAR_SIGNKEY_ANY; + if (readhostkey(expand_path, read_key, &type) == DROPBEAR_FAILURE) { + if (!svr_opts.delay_hostkey) { + dropbear_log(LOG_WARNING, "Failed loading %s", expand_path); + } + } + m_free(expand_path); + +#if DROPBEAR_RSA + if (type == DROPBEAR_SIGNKEY_RSA) { + loadhostkey_helper("RSA", (void**)&read_key->rsakey, (void**)&svr_opts.hostkey->rsakey, fatal_duplicate); + } +#endif + +#if DROPBEAR_DSS + if (type == DROPBEAR_SIGNKEY_DSS) { + loadhostkey_helper("DSS", (void**)&read_key->dsskey, (void**)&svr_opts.hostkey->dsskey, fatal_duplicate); + } +#endif + +#if DROPBEAR_ECDSA +#if DROPBEAR_ECC_256 + if (type == DROPBEAR_SIGNKEY_ECDSA_NISTP256) { + loadhostkey_helper("ECDSA256", (void**)&read_key->ecckey256, (void**)&svr_opts.hostkey->ecckey256, fatal_duplicate); + } +#endif +#if DROPBEAR_ECC_384 + if (type == DROPBEAR_SIGNKEY_ECDSA_NISTP384) { + loadhostkey_helper("ECDSA384", (void**)&read_key->ecckey384, (void**)&svr_opts.hostkey->ecckey384, fatal_duplicate); + } +#endif +#if DROPBEAR_ECC_521 + if (type == DROPBEAR_SIGNKEY_ECDSA_NISTP521) { + loadhostkey_helper("ECDSA521", (void**)&read_key->ecckey521, (void**)&svr_opts.hostkey->ecckey521, fatal_duplicate); + } +#endif +#endif /* DROPBEAR_ECDSA */ + +#if DROPBEAR_ED25519 + if (type == DROPBEAR_SIGNKEY_ED25519) { + loadhostkey_helper("ed25519", (void**)&read_key->ed25519key, (void**)&svr_opts.hostkey->ed25519key, fatal_duplicate); + } +#endif + + sign_key_free(read_key); + TRACE(("leave loadhostkey")) +} + +static void addhostkey(const char *keyfile) { + if (svr_opts.num_hostkey_files >= MAX_HOSTKEYS) { + dropbear_exit("Too many hostkeys"); + } + svr_opts.hostkey_files[svr_opts.num_hostkey_files] = m_strdup(keyfile); + svr_opts.num_hostkey_files++; +} + + +void load_all_hostkeys() { + int i; + int any_keys = 0; +#if DROPBEAR_ECDSA + int loaded_any_ecdsa = 0; +#endif + + svr_opts.hostkey = new_sign_key(); + + for (i = 0; i < svr_opts.num_hostkey_files; i++) { + char *hostkey_file = svr_opts.hostkey_files[i]; + loadhostkey(hostkey_file, 1); + m_free(hostkey_file); + } + + /* Only load default host keys if a host key is not specified by the user */ + if (svr_opts.num_hostkey_files == 0) { +#if DROPBEAR_RSA + loadhostkey(RSA_PRIV_FILENAME, 0); +#endif + +#if DROPBEAR_DSS + loadhostkey(DSS_PRIV_FILENAME, 0); +#endif + +#if DROPBEAR_ECDSA + loadhostkey(ECDSA_PRIV_FILENAME, 0); +#endif +#if DROPBEAR_ED25519 + loadhostkey(ED25519_PRIV_FILENAME, 0); +#endif + } + +#if DROPBEAR_RSA + if (!svr_opts.delay_hostkey && !svr_opts.hostkey->rsakey) { + disablekey(DROPBEAR_SIGNATURE_RSA_SHA256); + disablekey(DROPBEAR_SIGNATURE_RSA_SHA1); + } else { + any_keys = 1; + } +#endif + +#if DROPBEAR_DSS + if (!svr_opts.delay_hostkey && !svr_opts.hostkey->dsskey) { + disablekey(DROPBEAR_SIGNATURE_DSS); + } else { + any_keys = 1; + } +#endif + +#if DROPBEAR_ECDSA + /* We want to advertise a single ecdsa algorithm size. + - If there is a ecdsa hostkey at startup we choose that that size. + - If we generate at runtime we choose the default ecdsa size. + - Otherwise no ecdsa keys will be advertised */ + + /* check if any keys were loaded at startup */ + loaded_any_ecdsa = + 0 +#if DROPBEAR_ECC_256 + || svr_opts.hostkey->ecckey256 +#endif +#if DROPBEAR_ECC_384 + || svr_opts.hostkey->ecckey384 +#endif +#if DROPBEAR_ECC_521 + || svr_opts.hostkey->ecckey521 +#endif + ; + any_keys |= loaded_any_ecdsa; + + /* Or an ecdsa key could be generated at runtime */ + any_keys |= svr_opts.delay_hostkey; + + /* At most one ecdsa key size will be left enabled */ +#if DROPBEAR_ECC_256 + if (!svr_opts.hostkey->ecckey256 + && (!svr_opts.delay_hostkey || loaded_any_ecdsa || ECDSA_DEFAULT_SIZE != 256 )) { + disablekey(DROPBEAR_SIGNATURE_ECDSA_NISTP256); + } +#endif +#if DROPBEAR_ECC_384 + if (!svr_opts.hostkey->ecckey384 + && (!svr_opts.delay_hostkey || loaded_any_ecdsa || ECDSA_DEFAULT_SIZE != 384 )) { + disablekey(DROPBEAR_SIGNATURE_ECDSA_NISTP384); + } +#endif +#if DROPBEAR_ECC_521 + if (!svr_opts.hostkey->ecckey521 + && (!svr_opts.delay_hostkey || loaded_any_ecdsa || ECDSA_DEFAULT_SIZE != 521 )) { + disablekey(DROPBEAR_SIGNATURE_ECDSA_NISTP521); + } +#endif +#endif /* DROPBEAR_ECDSA */ + +#if DROPBEAR_ED25519 + if (!svr_opts.delay_hostkey && !svr_opts.hostkey->ed25519key) { + disablekey(DROPBEAR_SIGNATURE_ED25519); + } else { + any_keys = 1; + } +#endif +#if DROPBEAR_SK_ECDSA + disablekey(DROPBEAR_SIGNATURE_SK_ECDSA_NISTP256); +#endif +#if DROPBEAR_SK_ED25519 + disablekey(DROPBEAR_SIGNATURE_SK_ED25519); +#endif + + if (!any_keys) { + dropbear_exit("No hostkeys available. 'dropbear -R' may be useful or run dropbearkey."); + } +} diff --git a/src/svr-service.c b/src/svr-service.c new file mode 100644 index 0000000..0aa487c --- /dev/null +++ b/src/svr-service.c @@ -0,0 +1,87 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "dbutil.h" +#include "service.h" +#include "session.h" +#include "packet.h" +#include "ssh.h" +#include "auth.h" + +static void send_msg_service_accept(const char *name, int len); + +/* processes a SSH_MSG_SERVICE_REQUEST, returning 0 if finished, + * 1 if not */ +void recv_msg_service_request() { + + char * name; + unsigned int len; + + TRACE(("enter recv_msg_service_request")) + + name = buf_getstring(ses.payload, &len); + + /* ssh-userauth */ + if (len == SSH_SERVICE_USERAUTH_LEN && + strncmp(SSH_SERVICE_USERAUTH, name, len) == 0) { + + send_msg_service_accept(name, len); + m_free(name); + TRACE(("leave recv_msg_service_request: done ssh-userauth")) + return; + } + + /* ssh-connection */ + if (len == SSH_SERVICE_CONNECTION_LEN && + (strncmp(SSH_SERVICE_CONNECTION, name, len) == 0)) { + if (ses.authstate.authdone != 1) { + dropbear_exit("Request for connection before auth"); + } + + send_msg_service_accept(name, len); + m_free(name); + TRACE(("leave recv_msg_service_request: done ssh-connection")) + return; + } + + m_free(name); + /* TODO this should be a MSG_DISCONNECT */ + dropbear_exit("Unrecognised SSH_MSG_SERVICE_REQUEST"); + + +} + +static void send_msg_service_accept(const char *name, int len) { + + TRACE(("accepting service %s", name)) + + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_SERVICE_ACCEPT); + buf_putstring(ses.writepayload, name, len); + + encrypt_packet(); + +} diff --git a/src/svr-session.c b/src/svr-session.c new file mode 100644 index 0000000..769f073 --- /dev/null +++ b/src/svr-session.c @@ -0,0 +1,375 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "session.h" +#include "dbutil.h" +#include "packet.h" +#include "algo.h" +#include "buffer.h" +#include "dss.h" +#include "ssh.h" +#include "dbrandom.h" +#include "kex.h" +#include "channel.h" +#include "chansession.h" +#include "atomicio.h" +#include "tcpfwd.h" +#include "service.h" +#include "auth.h" +#include "runopts.h" +#include "crypto_desc.h" +#include "fuzz.h" + +static void svr_remoteclosed(void); +static void svr_algos_initialise(void); + +struct serversession svr_ses; /* GLOBAL */ + +static const packettype svr_packettypes[] = { + {SSH_MSG_CHANNEL_DATA, recv_msg_channel_data}, + {SSH_MSG_CHANNEL_WINDOW_ADJUST, recv_msg_channel_window_adjust}, + {SSH_MSG_USERAUTH_REQUEST, recv_msg_userauth_request}, /* server */ + {SSH_MSG_SERVICE_REQUEST, recv_msg_service_request}, /* server */ + {SSH_MSG_KEXINIT, recv_msg_kexinit}, + {SSH_MSG_KEXDH_INIT, recv_msg_kexdh_init}, /* server */ + {SSH_MSG_NEWKEYS, recv_msg_newkeys}, + {SSH_MSG_GLOBAL_REQUEST, recv_msg_global_request_remotetcp}, + {SSH_MSG_CHANNEL_REQUEST, recv_msg_channel_request}, + {SSH_MSG_CHANNEL_OPEN, recv_msg_channel_open}, + {SSH_MSG_CHANNEL_EOF, recv_msg_channel_eof}, + {SSH_MSG_CHANNEL_CLOSE, recv_msg_channel_close}, + {SSH_MSG_CHANNEL_SUCCESS, ignore_recv_response}, + {SSH_MSG_CHANNEL_FAILURE, ignore_recv_response}, + {SSH_MSG_REQUEST_FAILURE, ignore_recv_response}, /* for keepalive */ + {SSH_MSG_REQUEST_SUCCESS, ignore_recv_response}, /* client */ +#if DROPBEAR_LISTENERS + {SSH_MSG_CHANNEL_OPEN_CONFIRMATION, recv_msg_channel_open_confirmation}, + {SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure}, +#endif + {0, NULL} /* End */ +}; + +static const struct ChanType *svr_chantypes[] = { + &svrchansess, +#if DROPBEAR_SVR_LOCALTCPFWD + &svr_chan_tcpdirect, +#endif + NULL /* Null termination is mandatory. */ +}; + +static void +svr_session_cleanup(void) { + /* free potential public key options */ + svr_pubkey_options_cleanup(); + + m_free(svr_ses.addrstring); + m_free(svr_ses.remotehost); + m_free(svr_ses.childpids); + svr_ses.childpidsize = 0; + +#if DROPBEAR_PLUGIN + if (svr_ses.plugin_handle != NULL) { + if (svr_ses.plugin_instance) { + svr_ses.plugin_instance->delete_plugin(svr_ses.plugin_instance); + svr_ses.plugin_instance = NULL; + } + + dlclose(svr_ses.plugin_handle); + svr_ses.plugin_handle = NULL; + } +#endif +} + +void svr_session(int sock, int childpipe) { + char *host, *port; + size_t len; + + common_session_init(sock, sock); + + /* Initialise server specific parts of the session */ + svr_ses.childpipe = childpipe; +#if DROPBEAR_VFORK + svr_ses.server_pid = getpid(); +#endif + + /* for logging the remote address */ + get_socket_address(ses.sock_in, NULL, NULL, &host, &port, 0); + len = strlen(host) + strlen(port) + 2; + svr_ses.addrstring = m_malloc(len); + snprintf(svr_ses.addrstring, len, "%s:%s", host, port); + m_free(host); + m_free(port); + +#if DROPBEAR_PLUGIN + /* Initializes the PLUGIN Plugin */ + svr_ses.plugin_handle = NULL; + svr_ses.plugin_instance = NULL; + if (svr_opts.pubkey_plugin) { +#if DEBUG_TRACE + const int verbose = debug_trace; +#else + const int verbose = 0; +#endif + PubkeyExtPlugin_newFn pluginConstructor; + + /* RTLD_NOW: fails if not all the symbols are resolved now. Better fail now than at run-time */ + svr_ses.plugin_handle = dlopen(svr_opts.pubkey_plugin, RTLD_NOW); + if (svr_ses.plugin_handle == NULL) { + dropbear_exit("failed to load external pubkey plugin '%s': %s", svr_opts.pubkey_plugin, dlerror()); + } + pluginConstructor = (PubkeyExtPlugin_newFn)dlsym(svr_ses.plugin_handle, DROPBEAR_PUBKEY_PLUGIN_FNNAME_NEW); + if (!pluginConstructor) { + dropbear_exit("plugin constructor method not found in external pubkey plugin"); + } + + /* Create an instance of the plugin */ + svr_ses.plugin_instance = pluginConstructor(verbose, svr_opts.pubkey_plugin_options, svr_ses.addrstring); + if (svr_ses.plugin_instance == NULL) { + dropbear_exit("external plugin initialization failed"); + } + /* Check if the plugin is compatible */ + if ( (svr_ses.plugin_instance->api_version[0] != DROPBEAR_PLUGIN_VERSION_MAJOR) || + (svr_ses.plugin_instance->api_version[1] < DROPBEAR_PLUGIN_VERSION_MINOR) ) { + dropbear_exit("plugin version check failed: " + "Dropbear=%d.%d, plugin=%d.%d", + DROPBEAR_PLUGIN_VERSION_MAJOR, DROPBEAR_PLUGIN_VERSION_MINOR, + svr_ses.plugin_instance->api_version[0], svr_ses.plugin_instance->api_version[1]); + } + if (svr_ses.plugin_instance->api_version[1] > DROPBEAR_PLUGIN_VERSION_MINOR) { + dropbear_log(LOG_WARNING, "plugin API newer than dropbear API: " + "Dropbear=%d.%d, plugin=%d.%d", + DROPBEAR_PLUGIN_VERSION_MAJOR, DROPBEAR_PLUGIN_VERSION_MINOR, + svr_ses.plugin_instance->api_version[0], svr_ses.plugin_instance->api_version[1]); + } + dropbear_log(LOG_INFO, "successfully loaded and initialized pubkey plugin '%s'", svr_opts.pubkey_plugin); + } +#endif + + svr_authinitialise(); + chaninitialise(svr_chantypes); + svr_chansessinitialise(); + svr_algos_initialise(); + + get_socket_address(ses.sock_in, NULL, NULL, + &svr_ses.remotehost, NULL, 1); + + /* set up messages etc */ + ses.remoteclosed = svr_remoteclosed; + ses.extra_session_cleanup = svr_session_cleanup; + + /* packet handlers */ + ses.packettypes = svr_packettypes; + + ses.isserver = 1; + + /* We're ready to go now */ + ses.init_done = 1; + + /* exchange identification, version etc */ + send_session_identification(); + + kexfirstinitialise(); /* initialise the kex state */ + + /* start off with key exchange */ + send_msg_kexinit(); + +#if DROPBEAR_FUZZ + if (fuzz.fuzzing) { + fuzz_svr_hook_preloop(); + } +#endif + + /* Run the main for-loop. */ + session_loop(svr_chansess_checksignal); + + /* Not reached */ + +} + +/* cleanup and exit - format must be <= 100 chars */ +void svr_dropbear_exit(int exitcode, const char* format, va_list param) { + char exitmsg[150]; + char fullmsg[300]; + char fromaddr[60]; + int i; + int add_delay = 0; + +#if DROPBEAR_PLUGIN + if ((ses.plugin_session != NULL)) { + svr_ses.plugin_instance->delete_session(ses.plugin_session); + } + ses.plugin_session = NULL; + svr_opts.pubkey_plugin_options = NULL; + m_free(svr_opts.pubkey_plugin); +#endif + + /* Render the formatted exit message */ + vsnprintf(exitmsg, sizeof(exitmsg), format, param); + + /* svr_ses.addrstring may not be set for some early exits, or for + the listener process */ + fromaddr[0] = '\0'; + if (svr_ses.addrstring) { + snprintf(fromaddr, sizeof(fromaddr), " from <%s>", svr_ses.addrstring); + } + + /* Add the prefix depending on session/auth state */ + if (!ses.init_done) { + /* before session init */ + snprintf(fullmsg, sizeof(fullmsg), "Early exit%s: %s", fromaddr, exitmsg); + } else if (ses.authstate.authdone) { + /* user has authenticated */ + snprintf(fullmsg, sizeof(fullmsg), + "Exit (%s)%s: %s", + ses.authstate.pw_name, fromaddr, exitmsg); + } else if (ses.authstate.pw_name) { + /* we have a potential user */ + snprintf(fullmsg, sizeof(fullmsg), + "Exit before auth%s: (user '%s', %u fails): %s", + fromaddr, ses.authstate.pw_name, ses.authstate.failcount, exitmsg); + add_delay = 1; + } else { + /* before userauth */ + snprintf(fullmsg, sizeof(fullmsg), "Exit before auth%s: %s", fromaddr, exitmsg); + add_delay = 1; + } + + dropbear_log(LOG_INFO, "%s", fullmsg); + + /* To make it harder for attackers, introduce a delay to keep an + * unauthenticated session open a bit longer, thus blocking a connection + * slot until after the delay. Without this, while there is a limit on + * the amount of attempts an attacker can make at the same time + * (MAX_UNAUTH_PER_IP), the time taken by dropbear to handle one attempt + * is still short and thus for each of the allowed parallel attempts + * many attempts can be chained one after the other. The attempt rate is + * then: + * "MAX_UNAUTH_PER_IP / <process time of one attempt>". + * With the delay, this rate becomes: + * "MAX_UNAUTH_PER_IP / UNAUTH_CLOSE_DELAY". + */ + if ((add_delay != 0) && (UNAUTH_CLOSE_DELAY > 0)) { + TRACE(("svr_dropbear_exit: start delay of %d seconds", UNAUTH_CLOSE_DELAY)); + sleep(UNAUTH_CLOSE_DELAY); + TRACE(("svr_dropbear_exit: end delay of %d seconds", UNAUTH_CLOSE_DELAY)); + } + +#if DROPBEAR_VFORK + /* For uclinux only the main server process should cleanup - we don't want + * forked children doing that */ + if (svr_ses.server_pid == getpid()) +#endif + { + /* must be after we've done with username etc */ + session_cleanup(); + } + +#if DROPBEAR_FUZZ + /* longjmp before cleaning up svr_opts */ + if (fuzz.do_jmp) { + longjmp(fuzz.jmp, 1); + } +#endif + + if (svr_opts.hostkey) { + sign_key_free(svr_opts.hostkey); + svr_opts.hostkey = NULL; + } + for (i = 0; i < DROPBEAR_MAX_PORTS; i++) { + m_free(svr_opts.addresses[i]); + m_free(svr_opts.ports[i]); + } + + + exit(exitcode); + +} + +/* priority is priority as with syslog() */ +void svr_dropbear_log(int priority, const char* format, va_list param) { + + char printbuf[1024]; + char datestr[20]; + time_t timesec; + int havetrace = 0; + + vsnprintf(printbuf, sizeof(printbuf), format, param); + +#ifndef DISABLE_SYSLOG + if (opts.usingsyslog) { + syslog(priority, "%s", printbuf); + } +#endif + + /* if we are using DEBUG_TRACE, we want to print to stderr even if + * syslog is used, so it is included in error reports */ +#if DEBUG_TRACE + havetrace = debug_trace; +#endif + + if (!opts.usingsyslog || havetrace) { + struct tm * local_tm = NULL; + timesec = time(NULL); + local_tm = localtime(×ec); + if (local_tm == NULL + || strftime(datestr, sizeof(datestr), "%b %d %H:%M:%S", + local_tm) == 0) + { + /* upon failure, just print the epoch-seconds time. */ + snprintf(datestr, sizeof(datestr), "%d", (int)timesec); + } + fprintf(stderr, "[%d] %s %s\n", getpid(), datestr, printbuf); + } +} + +/* called when the remote side closes the connection */ +static void svr_remoteclosed() { + + m_close(ses.sock_in); + if (ses.sock_in != ses.sock_out) { + m_close(ses.sock_out); + } + ses.sock_in = -1; + ses.sock_out = -1; + dropbear_close("Exited normally"); + +} + +static void svr_algos_initialise(void) { + algo_type *algo; + for (algo = sshkex; algo->name; algo++) { +#if DROPBEAR_DH_GROUP1 && DROPBEAR_DH_GROUP1_CLIENTONLY + if (strcmp(algo->name, "diffie-hellman-group1-sha1") == 0) { + algo->usable = 0; + } +#endif +#if DROPBEAR_EXT_INFO + if (strcmp(algo->name, SSH_EXT_INFO_C) == 0) { + algo->usable = 0; + } +#endif + } +} + diff --git a/src/svr-tcpfwd.c b/src/svr-tcpfwd.c new file mode 100644 index 0000000..7967cfa --- /dev/null +++ b/src/svr-tcpfwd.c @@ -0,0 +1,310 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2004 by Mihnea Stoenescu + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "ssh.h" +#include "tcpfwd.h" +#include "dbutil.h" +#include "session.h" +#include "buffer.h" +#include "packet.h" +#include "listener.h" +#include "runopts.h" +#include "auth.h" +#include "netio.h" + +#if !DROPBEAR_SVR_REMOTETCPFWD + +/* This is better than SSH_MSG_UNIMPLEMENTED */ +void recv_msg_global_request_remotetcp() { + unsigned int wantreply = 0; + + TRACE(("recv_msg_global_request_remotetcp: remote tcp forwarding not compiled in")) + + buf_eatstring(ses.payload); + wantreply = buf_getbool(ses.payload); + if (wantreply) { + send_msg_request_failure(); + } +} + +/* */ +#endif /* !DROPBEAR_SVR_REMOTETCPFWD */ + +static int svr_cancelremotetcp(void); +static int svr_remotetcpreq(int *allocated_listen_port); +static int newtcpdirect(struct Channel * channel); + +#if DROPBEAR_SVR_REMOTETCPFWD +static const struct ChanType svr_chan_tcpremote = { + "forwarded-tcpip", + NULL, + NULL, + NULL, + NULL, + NULL +}; + +/* At the moment this is completely used for tcp code (with the name reflecting + * that). If new request types are added, this should be replaced with code + * similar to the request-switching in chansession.c */ +void recv_msg_global_request_remotetcp() { + + char* reqname = NULL; + unsigned int namelen; + unsigned int wantreply = 0; + int ret = DROPBEAR_FAILURE; + + TRACE(("enter recv_msg_global_request_remotetcp")) + + if (svr_opts.noremotetcp || !svr_pubkey_allows_tcpfwd()) { + TRACE(("leave recv_msg_global_request_remotetcp: remote tcp forwarding disabled")) + goto out; + } + + reqname = buf_getstring(ses.payload, &namelen); + wantreply = buf_getbool(ses.payload); + + if (namelen > MAX_NAME_LEN) { + TRACE(("name len is wrong: %d", namelen)) + goto out; + } + + if (strcmp("tcpip-forward", reqname) == 0) { + int allocated_listen_port = 0; + ret = svr_remotetcpreq(&allocated_listen_port); + /* client expects-port-number-to-make-use-of-server-allocated-ports */ + if (DROPBEAR_SUCCESS == ret) { + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_REQUEST_SUCCESS); + buf_putint(ses.writepayload, allocated_listen_port); + encrypt_packet(); + wantreply = 0; /* avoid out: below sending another reply */ + } + } else if (strcmp("cancel-tcpip-forward", reqname) == 0) { + ret = svr_cancelremotetcp(); + } else { + TRACE(("reqname isn't tcpip-forward: '%s'", reqname)) + } + +out: + if (wantreply) { + if (ret == DROPBEAR_SUCCESS) { + send_msg_request_success(); + } else { + send_msg_request_failure(); + } + } + + m_free(reqname); + + TRACE(("leave recv_msg_global_request")) +} + +static int matchtcp(const void* typedata1, const void* typedata2) { + + const struct TCPListener *info1 = (struct TCPListener*)typedata1; + const struct TCPListener *info2 = (struct TCPListener*)typedata2; + + return (info1->listenport == info2->listenport) + && (info1->chantype == info2->chantype) + && (strcmp(info1->listenaddr, info2->listenaddr) == 0); +} + +static int svr_cancelremotetcp() { + + int ret = DROPBEAR_FAILURE; + char * bindaddr = NULL; + unsigned int addrlen; + unsigned int port; + struct Listener * listener = NULL; + struct TCPListener tcpinfo; + + TRACE(("enter cancelremotetcp")) + + bindaddr = buf_getstring(ses.payload, &addrlen); + if (addrlen > MAX_HOST_LEN) { + TRACE(("addr len too long: %d", addrlen)) + goto out; + } + + port = buf_getint(ses.payload); + + tcpinfo.sendaddr = NULL; + tcpinfo.sendport = 0; + tcpinfo.listenaddr = bindaddr; + tcpinfo.listenport = port; + listener = get_listener(CHANNEL_ID_TCPFORWARDED, &tcpinfo, matchtcp); + if (listener) { + remove_listener( listener ); + ret = DROPBEAR_SUCCESS; + } + +out: + m_free(bindaddr); + TRACE(("leave cancelremotetcp")) + return ret; +} + +static int svr_remotetcpreq(int *allocated_listen_port) { + + int ret = DROPBEAR_FAILURE; + char * request_addr = NULL; + unsigned int addrlen; + struct TCPListener *tcpinfo = NULL; + unsigned int port; + struct Listener *listener = NULL; + + TRACE(("enter remotetcpreq")) + + request_addr = buf_getstring(ses.payload, &addrlen); + if (addrlen > MAX_HOST_LEN) { + TRACE(("addr len too long: %d", addrlen)) + goto out; + } + + port = buf_getint(ses.payload); + + if (port != 0) { + if (port < 1 || port > 65535) { + TRACE(("invalid port: %d", port)) + goto out; + } + + if (!ses.allowprivport && port < IPPORT_RESERVED) { + TRACE(("can't assign port < 1024 for non-root")) + goto out; + } + } + + tcpinfo = (struct TCPListener*)m_malloc(sizeof(struct TCPListener)); + tcpinfo->sendaddr = NULL; + tcpinfo->sendport = 0; + tcpinfo->listenport = port; + tcpinfo->chantype = &svr_chan_tcpremote; + tcpinfo->tcp_type = forwarded; + + tcpinfo->request_listenaddr = request_addr; + if (!opts.listen_fwd_all || (strcmp(request_addr, "localhost") == 0) ) { + /* NULL means "localhost only" */ + tcpinfo->listenaddr = NULL; + } + else + { + tcpinfo->listenaddr = m_strdup(request_addr); + } + + ret = listen_tcpfwd(tcpinfo, &listener); + if (DROPBEAR_SUCCESS == ret) { + tcpinfo->listenport = get_sock_port(listener->socks[0]); + *allocated_listen_port = tcpinfo->listenport; + } + +out: + if (ret == DROPBEAR_FAILURE) { + /* we only free it if a listener wasn't created, since the listener + * has to remember it if it's to be cancelled */ + m_free(request_addr); + m_free(tcpinfo); + } + + TRACE(("leave remotetcpreq")) + + return ret; +} + +#endif /* DROPBEAR_SVR_REMOTETCPFWD */ + +#if DROPBEAR_SVR_LOCALTCPFWD + +const struct ChanType svr_chan_tcpdirect = { + "direct-tcpip", + newtcpdirect, /* init */ + NULL, /* checkclose */ + NULL, /* reqhandler */ + NULL, /* closehandler */ + NULL /* cleanup */ +}; + +/* Called upon creating a new direct tcp channel (ie we connect out to an + * address */ +static int newtcpdirect(struct Channel * channel) { + + char* desthost = NULL; + unsigned int destport; + char* orighost = NULL; + unsigned int origport; + char portstring[NI_MAXSERV]; + unsigned int len; + int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED; + + TRACE(("newtcpdirect channel %d", channel->index)) + + if (svr_opts.nolocaltcp || !svr_pubkey_allows_tcpfwd()) { + TRACE(("leave newtcpdirect: local tcp forwarding disabled")) + goto out; + } + + desthost = buf_getstring(ses.payload, &len); + if (len > MAX_HOST_LEN) { + TRACE(("leave newtcpdirect: desthost too long")) + goto out; + } + + destport = buf_getint(ses.payload); + + orighost = buf_getstring(ses.payload, &len); + if (len > MAX_HOST_LEN) { + TRACE(("leave newtcpdirect: orighost too long")) + goto out; + } + + origport = buf_getint(ses.payload); + + /* best be sure */ + if (origport > 65535 || destport > 65535) { + TRACE(("leave newtcpdirect: port > 65535")) + goto out; + } + + if (!svr_pubkey_allows_local_tcpfwd(desthost, destport)) { + TRACE(("leave newtcpdirect: local tcp forwarding not permitted to requested destination")); + goto out; + } + + snprintf(portstring, sizeof(portstring), "%u", destport); + channel->conn_pending = connect_remote(desthost, portstring, channel_connect_done, + channel, NULL, NULL, DROPBEAR_PRIO_NORMAL); + + err = SSH_OPEN_IN_PROGRESS; + +out: + m_free(desthost); + m_free(orighost); + TRACE(("leave newtcpdirect: err %d", err)) + return err; +} + +#endif /* DROPBEAR_SVR_LOCALTCPFWD */ diff --git a/src/svr-x11fwd.c b/src/svr-x11fwd.c new file mode 100644 index 0000000..5d9e6a9 --- /dev/null +++ b/src/svr-x11fwd.c @@ -0,0 +1,269 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" + +#if DROPBEAR_X11FWD +#include "x11fwd.h" +#include "session.h" +#include "ssh.h" +#include "dbutil.h" +#include "chansession.h" +#include "channel.h" +#include "packet.h" +#include "buffer.h" +#include "auth.h" + +#define X11BASEPORT 6000 +#define X11BINDBASE 6010 + +static void x11accept(const struct Listener* listener, int sock); +static int bindport(int fd); +static int send_msg_channel_open_x11(int fd, const struct sockaddr_in* addr); + +/* Check untrusted xauth strings for metacharacters */ +/* Returns DROPBEAR_SUCCESS/DROPBEAR_FAILURE */ +static int +xauth_valid_string(const char *s) +{ + size_t i; + + for (i = 0; s[i] != '\0'; i++) { + if (!isalnum(s[i]) && + s[i] != '.' && s[i] != ':' && s[i] != '/' && + s[i] != '-' && s[i] != '_') { + return DROPBEAR_FAILURE; + } + } + return DROPBEAR_SUCCESS; +} + + +/* called as a request for a session channel, sets up listening X11 */ +/* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ +int x11req(struct ChanSess * chansess) { + + int fd = -1; + + if (!svr_pubkey_allows_x11fwd()) { + return DROPBEAR_FAILURE; + } + + /* we already have an x11 connection */ + if (chansess->x11listener != NULL) { + return DROPBEAR_FAILURE; + } + + chansess->x11singleconn = buf_getbool(ses.payload); + chansess->x11authprot = buf_getstring(ses.payload, NULL); + chansess->x11authcookie = buf_getstring(ses.payload, NULL); + chansess->x11screennum = buf_getint(ses.payload); + + if (xauth_valid_string(chansess->x11authprot) == DROPBEAR_FAILURE || + xauth_valid_string(chansess->x11authcookie) == DROPBEAR_FAILURE) { + dropbear_log(LOG_WARNING, "Bad xauth request"); + goto fail; + } + /* create listening socket */ + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) { + goto fail; + } + + /* allocate port and bind */ + chansess->x11port = bindport(fd); + if (chansess->x11port < 0) { + goto fail; + } + + /* listen */ + if (listen(fd, 20) < 0) { + goto fail; + } + + /* set non-blocking */ + setnonblocking(fd); + + /* listener code will handle the socket now. + * No cleanup handler needed, since listener_remove only happens + * from our cleanup anyway */ + chansess->x11listener = new_listener( &fd, 1, 0, chansess, x11accept, NULL); + if (chansess->x11listener == NULL) { + goto fail; + } + + return DROPBEAR_SUCCESS; + +fail: + /* cleanup */ + m_free(chansess->x11authprot); + m_free(chansess->x11authcookie); + m_close(fd); + + return DROPBEAR_FAILURE; +} + +/* accepts a new X11 socket */ +/* returns DROPBEAR_FAILURE or DROPBEAR_SUCCESS */ +static void x11accept(const struct Listener* listener, int sock) { + + int fd; + struct sockaddr_in addr; + socklen_t len; + int ret; + struct ChanSess * chansess = (struct ChanSess *)(listener->typedata); + + len = sizeof(addr); + + fd = accept(sock, (struct sockaddr*)&addr, &len); + if (fd < 0) { + return; + } + + /* if single-connection we close it up */ + if (chansess->x11singleconn) { + x11cleanup(chansess); + } + + ret = send_msg_channel_open_x11(fd, &addr); + if (ret == DROPBEAR_FAILURE) { + close(fd); + } +} + +/* This is called after switching to the user, and sets up the xauth + * and environment variables. */ +void x11setauth(const struct ChanSess *chansess) { + + char display[20]; /* space for "localhost:12345.123" */ + FILE * authprog = NULL; + int val; + + if (chansess->x11listener == NULL) { + return; + } + + /* create the DISPLAY string */ + val = snprintf(display, sizeof(display), "localhost:%d.%u", + chansess->x11port - X11BASEPORT, chansess->x11screennum); + if (val < 0 || val >= (int)sizeof(display)) { + /* string was truncated */ + return; + } + + addnewvar("DISPLAY", display); + + /* create the xauth string */ + val = snprintf(display, sizeof(display), "unix:%d.%u", + chansess->x11port - X11BASEPORT, chansess->x11screennum); + if (val < 0 || val >= (int)sizeof(display)) { + /* string was truncated */ + return; + } + + /* code is strongly based on OpenSSH's */ + authprog = popen(XAUTH_COMMAND, "w"); + if (authprog) { + fprintf(authprog, "add %s %s %s\n", + display, chansess->x11authprot, chansess->x11authcookie); + pclose(authprog); + } else { + fprintf(stderr, "Failed to run %s\n", XAUTH_COMMAND); + } +} + +void x11cleanup(struct ChanSess *chansess) { + + m_free(chansess->x11authprot); + m_free(chansess->x11authcookie); + + TRACE(("chansess %p", (void*)chansess)) + if (chansess->x11listener != NULL) { + remove_listener(chansess->x11listener); + chansess->x11listener = NULL; + } +} + +static int x11_inithandler(struct Channel *channel) { + channel->prio = DROPBEAR_PRIO_LOWDELAY; + return 0; +} + +static const struct ChanType chan_x11 = { + "x11", + x11_inithandler, /* inithandler */ + NULL, /* checkclose */ + NULL, /* reqhandler */ + NULL, /* closehandler */ + NULL /* cleanup */ +}; + + +static int send_msg_channel_open_x11(int fd, const struct sockaddr_in* addr) { + + char* ipstring = NULL; + + if (send_msg_channel_open_init(fd, &chan_x11) == DROPBEAR_SUCCESS) { + ipstring = inet_ntoa(addr->sin_addr); + buf_putstring(ses.writepayload, ipstring, strlen(ipstring)); + buf_putint(ses.writepayload, addr->sin_port); + + encrypt_packet(); + return DROPBEAR_SUCCESS; + } else { + return DROPBEAR_FAILURE; + } + +} + +/* returns the port bound to, or -1 on failure. + * Will attempt to bind to a port X11BINDBASE (6010 usually) or upwards */ +static int bindport(int fd) { + + struct sockaddr_in addr; + uint16_t port; + + memset((void*)&addr, 0x0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + /* if we can't find one in 2000 ports free, something's wrong */ + for (port = X11BINDBASE; port < X11BINDBASE + 2000; port++) { + addr.sin_port = htons(port); + if (bind(fd, (struct sockaddr*)&addr, + sizeof(struct sockaddr_in)) == 0) { + /* success */ + return port; + } + if (errno == EADDRINUSE) { + /* try the next port */ + continue; + } + /* otherwise it was an error we don't know about */ + dropbear_log(LOG_DEBUG, "Failed to bind x11 socket"); + break; + } + return -1; +} +#endif /* DROPBEAR_X11FWD */ diff --git a/src/sysoptions.h b/src/sysoptions.h new file mode 100644 index 0000000..82249f5 --- /dev/null +++ b/src/sysoptions.h @@ -0,0 +1,398 @@ +/******************************************************************* + * You shouldn't edit this file unless you know you need to. + * This file is only included from options.h + *******************************************************************/ + +#ifndef DROPBEAR_VERSION +#define DROPBEAR_VERSION "2022.83" +#endif + +#define LOCAL_IDENT "SSH-2.0-dropbear_" DROPBEAR_VERSION +#define PROGNAME "dropbear" + +#ifndef DROPBEAR_CLIENT +#define DROPBEAR_CLIENT 0 +#endif + +#ifndef DROPBEAR_SERVER +#define DROPBEAR_SERVER 0 +#endif + +/* Spec recommends after one hour or 1 gigabyte of data. One hour + * is a bit too verbose, so we try 8 hours */ +#ifndef KEX_REKEY_TIMEOUT +#define KEX_REKEY_TIMEOUT (3600 * 8) +#endif +#ifndef KEX_REKEY_DATA +#define KEX_REKEY_DATA (1<<30) /* 2^30 == 1GB, this value must be < INT_MAX */ +#endif +/* Close connections to clients which haven't authorised after AUTH_TIMEOUT */ +#ifndef AUTH_TIMEOUT +#define AUTH_TIMEOUT 300 /* we choose 5 minutes */ +#endif + +#define DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT ((DROPBEAR_SVR_PUBKEY_AUTH) && (DROPBEAR_SVR_PUBKEY_OPTIONS)) + +#if !(NON_INETD_MODE || INETD_MODE) + #error "NON_INETD_MODE or INETD_MODE (or both) must be enabled." +#endif + +/* Would probably work on freebsd but hasn't been tested */ +#if defined(HAVE_FEXECVE) && DROPBEAR_REEXEC && defined(__linux__) +#define DROPBEAR_DO_REEXEC 1 +#else +#define DROPBEAR_DO_REEXEC 0 +#endif + +/* A client should try and send an initial key exchange packet guessing + * the algorithm that will match - saves a round trip connecting, has little + * overhead if the guess was "wrong". */ +#ifndef DROPBEAR_KEX_FIRST_FOLLOWS +#define DROPBEAR_KEX_FIRST_FOLLOWS 1 +#endif +/* Use protocol extension to allow "first follows" to succeed more frequently. + * This is currently Dropbear-specific but will gracefully fallback when connecting + * to other implementations. */ +#ifndef DROPBEAR_KEXGUESS2 +#define DROPBEAR_KEXGUESS2 1 +#endif + +/* Minimum key sizes for DSS and RSA */ +#ifndef MIN_DSS_KEYLEN +#define MIN_DSS_KEYLEN 1024 +#endif +#ifndef MIN_RSA_KEYLEN +#define MIN_RSA_KEYLEN 1024 +#endif + +#define MAX_BANNER_SIZE 2050 /* this is 25*80 chars, any more is foolish */ +#define MAX_BANNER_LINES 20 /* How many lines the client will display */ + +/* the number of NAME=VALUE pairs to malloc for environ, if we don't have + * the clearenv() function */ +#define ENV_SIZE 100 + +#define MAX_CMD_LEN 9000 /* max length of a command */ +#define MAX_TERM_LEN 200 /* max length of TERM name */ + +#define MAX_HOST_LEN 254 /* max hostname len for tcp fwding */ + +#define DROPBEAR_MAX_PORTS 10 /* max number of ports which can be specified, + ipv4 and ipv6 don't count twice */ + +/* Each port might have at least a v4 and a v6 address */ +#define MAX_LISTEN_ADDR (DROPBEAR_MAX_PORTS*3) + +#define _PATH_TTY "/dev/tty" + +#define _PATH_CP "/bin/cp" + +/* Default contents of /etc/shells if system getusershell() doesn't exist. + * Paths taken from getusershell(3) manpage. These can be customised + * on other platforms. One the commandline for CFLAGS it would look like eg + -DCOMPAT_USER_SHELLS='"/bin/sh","/apps/bin/sh","/data/bin/zsh"' + */ +#ifndef COMPAT_USER_SHELLS +#define COMPAT_USER_SHELLS "/bin/sh","/bin/csh" +#endif + +#define DROPBEAR_ESCAPE_CHAR '~' + +/* success/failure defines */ +#define DROPBEAR_SUCCESS 0 +#define DROPBEAR_FAILURE -1 + +#define DROPBEAR_PASSWORD_ENV "DROPBEAR_PASSWORD" + +#define DROPBEAR_NGROUP_MAX 1024 + +/* Required for pubkey auth */ +#define DROPBEAR_SIGNKEY_VERIFY ((DROPBEAR_SVR_PUBKEY_AUTH) || (DROPBEAR_CLIENT)) + +/* crypt(password) must take less time than the auth failure delay + (250ms set in svr-auth.c). On Linux the delay depends on + password length, 100 characters here was empirically derived. + + If a longer password is allowed Dropbear cannot compensate + for the crypt time which will expose which usernames exist */ +#define DROPBEAR_MAX_PASSWORD_LEN 100 + +#define SHA1_HASH_SIZE 20 +#define SHA256_HASH_SIZE 32 +#define MAX_HASH_SIZE 64 /* sha512 */ + +#if DROPBEAR_CHACHA20POLY1305 +#define MAX_KEY_LEN 64 /* 2 x 256 bits for chacha20 */ +#else +#define MAX_KEY_LEN 32 /* 256 bits for aes256 etc */ +#endif +#define MAX_IV_LEN 20 /* must be same as max blocksize, */ + +#if DROPBEAR_SHA2_512_HMAC +#define MAX_MAC_LEN 64 +#elif DROPBEAR_SHA2_256_HMAC +#define MAX_MAC_LEN 32 +#else +#define MAX_MAC_LEN 20 +#endif + +/* sha2-512 is not necessary unless unforseen problems arise with sha2-256 */ +#ifndef DROPBEAR_SHA2_512_HMAC +#define DROPBEAR_SHA2_512_HMAC 0 +#endif + +#define DROPBEAR_ECC ((DROPBEAR_ECDH) || (DROPBEAR_ECDSA)) + +/* Debian doesn't define this in system headers */ +#if !defined(LTM_DESC) && (DROPBEAR_ECC) +#define LTM_DESC +#endif + +#define DROPBEAR_ECC_256 (DROPBEAR_ECC) +#define DROPBEAR_ECC_384 (DROPBEAR_ECC) +#define DROPBEAR_ECC_521 (DROPBEAR_ECC) + +#define DROPBEAR_LTC_PRNG (DROPBEAR_ECC) + +/* RSA can be vulnerable to timing attacks which use the time required for + * signing to guess the private key. Blinding avoids this attack, though makes + * signing operations slightly slower. */ +#define DROPBEAR_RSA_BLINDING 1 + +#ifndef DROPBEAR_RSA_SHA256 +#define DROPBEAR_RSA_SHA256 DROPBEAR_RSA +#endif + +/* Miller-Rabin primality testing is sufficient for RSA but not DSS. + * It's a compile-time setting for libtommath, we can get a speedup + * for key generation if DSS is disabled. + * https://github.com/mkj/dropbear/issues/174#issuecomment-1267374858 + */ +#if !DROPBEAR_DSS +#define LTM_USE_ONLY_MR 1 +#endif + +/* hashes which will be linked and registered */ +#define DROPBEAR_SHA1 (DROPBEAR_RSA_SHA1 || DROPBEAR_DSS \ + || DROPBEAR_SHA1_HMAC || DROPBEAR_SHA1_96_HMAC \ + || DROPBEAR_DH_GROUP1 || DROPBEAR_DH_GROUP14_SHA1 ) +/* sha256 is always used for fingerprints and dbrandom */ +#define DROPBEAR_SHA256 1 +#define DROPBEAR_SHA384 (DROPBEAR_ECC_384) +/* LTC SHA384 depends on SHA512 */ +#define DROPBEAR_SHA512 ((DROPBEAR_SHA2_512_HMAC) || (DROPBEAR_ECC_521) \ + || (DROPBEAR_SHA384) || (DROPBEAR_DH_GROUP16) \ + || (DROPBEAR_ED25519)) + +#define DROPBEAR_DH_GROUP14 ((DROPBEAR_DH_GROUP14_SHA256) || (DROPBEAR_DH_GROUP14_SHA1)) + +#define DROPBEAR_NORMAL_DH ((DROPBEAR_DH_GROUP1) || (DROPBEAR_DH_GROUP14) || (DROPBEAR_DH_GROUP16)) + +#ifndef DROPBEAR_SK_ECDSA +#define DROPBEAR_SK_ECDSA DROPBEAR_SK_KEYS +#endif +#ifndef DROPBEAR_SK_ED25519 +#define DROPBEAR_SK_ED25519 DROPBEAR_SK_KEYS +#endif + +/* Dropbear only uses server-sig-algs, only needed if we have rsa-sha256 pubkey auth */ +#define DROPBEAR_EXT_INFO ((DROPBEAR_RSA_SHA256) \ + && ((DROPBEAR_CLI_PUBKEY_AUTH) || (DROPBEAR_SVR_PUBKEY_AUTH))) + +/* roughly 2x 521 bits */ +#define MAX_ECC_SIZE 140 + +#define MAX_NAME_LEN 64 /* maximum length of a protocol name, isn't + explicitly specified for all protocols (just + for algos) but seems valid */ + +#define MAX_PROPOSED_ALGO 50 + +/* size/count limits */ +/* From transport rfc */ +#define MIN_PACKET_LEN 16 + +#define RECV_MAX_PACKET_LEN (MAX(35000, ((RECV_MAX_PAYLOAD_LEN)+100))) + +/* for channel code */ +#define TRANS_MAX_WINDOW 500000000 /* 500MB is sufficient, stopping overflow */ +#define TRANS_MAX_WIN_INCR 500000000 /* overflow prevention */ + +#define RECV_WINDOWEXTEND (opts.recv_window / 3) /* We send a "window extend" every + RECV_WINDOWEXTEND bytes */ +#define MAX_RECV_WINDOW (10*1024*1024) /* 10 MB should be enough */ + +#define MAX_CHANNELS 1000 /* simple mem restriction, includes each tcp/x11 + connection, so can't be _too_ small */ + +#define MAX_STRING_LEN (MAX(MAX_CMD_LEN, 2400)) /* Sun SSH needs 2400 for algos, + MAX_CMD_LEN is usually longer */ + +/* For a 4096 bit DSS key, empirically determined */ +#define MAX_PUBKEY_SIZE 1700 +/* For a 4096 bit DSS key, empirically determined */ +#define MAX_PRIVKEY_SIZE 1700 + +#define MAX_HOSTKEYS 4 + +/* The maximum size of the bignum portion of the kexhash buffer */ +/* Sect. 8 of the transport rfc 4253, K_S + e + f + K */ +#define KEXHASHBUF_MAX_INTS (1700 + 130 + 130 + 130) + +#define DROPBEAR_MAX_SOCKS 2 /* IPv4, IPv6 are all we'll get for now. Revisit + in a few years time.... */ + +#define DROPBEAR_MAX_CLI_PASS 1024 + +#define DROPBEAR_MAX_CLI_INTERACT_PROMPTS 80 /* The number of prompts we'll + accept for keyb-interactive + auth */ + + +#define DROPBEAR_AES ((DROPBEAR_AES256) || (DROPBEAR_AES128)) + +#define DROPBEAR_AEAD_MODE ((DROPBEAR_CHACHA20POLY1305) || (DROPBEAR_ENABLE_GCM_MODE)) + +#define DROPBEAR_CLI_ANYTCPFWD ((DROPBEAR_CLI_REMOTETCPFWD) || (DROPBEAR_CLI_LOCALTCPFWD)) + +#define DROPBEAR_TCP_ACCEPT ((DROPBEAR_CLI_LOCALTCPFWD) || (DROPBEAR_SVR_REMOTETCPFWD)) + +#define DROPBEAR_LISTENERS \ + ((DROPBEAR_CLI_REMOTETCPFWD) || (DROPBEAR_CLI_LOCALTCPFWD) || \ + (DROPBEAR_SVR_REMOTETCPFWD) || (DROPBEAR_SVR_LOCALTCPFWD) || \ + (DROPBEAR_SVR_AGENTFWD) || (DROPBEAR_X11FWD)) + +#define DROPBEAR_CLI_MULTIHOP ((DROPBEAR_CLI_NETCAT) && (DROPBEAR_CLI_PROXYCMD)) + +#define ENABLE_CONNECT_UNIX ((DROPBEAR_CLI_AGENTFWD) || (DROPBEAR_USE_PRNGD)) + +/* if we're using authorized_keys or known_hosts */ +#define DROPBEAR_KEY_LINES ((DROPBEAR_CLIENT) || (DROPBEAR_SVR_PUBKEY_AUTH)) + +/* Changing this is inadvisable, it appears to have problems + * with flushing compressed data */ +#define DROPBEAR_ZLIB_MEM_LEVEL 8 + +#if (DROPBEAR_SVR_PASSWORD_AUTH) && (DROPBEAR_SVR_PAM_AUTH) +#error "You can't turn on PASSWORD and PAM auth both at once. Fix it in localoptions.h" +#endif + +/* PAM requires ./configure --enable-pam */ +#if !defined(HAVE_LIBPAM) && DROPBEAR_SVR_PAM_AUTH +#error "DROPBEAR_SVR_PATM_AUTH requires PAM headers. Perhaps ./configure --enable-pam ?" +#endif + +#if DROPBEAR_SVR_PASSWORD_AUTH && !HAVE_CRYPT + #error "DROPBEAR_SVR_PASSWORD_AUTH requires `crypt()'." +#endif + +#if !(DROPBEAR_SVR_PASSWORD_AUTH || DROPBEAR_SVR_PAM_AUTH || DROPBEAR_SVR_PUBKEY_AUTH) + #error "At least one server authentication type must be enabled. DROPBEAR_SVR_PUBKEY_AUTH and DROPBEAR_SVR_PASSWORD_AUTH are recommended." +#endif + +#if (DROPBEAR_PLUGIN && !DROPBEAR_SVR_PUBKEY_AUTH) + #error "You must define DROPBEAR_SVR_PUBKEY_AUTH in order to use plugins" +#endif + +#if !(DROPBEAR_AES128 || DROPBEAR_3DES || DROPBEAR_AES256 || DROPBEAR_CHACHA20POLY1305) + #error "At least one encryption algorithm must be enabled. AES128 is recommended." +#endif + +#if !(DROPBEAR_RSA || DROPBEAR_DSS || DROPBEAR_ECDSA || DROPBEAR_ED25519) + #error "At least one hostkey or public-key algorithm must be enabled; RSA is recommended." +#endif + +/* Source for randomness. This must be able to provide hundreds of bytes per SSH + * connection without blocking. */ +#ifndef DROPBEAR_URANDOM_DEV +#define DROPBEAR_URANDOM_DEV "/dev/urandom" +#endif + +/* client keyboard interactive authentication is often used for password auth. + rfc4256 */ +#define DROPBEAR_CLI_INTERACT_AUTH (DROPBEAR_CLI_PASSWORD_AUTH) + +/* We use dropbear_client and dropbear_server as shortcuts to avoid redundant + * code, if we're just compiling as client or server */ +#if (DROPBEAR_SERVER) && (DROPBEAR_CLIENT) + +#define IS_DROPBEAR_SERVER (ses.isserver == 1) +#define IS_DROPBEAR_CLIENT (ses.isserver == 0) + +#elif DROPBEAR_SERVER + +#define IS_DROPBEAR_SERVER 1 +#define IS_DROPBEAR_CLIENT 0 + +#elif DROPBEAR_CLIENT + +#define IS_DROPBEAR_SERVER 0 +#define IS_DROPBEAR_CLIENT 1 + +#else +/* Just building key utils? */ +#define IS_DROPBEAR_SERVER 0 +#define IS_DROPBEAR_CLIENT 0 + +#endif /* neither DROPBEAR_SERVER nor DROPBEAR_CLIENT */ + +#ifdef HAVE_FORK +#define DROPBEAR_VFORK 0 +#else +#define DROPBEAR_VFORK 1 +#endif + +#ifndef DROPBEAR_LISTEN_BACKLOG +#if MAX_UNAUTH_CLIENTS > MAX_CHANNELS +#define DROPBEAR_LISTEN_BACKLOG MAX_UNAUTH_CLIENTS +#else +#define DROPBEAR_LISTEN_BACKLOG MAX_CHANNELS +#endif +#endif + +/* free memory before exiting */ +#define DROPBEAR_CLEANUP 1 + +/* Use this string since some implementations might special-case it */ +#define DROPBEAR_KEEPALIVE_STRING "keepalive@openssh.com" + +/* Linux will attempt TCP fast open, falling back if not supported by the kernel. + * Currently server is enabled but client is disabled by default until there + * is further compatibility testing */ +#ifdef __linux__ +#define DROPBEAR_SERVER_TCP_FAST_OPEN 1 +#define DROPBEAR_CLIENT_TCP_FAST_OPEN 0 +#else +#define DROPBEAR_SERVER_TCP_FAST_OPEN 0 +#define DROPBEAR_CLIENT_TCP_FAST_OPEN 0 +#endif + +#define DROPBEAR_TRACKING_MALLOC (DROPBEAR_FUZZ) + +/* Used to work around Memory Sanitizer false positives */ +#if defined(__has_feature) +# if __has_feature(memory_sanitizer) +# define DROPBEAR_MSAN 1 +# endif +#endif +#ifndef DROPBEAR_MSAN +#define DROPBEAR_MSAN 0 +#endif + +#ifndef DEBUG_DSS_VERIFY +#define DEBUG_DSS_VERIFY 0 +#endif + +#ifndef DROPBEAR_MULTI +#define DROPBEAR_MULTI 0 +#endif + +/* Fuzzing expects all key types to be enabled */ +#if DROPBEAR_FUZZ +#if defined(DROPBEAR_DSS) +#undef DROPBEAR_DSS +#endif +#define DROPBEAR_DSS 1 +#endif + +/* no include guard for this file */ diff --git a/src/tcp-accept.c b/src/tcp-accept.c new file mode 100644 index 0000000..73cfa54 --- /dev/null +++ b/src/tcp-accept.c @@ -0,0 +1,146 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "ssh.h" +#include "tcpfwd.h" +#include "dbutil.h" +#include "session.h" +#include "buffer.h" +#include "packet.h" +#include "listener.h" +#include "listener.h" +#include "runopts.h" + +#if DROPBEAR_TCP_ACCEPT + +static void cleanup_tcp(const struct Listener *listener) { + + struct TCPListener *tcpinfo = (struct TCPListener*)(listener->typedata); + + m_free(tcpinfo->sendaddr); + m_free(tcpinfo->listenaddr); + m_free(tcpinfo->request_listenaddr); + m_free(tcpinfo); +} + +static void tcp_acceptor(const struct Listener *listener, int sock) { + + int fd; + struct sockaddr_storage sa; + socklen_t len; + char ipstring[NI_MAXHOST], portstring[NI_MAXSERV]; + struct TCPListener *tcpinfo = (struct TCPListener*)(listener->typedata); + + len = sizeof(sa); + + fd = accept(sock, (struct sockaddr*)&sa, &len); + if (fd < 0) { + return; + } + + if (getnameinfo((struct sockaddr*)&sa, len, ipstring, sizeof(ipstring), + portstring, sizeof(portstring), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) { + m_close(fd); + return; + } + + if (send_msg_channel_open_init(fd, tcpinfo->chantype) == DROPBEAR_SUCCESS) { + char* addr = NULL; + unsigned int port = 0; + + if (tcpinfo->tcp_type == direct) { + /* "direct-tcpip" */ + /* host to connect, port to connect */ + addr = tcpinfo->sendaddr; + port = tcpinfo->sendport; + } else { + dropbear_assert(tcpinfo->tcp_type == forwarded); + /* "forwarded-tcpip" */ + /* address that was connected, port that was connected */ + addr = tcpinfo->request_listenaddr; + port = tcpinfo->listenport; + } + + if (addr == NULL) { + addr = "localhost"; + } + buf_putstring(ses.writepayload, addr, strlen(addr)); + buf_putint(ses.writepayload, port); + + /* originator ip */ + buf_putstring(ses.writepayload, ipstring, strlen(ipstring)); + /* originator port */ + buf_putint(ses.writepayload, atol(portstring)); + + encrypt_packet(); + + } else { + /* XXX debug? */ + close(fd); + } +} + +int listen_tcpfwd(struct TCPListener* tcpinfo, struct Listener **ret_listener) { + + char portstring[NI_MAXSERV]; + int socks[DROPBEAR_MAX_SOCKS]; + int nsocks; + struct Listener *listener; + char* errstring = NULL; + + TRACE(("enter listen_tcpfwd")) + + /* first we try to bind, so don't need to do so much cleanup on failure */ + snprintf(portstring, sizeof(portstring), "%u", tcpinfo->listenport); + + nsocks = dropbear_listen(tcpinfo->listenaddr, portstring, socks, + DROPBEAR_MAX_SOCKS, &errstring, &ses.maxfd); + if (nsocks < 0) { + dropbear_log(LOG_INFO, "TCP forward failed: %s", errstring); + m_free(errstring); + TRACE(("leave listen_tcpfwd: dropbear_listen failed")) + return DROPBEAR_FAILURE; + } + m_free(errstring); + + /* new_listener will close the socks if it fails */ + listener = new_listener(socks, nsocks, CHANNEL_ID_TCPFORWARDED, tcpinfo, + tcp_acceptor, cleanup_tcp); + + if (listener == NULL) { + TRACE(("leave listen_tcpfwd: listener failed")) + return DROPBEAR_FAILURE; + } + + if (ret_listener) { + *ret_listener = listener; + } + + TRACE(("leave listen_tcpfwd: success")) + return DROPBEAR_SUCCESS; +} + +#endif /* DROPBEAR_TCP_ACCEPT */ diff --git a/src/tcpfwd.h b/src/tcpfwd.h new file mode 100644 index 0000000..69e5af2 --- /dev/null +++ b/src/tcpfwd.h @@ -0,0 +1,78 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ +#ifndef DROPBEAR_TCPFWD_H +#define DROPBEAR_TCPFWD_H + +#include "channel.h" +#include "list.h" +#include "listener.h" + +struct TCPListener { + + /* For a direct-tcpip request, it's the addr/port we want the other + * end to connect to */ + char *sendaddr; + unsigned int sendport; + + /* This is the address/port that we listen on. The address has special + * meanings as per the rfc, "" for all interfaces, "localhost" for + * localhost, or a normal interface name. */ + char *listenaddr; + unsigned int listenport; + /* The address that the remote host asked to listen on */ + char *request_listenaddr; + + const struct ChanType *chantype; + enum {direct, forwarded} tcp_type; +}; + +/* A forwarding entry */ +struct TCPFwdEntry { + const char *connectaddr; + unsigned int connectport; + const char *listenaddr; + unsigned int listenport; + unsigned int have_reply; /* is set to 1 after a reply has been received + when setting up the forwarding */ +}; + +/* Server */ +void recv_msg_global_request_remotetcp(void); + +extern const struct ChanType svr_chan_tcpdirect; + +/* Client */ +void setup_localtcp(void); +void setup_remotetcp(void); +extern const struct ChanType cli_chan_tcpremote; +void cli_recv_msg_request_success(void); +void cli_recv_msg_request_failure(void); + +/* Common */ +int listen_tcpfwd(struct TCPListener* tcpinfo, struct Listener **ret_listener); + +/* A random identifier */ +#define CHANNEL_ID_TCPFORWARDED 0x43612c67 + +#endif diff --git a/src/termcodes.c b/src/termcodes.c new file mode 100644 index 0000000..c5819c1 --- /dev/null +++ b/src/termcodes.c @@ -0,0 +1,213 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "termcodes.h" + +const struct TermCode termcodes[MAX_TERMCODE+1] = { + + {0, 0}, /* TTY_OP_END */ + {VINTR, TERMCODE_CONTROLCHAR}, /* control character codes */ + {VQUIT, TERMCODE_CONTROLCHAR}, + {VERASE, TERMCODE_CONTROLCHAR}, + {VKILL, TERMCODE_CONTROLCHAR}, + {VEOF, TERMCODE_CONTROLCHAR}, + {VEOL, TERMCODE_CONTROLCHAR}, +#ifdef VEOL2 + {VEOL2, TERMCODE_CONTROLCHAR}, +#else + {0, 0}, +#endif + {VSTART, TERMCODE_CONTROLCHAR}, + {VSTOP, TERMCODE_CONTROLCHAR}, + {VSUSP, TERMCODE_CONTROLCHAR}, +#ifdef VDSUSP + {VDSUSP, TERMCODE_CONTROLCHAR}, +#else + {0, 0}, +#endif +#ifdef VREPRINT + {VREPRINT, TERMCODE_CONTROLCHAR}, +#else + {0, 0}, +#endif +#ifdef AIX + {CERASE, TERMCODE_CONTROLCHAR}, +#else +#ifdef VWERASE + {VWERASE, TERMCODE_CONTROLCHAR}, +#else + {0, 0}, +#endif +#endif +#ifdef VLNEXT + {VLNEXT, TERMCODE_CONTROLCHAR}, +#else + {0, 0}, +#endif +#ifdef VFLUSH + {VFLUSH, TERMCODE_CONTROLCHAR}, +#else + {0, 0}, +#endif +#ifdef VSWTCH + {VSWTCH, TERMCODE_CONTROLCHAR}, +#else + {0, 0}, +#endif +#ifdef VSTATUS + {VSTATUS, TERMCODE_CONTROLCHAR}, +#else + {0, 0}, +#endif +#ifdef AIX + {CKILL, TERMCODE_CONTROLCHAR}, +#elif defined(VDISCARD) + {VDISCARD, TERMCODE_CONTROLCHAR}, +#else + {0, 0}, +#endif + {0, 0}, /* 19 */ + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, /* 29 */ + {IGNPAR, TERMCODE_INPUT}, /* input flags */ + {PARMRK, TERMCODE_INPUT}, + {INPCK, TERMCODE_INPUT}, + {ISTRIP, TERMCODE_INPUT}, + {INLCR, TERMCODE_INPUT}, + {IGNCR, TERMCODE_INPUT}, + {ICRNL, TERMCODE_INPUT}, +#ifdef IUCLC + {IUCLC, TERMCODE_INPUT}, +#else + {0, 0}, +#endif + {IXON, TERMCODE_INPUT}, + {IXANY, TERMCODE_INPUT}, + {IXOFF, TERMCODE_INPUT}, +#ifdef IMAXBEL + {IMAXBEL, TERMCODE_INPUT}, +#else + {0, 0}, +#endif + /* IUTF8 isn't standardised in rfc4254 but is likely soon. + * Implemented by linux and darwin */ +#ifdef IUTF8 + {IUTF8, TERMCODE_INPUT}, +#else + {0, 0}, +#endif + {0, 0}, /* 43 */ + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, /* 49 */ + {ISIG, TERMCODE_LOCAL}, /* local flags */ + {ICANON, TERMCODE_LOCAL}, +#ifdef XCASE + {XCASE, TERMCODE_LOCAL}, +#else + {0, 0}, +#endif + {ECHO, TERMCODE_LOCAL}, + {ECHOE, TERMCODE_LOCAL}, + {ECHOK, TERMCODE_LOCAL}, + {ECHONL, TERMCODE_LOCAL}, + {NOFLSH, TERMCODE_LOCAL}, + {TOSTOP, TERMCODE_LOCAL}, + {IEXTEN, TERMCODE_LOCAL}, +#ifdef ECHOCTL + {ECHOCTL, TERMCODE_LOCAL}, +#else + {0, 0}, +#endif +#ifdef ECHOKE + {ECHOKE, TERMCODE_LOCAL}, +#else + {0, 0}, +#endif +#ifdef PENDIN + {PENDIN, TERMCODE_LOCAL}, +#else + {0, 0}, +#endif + {0, 0}, /* 63 */ + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, /* 69 */ + {OPOST, TERMCODE_OUTPUT}, /* output flags */ +#ifdef OLCUC + {OLCUC, TERMCODE_OUTPUT}, +#else + {0, 0}, +#endif + {ONLCR, TERMCODE_OUTPUT}, +#ifdef OCRNL + {OCRNL, TERMCODE_OUTPUT}, +#else + {0, 0}, +#endif +#ifdef ONOCR + {ONOCR, TERMCODE_OUTPUT}, +#else + {0, 0}, +#endif +#ifdef ONLRET + {ONLRET, TERMCODE_OUTPUT}, +#else + {0, 0}, +#endif + {0, 0}, /* 76 */ + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, /* 89 */ + {CS7, TERMCODE_CONTROL}, + {CS8, TERMCODE_CONTROL}, + {PARENB, TERMCODE_CONTROL}, + {PARODD, TERMCODE_CONTROL} + /* 94 */ +}; diff --git a/src/termcodes.h b/src/termcodes.h new file mode 100644 index 0000000..cd76b7f --- /dev/null +++ b/src/termcodes.h @@ -0,0 +1,46 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_TERMCODES_H_ +#define DROPBEAR_TERMCODES_H_ + +#define TERMCODE_NONE 0 +#define TERMCODE_CONTROL 1 +#define TERMCODE_INPUT 2 +#define TERMCODE_OUTPUT 3 +#define TERMCODE_LOCAL 4 +#define TERMCODE_CONTROLCHAR 5 + +#define MAX_TERMCODE 93 + +struct TermCode { + + unsigned int mapcode; + unsigned char type; + +}; + +extern const struct TermCode termcodes[]; + +#endif /* DROPBEAR_TERMCODES_H_ */ diff --git a/src/x11fwd.h b/src/x11fwd.h new file mode 100644 index 0000000..96f51b1 --- /dev/null +++ b/src/x11fwd.h @@ -0,0 +1,37 @@ +/* + * Dropbear - a SSH2 server + * + * Copyright (c) 2002,2003 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ +#ifndef DROPBEAR__X11FWD_H_ +#define DROPBEAR__X11FWD_H_ +#if DROPBEAR_X11FWD + +#include "includes.h" +#include "chansession.h" +#include "channel.h" + +int x11req(struct ChanSess * chansess); +void x11setauth(const struct ChanSess *chansess); +void x11cleanup(struct ChanSess *chansess); + +#endif /* DROPBEAR_X11FWD */ +#endif /* DROPBEAR__X11FWD_H_ */ |