summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/agentfwd.h66
-rw-r--r--src/algo.h147
-rw-r--r--src/atomicio.c59
-rw-r--r--src/atomicio.h35
-rw-r--r--src/auth.h163
-rw-r--r--src/bignum.c104
-rw-r--r--src/bignum.h38
-rw-r--r--src/buffer.c372
-rw-r--r--src/buffer.h72
-rw-r--r--src/chachapoly.c148
-rw-r--r--src/chachapoly.h44
-rw-r--r--src/channel.h144
-rw-r--r--src/chansession.h106
-rw-r--r--src/circbuffer.c133
-rw-r--r--src/circbuffer.h52
-rw-r--r--src/cli-agentfwd.c316
-rw-r--r--src/cli-auth.c359
-rw-r--r--src/cli-authinteract.c176
-rw-r--r--src/cli-authpasswd.c161
-rw-r--r--src/cli-authpubkey.c291
-rw-r--r--src/cli-channel.c59
-rw-r--r--src/cli-chansession.c484
-rw-r--r--src/cli-kex.c465
-rw-r--r--src/cli-main.c155
-rw-r--r--src/cli-runopts.c929
-rw-r--r--src/cli-session.c489
-rw-r--r--src/cli-tcpfwd.c286
-rw-r--r--src/common-algo.c579
-rw-r--r--src/common-channel.c1223
-rw-r--r--src/common-chansession.c43
-rw-r--r--src/common-kex.c1021
-rw-r--r--src/common-runopts.c173
-rw-r--r--src/common-session.c705
-rw-r--r--src/compat.c281
-rw-r--r--src/compat.h56
-rw-r--r--src/crypto_desc.c76
-rw-r--r--src/crypto_desc.h9
-rw-r--r--src/curve25519.c497
-rw-r--r--src/curve25519.h37
-rw-r--r--src/dbhelpers.c18
-rw-r--r--src/dbhelpers.h21
-rw-r--r--src/dbmalloc.c192
-rw-r--r--src/dbmalloc.h27
-rw-r--r--src/dbmulti.c103
-rw-r--r--src/dbrandom.c377
-rw-r--r--src/dbrandom.h35
-rw-r--r--src/dbutil.c786
-rw-r--r--src/dbutil.h115
-rw-r--r--src/debug.h102
-rw-r--r--src/dh_groups.c97
-rw-r--r--src/dh_groups.h26
-rwxr-xr-xsrc/dropbear_lint.sh8
-rw-r--r--src/dropbearconvert.c145
-rw-r--r--src/dropbearkey.c367
-rw-r--r--src/dss.c378
-rw-r--r--src/dss.h59
-rw-r--r--src/ecc.c264
-rw-r--r--src/ecc.h35
-rw-r--r--src/ecdsa.c427
-rw-r--r--src/ecdsa.h36
-rw-r--r--src/ed25519.c193
-rw-r--r--src/ed25519.h56
-rw-r--r--src/fake-rfc2553.c237
-rw-r--r--src/fake-rfc2553.h177
-rw-r--r--src/filelist.txt121
-rw-r--r--src/fuzz-wrapfd.h27
-rw-r--r--src/fuzz.h114
-rw-r--r--src/gcm.c120
-rw-r--r--src/gcm.h47
-rw-r--r--src/gendss.c198
-rw-r--r--src/gendss.h36
-rw-r--r--src/gened25519.c47
-rw-r--r--src/gened25519.h36
-rw-r--r--src/genrsa.c134
-rw-r--r--src/genrsa.h36
-rw-r--r--src/gensignkey.c193
-rw-r--r--src/gensignkey.h9
-rw-r--r--src/includes.h198
-rw-r--r--src/kex.h113
-rw-r--r--src/keyimport.c1147
-rw-r--r--src/keyimport.h42
-rw-r--r--src/list.c49
-rw-r--r--src/list.h28
-rw-r--r--src/listener.c174
-rw-r--r--src/listener.h65
-rw-r--r--src/loginrec.c1380
-rw-r--r--src/loginrec.h181
-rw-r--r--src/ltc_prng.c136
-rw-r--r--src/ltc_prng.h12
-rw-r--r--src/netio.c699
-rw-r--r--src/netio.h65
-rw-r--r--src/options.h26
-rw-r--r--src/packet.c758
-rw-r--r--src/packet.h53
-rw-r--r--src/process-packet.c180
-rw-r--r--src/progressmeter.c294
-rw-r--r--src/progressmeter.h27
-rw-r--r--src/pubkeyapi.h151
-rw-r--r--src/queue.c87
-rw-r--r--src/queue.h49
-rw-r--r--src/rsa.c431
-rw-r--r--src/rsa.h59
-rw-r--r--src/runopts.h208
-rw-r--r--src/scp.c1259
-rw-r--r--src/scpmisc.c253
-rw-r--r--src/scpmisc.h66
-rw-r--r--src/service.h30
-rw-r--r--src/session.h352
-rw-r--r--src/signkey.c785
-rw-r--r--src/signkey.h163
-rw-r--r--src/signkey_ossh.c161
-rw-r--r--src/signkey_ossh.h15
-rw-r--r--src/sk-ecdsa.c63
-rw-r--r--src/sk-ecdsa.h17
-rw-r--r--src/sk-ed25519.c75
-rw-r--r--src/sk-ed25519.h17
-rw-r--r--src/ssh.h133
-rw-r--r--src/sshpty.c414
-rw-r--r--src/sshpty.h28
-rw-r--r--src/svr-agentfwd.c279
-rw-r--r--src/svr-auth.c472
-rw-r--r--src/svr-authpam.c309
-rw-r--r--src/svr-authpasswd.c134
-rw-r--r--src/svr-authpubkey.c624
-rw-r--r--src/svr-authpubkeyoptions.c331
-rw-r--r--src/svr-chansession.c1114
-rw-r--r--src/svr-kex.c276
-rw-r--r--src/svr-main.c512
-rw-r--r--src/svr-runopts.c704
-rw-r--r--src/svr-service.c87
-rw-r--r--src/svr-session.c375
-rw-r--r--src/svr-tcpfwd.c310
-rw-r--r--src/svr-x11fwd.c269
-rw-r--r--src/sysoptions.h398
-rw-r--r--src/tcp-accept.c146
-rw-r--r--src/tcpfwd.h78
-rw-r--r--src/termcodes.c213
-rw-r--r--src/termcodes.h46
-rw-r--r--src/x11fwd.h37
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(&param->pub, &param->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, &param->priv) != MP_OKAY) {
+ dropbear_exit("Diffie-Hellman error");
+ }
+ if (mp_div_2(&param->priv, &dh_q) != MP_OKAY) {
+ dropbear_exit("Diffie-Hellman error");
+ }
+
+ /* Generate a private portion 0 < dh_priv < dh_q */
+ gen_random_mpint(&dh_q, &param->priv);
+
+ /* f = g^y mod p */
+ if (mp_exptmod(&dh_g, &param->priv, &dh_p, &param->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(&param->pub, &param->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, &param->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 = &param->pub;
+ dh_f = dh_pub_them;
+ } else {
+ dh_e = dh_pub_them;
+ dh_f = &param->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,
+ &param->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(&param->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, &param->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 = &param->key;
+ Q_S = Q_them;
+ } else {
+ Q_C = Q_them;
+ Q_S = &param->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(&timesec);
+ 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_ */