summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta/recipes-core/dropbear/dropbear.inc3
-rw-r--r--meta/recipes-core/dropbear/dropbear/0001-Add-m_snprintf-that-won-t-return-negative.patch48
-rw-r--r--meta/recipes-core/dropbear/dropbear/0001-Handle-arbitrary-length-paths-and-commands-in-multih.patch126
-rw-r--r--meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch344
4 files changed, 521 insertions, 0 deletions
diff --git a/meta/recipes-core/dropbear/dropbear.inc b/meta/recipes-core/dropbear/dropbear.inc
index a32242949b..94059df258 100644
--- a/meta/recipes-core/dropbear/dropbear.inc
+++ b/meta/recipes-core/dropbear/dropbear.inc
@@ -31,6 +31,9 @@ SRC_URI = "http://matt.ucc.asn.au/dropbear/releases/dropbear-${PV}.tar.bz2 \
31 file://CVE-2021-36369.patch \ 31 file://CVE-2021-36369.patch \
32 file://CVE-2023-36328.patch \ 32 file://CVE-2023-36328.patch \
33 file://CVE-2023-48795.patch \ 33 file://CVE-2023-48795.patch \
34 file://0001-Add-m_snprintf-that-won-t-return-negative.patch \
35 file://0001-Handle-arbitrary-length-paths-and-commands-in-multih.patch \
36 file://CVE-2025-47203.patch \
34 " 37 "
35 38
36PAM_SRC_URI = "file://0005-dropbear-enable-pam.patch \ 39PAM_SRC_URI = "file://0005-dropbear-enable-pam.patch \
diff --git a/meta/recipes-core/dropbear/dropbear/0001-Add-m_snprintf-that-won-t-return-negative.patch b/meta/recipes-core/dropbear/dropbear/0001-Add-m_snprintf-that-won-t-return-negative.patch
new file mode 100644
index 0000000000..ec75fcbc61
--- /dev/null
+++ b/meta/recipes-core/dropbear/dropbear/0001-Add-m_snprintf-that-won-t-return-negative.patch
@@ -0,0 +1,48 @@
1From ac2433cb8daa1279d14f8b2cd4c7e1f3405787d4 Mon Sep 17 00:00:00 2001
2From: Matt Johnston <matt@ucc.asn.au>
3Date: Fri, 1 Apr 2022 12:10:48 +0800
4Subject: [PATCH] Add m_snprintf() that won't return negative
5
6Origin: https://github.com/mkj/dropbear/commit/ac2433cb8daa1279d14f8b2cd4c7e1f3405787d4
7
8Upstream-Status: Backport [https://github.com/mkj/dropbear/commit/ac2433cb8daa1279d14f8b2cd4c7e1f3405787d4]
9Signed-off-by: Peter Marko <peter.marko@siemens.com>
10---
11 dbutil.c | 13 +++++++++++++
12 dbutil.h | 2 ++
13 2 files changed, 15 insertions(+)
14
15diff --git a/dbutil.c b/dbutil.c
16index 5af6330..d4c3298 100644
17--- a/dbutil.c
18+++ b/dbutil.c
19@@ -691,3 +691,16 @@ void fsync_parent_dir(const char* fn) {
20 m_free(fn_dir);
21 #endif
22 }
23+
24+int m_snprintf(char *str, size_t size, const char *format, ...) {
25+ va_list param;
26+ int ret;
27+
28+ va_start(param, format);
29+ ret = vsnprintf(str, size, format, param);
30+ va_end(param);
31+ if (ret < 0) {
32+ dropbear_exit("snprintf failed");
33+ }
34+ return ret;
35+}
36diff --git a/dbutil.h b/dbutil.h
37index 2a1c82c..71cffe8 100644
38--- a/dbutil.h
39+++ b/dbutil.h
40@@ -70,6 +70,8 @@ void m_close(int fd);
41 void setnonblocking(int fd);
42 void disallow_core(void);
43 int m_str_to_uint(const char* str, unsigned int *val);
44+/* The same as snprintf() but exits rather than returning negative */
45+int m_snprintf(char *str, size_t size, const char *format, ...);
46
47 /* Used to force mp_ints to be initialised */
48 #define DEF_MP_INT(X) mp_int X = {0, 0, 0, NULL}
diff --git a/meta/recipes-core/dropbear/dropbear/0001-Handle-arbitrary-length-paths-and-commands-in-multih.patch b/meta/recipes-core/dropbear/dropbear/0001-Handle-arbitrary-length-paths-and-commands-in-multih.patch
new file mode 100644
index 0000000000..dbc457209d
--- /dev/null
+++ b/meta/recipes-core/dropbear/dropbear/0001-Handle-arbitrary-length-paths-and-commands-in-multih.patch
@@ -0,0 +1,126 @@
1From fe15c36664a984de9e1b2386ac52d4b8577cac93 Mon Sep 17 00:00:00 2001
2From: Matt Johnston <matt@ucc.asn.au>
3Date: Mon, 1 Apr 2024 11:50:26 +0800
4Subject: [PATCH] Handle arbitrary length paths and commands in
5 multihop_passthrough_args()
6
7Origin: https://github.com/mkj/dropbear/commit/7894254afa9b1d3a836911b7ccea1fe18391b881
8Origin: https://github.com/mkj/dropbear/commit/2f1177e55f33afd676e08c9449ab7ab517fc3b30
9Origin: https://github.com/mkj/dropbear/commit/697b1f86c0b2b0caf12e9e32bab29161093ab5d4
10Origin: https://github.com/mkj/dropbear/commit/dd03da772bfad6174425066ff9752b60e25ed183
11Origin: https://github.com/mkj/dropbear/commit/d59436a4d56de58b856142a5d489a4a8fc7382ed
12
13Upstream-Status: Backport [see commits above]
14Signed-off-by: Peter Marko <peter.marko@siemens.com>
15---
16 cli-runopts.c | 63 +++++++++++++++++++++------------------------------
17 1 file changed, 26 insertions(+), 37 deletions(-)
18
19diff --git a/cli-runopts.c b/cli-runopts.c
20index 255b47e..9798f62 100644
21--- a/cli-runopts.c
22+++ b/cli-runopts.c
23@@ -523,61 +523,50 @@ static void loadidentityfile(const char* filename, int warnfail) {
24
25 #if DROPBEAR_CLI_MULTIHOP
26
27-static char*
28-multihop_passthrough_args() {
29- char *ret;
30- int total;
31- unsigned int len = 0;
32+/* Fill out -i, -y, -W options that make sense for all
33+ * the intermediate processes */
34+static char* multihop_passthrough_args(void) {
35+ char *args = NULL;
36+ unsigned int len, total;
37+#if DROPBEAR_CLI_PUBKEY_AUTH
38 m_list_elem *iter;
39- /* Fill out -i, -y, -W options that make sense for all
40- * the intermediate processes */
41+#endif
42+ /* Sufficient space for non-string args */
43+ len = 100;
44+
45+ /* String arguments have arbitrary length, so determine space required */
46 #if DROPBEAR_CLI_PUBKEY_AUTH
47 for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
48 {
49 sign_key * key = (sign_key*)iter->item;
50- len += 3 + strlen(key->filename);
51+ len += 4 + strlen(key->filename);
52 }
53-#endif /* DROPBEAR_CLI_PUBKEY_AUTH */
54+#endif
55
56- len += 30; /* space for -W <size>, terminator. */
57- ret = m_malloc(len);
58+ args = m_malloc(len);
59 total = 0;
60
61- if (cli_opts.no_hostkey_check)
62- {
63- int written = snprintf(ret+total, len-total, "-y -y ");
64- total += written;
65- }
66- else if (cli_opts.always_accept_key)
67- {
68- int written = snprintf(ret+total, len-total, "-y ");
69- total += written;
70+ /* Create new argument string */
71+
72+ if (cli_opts.no_hostkey_check) {
73+ total += m_snprintf(args+total, len-total, "-y -y ");
74+ } else if (cli_opts.always_accept_key) {
75+ total += m_snprintf(args+total, len-total, "-y ");
76 }
77
78- if (opts.recv_window != DEFAULT_RECV_WINDOW)
79- {
80- int written = snprintf(ret+total, len-total, "-W %u ", opts.recv_window);
81- total += written;
82+ if (opts.recv_window != DEFAULT_RECV_WINDOW) {
83+ total += m_snprintf(args+total, len-total, "-W %u ", opts.recv_window);
84 }
85
86 #if DROPBEAR_CLI_PUBKEY_AUTH
87 for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
88 {
89 sign_key * key = (sign_key*)iter->item;
90- const size_t size = len - total;
91- int written = snprintf(ret+total, size, "-i %s ", key->filename);
92- dropbear_assert((unsigned int)written < size);
93- total += written;
94+ total += m_snprintf(args+total, len-total, "-i %s ", key->filename);
95 }
96 #endif /* DROPBEAR_CLI_PUBKEY_AUTH */
97
98- /* if args were passed, total will be not zero, and it will have a space at the end, so remove that */
99- if (total > 0)
100- {
101- total--;
102- }
103-
104- return ret;
105+ return args;
106 }
107
108 /* Sets up 'onion-forwarding' connections. This will spawn
109@@ -608,7 +597,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
110 && strchr(cli_opts.username, '@')) {
111 unsigned int len = strlen(orighostarg) + strlen(cli_opts.username) + 2;
112 hostbuf = m_malloc(len);
113- snprintf(hostbuf, len, "%s@%s", cli_opts.username, orighostarg);
114+ m_snprintf(hostbuf, len, "%s@%s", cli_opts.username, orighostarg);
115 } else {
116 hostbuf = m_strdup(orighostarg);
117 }
118@@ -642,7 +631,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
119 + strlen(passthrough_args)
120 + 30;
121 cli_opts.proxycmd = m_malloc(cmd_len);
122- snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s",
123+ m_snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s",
124 argv0, cli_opts.remotehost, cli_opts.remoteport,
125 passthrough_args, remainder);
126 #ifndef DISABLE_ZLIB
diff --git a/meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch b/meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch
new file mode 100644
index 0000000000..3a51927cfe
--- /dev/null
+++ b/meta/recipes-core/dropbear/dropbear/CVE-2025-47203.patch
@@ -0,0 +1,344 @@
1From e5a0ef27c227f7ae69d9a9fec98a056494409b9b Mon Sep 17 00:00:00 2001
2From: Matt Johnston <matt@ucc.asn.au>
3Date: Mon, 5 May 2025 23:14:19 +0800
4Subject: [PATCH] Execute multihop commands directly, no shell
5
6This avoids problems with shell escaping if arguments contain special
7characters.
8
9Origin: https://github.com/mkj/dropbear/commit/e5a0ef27c227f7ae69d9a9fec98a056494409b9b
10Bug: https://www.openwall.com/lists/oss-security/2025/05/13/1
11Bug-Debian: https://deb.freexian.com/extended-lts/tracker/CVE-2025-47203
12
13CVE: CVE-2025-47203
14Upstream-Status: Backport [https://github.com/mkj/dropbear/commit/e5a0ef27c227f7ae69d9a9fec98a056494409b9b]
15Signed-off-by: Peter Marko <peter.marko@siemens.com>
16---
17 cli-main.c | 60 ++++++++++++++++++++++++++++--------------
18 cli-runopts.c | 84 +++++++++++++++++++++++++++++++++++------------------------
19 dbutil.c | 9 +++++--
20 dbutil.h | 1 +
21 runopts.h | 5 ++++
22 5 files changed, 104 insertions(+), 55 deletions(-)
23
24diff --git a/cli-main.c b/cli-main.c
25index 7f455d1..53c55c1 100644
26--- a/cli-main.c
27+++ b/cli-main.c
28@@ -73,9 +73,8 @@ int main(int argc, char ** argv) {
29
30 pid_t proxy_cmd_pid = 0;
31 #if DROPBEAR_CLI_PROXYCMD
32- if (cli_opts.proxycmd) {
33+ if (cli_opts.proxycmd || cli_opts.proxyexec) {
34 cli_proxy_cmd(&sock_in, &sock_out, &proxy_cmd_pid);
35- m_free(cli_opts.proxycmd);
36 if (signal(SIGINT, kill_proxy_sighandler) == SIG_ERR ||
37 signal(SIGTERM, kill_proxy_sighandler) == SIG_ERR ||
38 signal(SIGHUP, kill_proxy_sighandler) == SIG_ERR) {
39@@ -96,7 +95,8 @@ int main(int argc, char ** argv) {
40 }
41 #endif /* DBMULTI stuff */
42
43-static void exec_proxy_cmd(const void *user_data_cmd) {
44+#if DROPBEAR_CLI_PROXYCMD
45+static void shell_proxy_cmd(const void *user_data_cmd) {
46 const char *cmd = user_data_cmd;
47 char *usershell;
48
49@@ -105,40 +105,62 @@ static void exec_proxy_cmd(const void *user_data_cmd) {
50 dropbear_exit("Failed to run '%s'\n", cmd);
51 }
52
53-#if DROPBEAR_CLI_PROXYCMD
54+static void exec_proxy_cmd(const void *unused) {
55+ (void)unused;
56+ run_command(cli_opts.proxyexec[0], cli_opts.proxyexec, ses.maxfd);
57+ dropbear_exit("Failed to run '%s'\n", cli_opts.proxyexec[0]);
58+}
59+
60 static void cli_proxy_cmd(int *sock_in, int *sock_out, pid_t *pid_out) {
61- char * ex_cmd = NULL;
62- size_t ex_cmdlen;
63+ char * cmd_arg = NULL;
64+ void (*exec_fn)(const void *user_data) = NULL;
65 int ret;
66
67+ /* exactly one of cli_opts.proxycmd or cli_opts.proxyexec should be set */
68+
69 /* File descriptor "-j &3" */
70- if (*cli_opts.proxycmd == '&') {
71+ if (cli_opts.proxycmd && *cli_opts.proxycmd == '&') {
72 char *p = cli_opts.proxycmd + 1;
73 int sock = strtoul(p, &p, 10);
74 /* must be a single number, and not stdin/stdout/stderr */
75 if (sock > 2 && sock < 1024 && *p == '\0') {
76 *sock_in = sock;
77 *sock_out = sock;
78- return;
79+ goto cleanup;
80 }
81 }
82
83- /* Normal proxycommand */
84-
85- /* So that spawn_command knows which shell to run */
86- fill_passwd(cli_opts.own_user);
87-
88- ex_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */
89- ex_cmd = m_malloc(ex_cmdlen);
90- snprintf(ex_cmd, ex_cmdlen, "exec %s", cli_opts.proxycmd);
91+ if (cli_opts.proxycmd) {
92+ /* Normal proxycommand */
93+ size_t shell_cmdlen;
94+ /* So that spawn_command knows which shell to run */
95+ fill_passwd(cli_opts.own_user);
96+
97+ shell_cmdlen = strlen(cli_opts.proxycmd) + 6; /* "exec " + command + '\0' */
98+ cmd_arg = m_malloc(shell_cmdlen);
99+ snprintf(cmd_arg, shell_cmdlen, "exec %s", cli_opts.proxycmd);
100+ exec_fn = shell_proxy_cmd;
101+ } else {
102+ /* No shell */
103+ exec_fn = exec_proxy_cmd;
104+ }
105
106- ret = spawn_command(exec_proxy_cmd, ex_cmd,
107- sock_out, sock_in, NULL, pid_out);
108- m_free(ex_cmd);
109+ ret = spawn_command(exec_fn, cmd_arg, sock_out, sock_in, NULL, pid_out);
110 if (ret == DROPBEAR_FAILURE) {
111 dropbear_exit("Failed running proxy command");
112 *sock_in = *sock_out = -1;
113 }
114+
115+cleanup:
116+ m_free(cli_opts.proxycmd);
117+ m_free(cmd_arg);
118+ if (cli_opts.proxyexec) {
119+ char **a = NULL;
120+ for (a = cli_opts.proxyexec; *a; a++) {
121+ m_free_direct(*a);
122+ }
123+ m_free(cli_opts.proxyexec);
124+ }
125 }
126
127 static void kill_proxy_sighandler(int UNUSED(signo)) {
128diff --git a/cli-runopts.c b/cli-runopts.c
129index 9798f62..0f3dcd0 100644
130--- a/cli-runopts.c
131+++ b/cli-runopts.c
132@@ -525,47 +525,69 @@ static void loadidentityfile(const char* filename, int warnfail) {
133
134 /* Fill out -i, -y, -W options that make sense for all
135 * the intermediate processes */
136-static char* multihop_passthrough_args(void) {
137- char *args = NULL;
138- unsigned int len, total;
139+static char** multihop_args(const char* argv0, const char* prior_hops) {
140+ /* null terminated array */
141+ char **args = NULL;
142+ size_t max_args = 14, pos = 0, len;
143 #if DROPBEAR_CLI_PUBKEY_AUTH
144 m_list_elem *iter;
145 #endif
146- /* Sufficient space for non-string args */
147- len = 100;
148
149- /* String arguments have arbitrary length, so determine space required */
150 #if DROPBEAR_CLI_PUBKEY_AUTH
151 for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
152 {
153- sign_key * key = (sign_key*)iter->item;
154- len += 4 + strlen(key->filename);
155+ /* "-i file" for each */
156+ max_args += 2;
157 }
158 #endif
159
160- args = m_malloc(len);
161- total = 0;
162+ args = m_malloc(sizeof(char*) * max_args);
163+ pos = 0;
164
165- /* Create new argument string */
166+ args[pos] = m_strdup(argv0);
167+ pos++;
168
169 if (cli_opts.no_hostkey_check) {
170- total += m_snprintf(args+total, len-total, "-y -y ");
171+ args[pos] = m_strdup("-y");
172+ pos++;
173+ args[pos] = m_strdup("-y");
174+ pos++;
175 } else if (cli_opts.always_accept_key) {
176- total += m_snprintf(args+total, len-total, "-y ");
177+ args[pos] = m_strdup("-y");
178+ pos++;
179 }
180
181 if (opts.recv_window != DEFAULT_RECV_WINDOW) {
182- total += m_snprintf(args+total, len-total, "-W %u ", opts.recv_window);
183+ args[pos] = m_strdup("-W");
184+ pos++;
185+ args[pos] = m_malloc(11);
186+ m_snprintf(args[pos], 11, "%u", opts.recv_window);
187+ pos++;
188 }
189
190 #if DROPBEAR_CLI_PUBKEY_AUTH
191 for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
192 {
193 sign_key * key = (sign_key*)iter->item;
194- total += m_snprintf(args+total, len-total, "-i %s ", key->filename);
195+ args[pos] = m_strdup("-i");
196+ pos++;
197+ args[pos] = m_strdup(key->filename);
198+ pos++;
199 }
200 #endif /* DROPBEAR_CLI_PUBKEY_AUTH */
201
202+ /* last hop */
203+ args[pos] = m_strdup("-B");
204+ pos++;
205+ len = strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) + 2;
206+ args[pos] = m_malloc(len);
207+ snprintf(args[pos], len, "%s:%s", cli_opts.remotehost, cli_opts.remoteport);
208+ pos++;
209+
210+ /* hostnames of prior hops */
211+ args[pos] = m_strdup(prior_hops);
212+ pos++;
213+
214 return args;
215 }
216
217@@ -585,7 +607,7 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
218 char *userhostarg = NULL;
219 char *hostbuf = NULL;
220 char *last_hop = NULL;
221- char *remainder = NULL;
222+ char *prior_hops = NULL;
223
224 /* both scp and rsync parse a user@host argument
225 * and turn it into "-l user host". This breaks
226@@ -603,6 +625,8 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
227 }
228 userhostarg = hostbuf;
229
230+ /* Split off any last hostname and use that as remotehost/remoteport.
231+ * That is used for authorized_keys checking etc */
232 last_hop = strrchr(userhostarg, ',');
233 if (last_hop) {
234 if (last_hop == userhostarg) {
235@@ -610,36 +634,28 @@ static void parse_multihop_hostname(const char* orighostarg, const char* argv0)
236 }
237 *last_hop = '\0';
238 last_hop++;
239- remainder = userhostarg;
240+ prior_hops = userhostarg;
241 userhostarg = last_hop;
242 }
243
244+ /* Update cli_opts.remotehost and cli_opts.remoteport */
245 parse_hostname(userhostarg);
246
247- if (last_hop) {
248- /* Set up the proxycmd */
249- unsigned int cmd_len = 0;
250- char *passthrough_args = multihop_passthrough_args();
251+ /* Construct any multihop proxy command. Use proxyexec to
252+ * avoid worrying about shell escaping. */
253+ if (prior_hops) {
254+ cli_opts.proxyexec = multihop_args(argv0, prior_hops);
255+ /* Any -J argument has been copied to proxyexec */
256 if (cli_opts.proxycmd) {
257 dropbear_exit("-J can't be used with multihop mode");
258 }
259- if (cli_opts.remoteport == NULL) {
260- cli_opts.remoteport = "22";
261- }
262- cmd_len = strlen(argv0) + strlen(remainder)
263- + strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport)
264- + strlen(passthrough_args)
265- + 30;
266- cli_opts.proxycmd = m_malloc(cmd_len);
267- m_snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s",
268- argv0, cli_opts.remotehost, cli_opts.remoteport,
269- passthrough_args, remainder);
270+
271 #ifndef DISABLE_ZLIB
272- /* The stream will be incompressible since it's encrypted. */
273+ /* This outer stream will be incompressible since it's encrypted. */
274 opts.compress_mode = DROPBEAR_COMPRESS_OFF;
275 #endif
276- m_free(passthrough_args);
277 }
278+
279 m_free(hostbuf);
280 }
281 #endif /* !DROPBEAR_CLI_MULTIHOP */
282diff --git a/dbutil.c b/dbutil.c
283index d4c3298..a51c1f9 100644
284--- a/dbutil.c
285+++ b/dbutil.c
286@@ -347,7 +347,6 @@ int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data,
287 void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
288 char * argv[4];
289 char * baseshell = NULL;
290- unsigned int i;
291
292 baseshell = basename(usershell);
293
294@@ -369,6 +368,12 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
295 argv[1] = NULL;
296 }
297
298+ run_command(usershell, argv, maxfd);
299+}
300+
301+void run_command(const char* argv0, char** args, unsigned int maxfd) {
302+ unsigned int i;
303+
304 /* Re-enable SIGPIPE for the executed process */
305 if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
306 dropbear_exit("signal() error");
307@@ -380,7 +385,7 @@ void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell) {
308 m_close(i);
309 }
310
311- execv(usershell, argv);
312+ execv(argv0, args);
313 }
314
315 #if DEBUG_TRACE
316diff --git a/dbutil.h b/dbutil.h
317index 71cffe8..5d86485 100644
318--- a/dbutil.h
319+++ b/dbutil.h
320@@ -60,6 +60,7 @@ char * stripcontrol(const char * text);
321 int spawn_command(void(*exec_fn)(const void *user_data), const void *exec_data,
322 int *writefd, int *readfd, int *errfd, pid_t *pid);
323 void run_shell_command(const char* cmd, unsigned int maxfd, char* usershell);
324+void run_command(const char* argv0, char** args, unsigned int maxfd);
325 #if ENABLE_CONNECT_UNIX
326 int connect_unix(const char* addr);
327 #endif
328diff --git a/runopts.h b/runopts.h
329index 01201d2..b49dc13 100644
330--- a/runopts.h
331+++ b/runopts.h
332@@ -179,7 +179,12 @@ typedef struct cli_runopts {
333 unsigned int netcat_port;
334 #endif
335 #if DROPBEAR_CLI_PROXYCMD
336+ /* A proxy command to run via the user's shell */
337 char *proxycmd;
338+#endif
339+#if DROPBEAR_CLI_MULTIHOP
340+ /* Similar to proxycmd, but is arguments for execve(), not shell */
341+ char **proxyexec;
342 #endif
343 char *bind_address;
344 char *bind_port;