/src/dropbear/src/svr-authpubkeyoptions.c
Line | Count | Source |
1 | | /* |
2 | | * Dropbear - a SSH2 server |
3 | | * |
4 | | * Copyright (c) 2008 Frederic Moulins |
5 | | * All rights reserved. |
6 | | * |
7 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
8 | | * of this software and associated documentation files (the "Software"), to deal |
9 | | * in the Software without restriction, including without limitation the rights |
10 | | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 | | * copies of the Software, and to permit persons to whom the Software is |
12 | | * furnished to do so, subject to the following conditions: |
13 | | * |
14 | | * The above copyright notice and this permission notice shall be included in |
15 | | * all copies or substantial portions of the Software. |
16 | | * |
17 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
20 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
22 | | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
23 | | * SOFTWARE. |
24 | | * |
25 | | * This file incorporates work covered by the following copyright and |
26 | | * permission notice: |
27 | | * |
28 | | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
29 | | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
30 | | * All rights reserved |
31 | | * As far as I am concerned, the code I have written for this software |
32 | | * can be used freely for any purpose. Any derived versions of this |
33 | | * software must be clearly marked as such, and if the derived work is |
34 | | * incompatible with the protocol description in the RFC file, it must be |
35 | | * called by a name other than "ssh" or "Secure Shell". |
36 | | * |
37 | | * This copyright and permission notice applies to the code parsing public keys |
38 | | * options string which can also be found in OpenSSH auth-options.c file |
39 | | * (auth_parse_options). |
40 | | * |
41 | | */ |
42 | | |
43 | | /* Process pubkey options during a pubkey auth request */ |
44 | | #include "includes.h" |
45 | | #include "session.h" |
46 | | #include "dbutil.h" |
47 | | #include "signkey.h" |
48 | | #include "auth.h" |
49 | | #include "runopts.h" |
50 | | |
51 | | #if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT |
52 | | |
53 | | /* Returns 1 if pubkey allows agent forwarding, |
54 | | * 0 otherwise */ |
55 | 0 | int svr_pubkey_allows_agentfwd() { |
56 | 0 | if (ses.authstate.pubkey_options |
57 | 0 | && ses.authstate.pubkey_options->no_agent_forwarding_flag) { |
58 | 0 | return 0; |
59 | 0 | } |
60 | 0 | return 1; |
61 | 0 | } |
62 | | |
63 | | /* Returns 1 if pubkey allows tcp forwarding, |
64 | | * 0 otherwise */ |
65 | 0 | int svr_pubkey_allows_tcpfwd() { |
66 | 0 | if (ses.authstate.pubkey_options |
67 | 0 | && ses.authstate.pubkey_options->no_port_forwarding_flag) { |
68 | 0 | return 0; |
69 | 0 | } |
70 | 0 | return 1; |
71 | 0 | } |
72 | | |
73 | | /* Returns 1 if a forced command pubkey option is set */ |
74 | 0 | int svr_pubkey_has_forced_command(void) { |
75 | 0 | return ses.authstate.pubkey_options |
76 | 0 | && ses.authstate.pubkey_options->forced_command; |
77 | 0 | } |
78 | | |
79 | | /* Returns 1 if pubkey allows x11 forwarding, |
80 | | * 0 otherwise */ |
81 | 0 | int svr_pubkey_allows_x11fwd() { |
82 | 0 | if (ses.authstate.pubkey_options |
83 | 0 | && ses.authstate.pubkey_options->no_x11_forwarding_flag) { |
84 | 0 | return 0; |
85 | 0 | } |
86 | 0 | return 1; |
87 | 0 | } |
88 | | |
89 | | /* Returns 1 if pubkey allows pty, 0 otherwise */ |
90 | 0 | int svr_pubkey_allows_pty() { |
91 | 0 | if (ses.authstate.pubkey_options |
92 | 0 | && ses.authstate.pubkey_options->no_pty_flag) { |
93 | 0 | return 0; |
94 | 0 | } |
95 | 0 | return 1; |
96 | 0 | } |
97 | | |
98 | | /* Returns 1 if pubkey allows local tcp forwarding to the provided destination, |
99 | | * 0 otherwise */ |
100 | 0 | int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) { |
101 | 0 | if (ses.authstate.pubkey_options |
102 | 0 | && ses.authstate.pubkey_options->permit_open_destinations) { |
103 | 0 | m_list_elem *iter = ses.authstate.pubkey_options->permit_open_destinations->first; |
104 | 0 | while (iter) { |
105 | 0 | struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)iter->item; |
106 | 0 | if (strcmp(entry->host, host) == 0) { |
107 | 0 | if ((entry->port == PUBKEY_OPTIONS_ANY_PORT) || (entry->port == port)) { |
108 | 0 | return 1; |
109 | 0 | } |
110 | 0 | } |
111 | | |
112 | 0 | iter = iter->next; |
113 | 0 | } |
114 | | |
115 | 0 | return 0; |
116 | 0 | } |
117 | | |
118 | 0 | return 1; |
119 | 0 | } |
120 | | |
121 | | /* Returns 1 if pubkey allows remote tcp forwarding from the provided source, |
122 | | * 0 otherwise */ |
123 | 0 | int svr_pubkey_allows_remote_tcpfwd(const char *host, unsigned int port) { |
124 | | /* no host restrictions */ |
125 | 0 | (void)host; |
126 | |
|
127 | 0 | if (ses.authstate.pubkey_options |
128 | 0 | && ses.authstate.pubkey_options->permit_listens) { |
129 | 0 | m_list_elem *iter = ses.authstate.pubkey_options->permit_listens->first; |
130 | 0 | while (iter) { |
131 | 0 | struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)iter->item; |
132 | 0 | if (entry->port == port) { |
133 | 0 | return 1; |
134 | 0 | } |
135 | | |
136 | 0 | iter = iter->next; |
137 | 0 | } |
138 | | |
139 | 0 | return 0; |
140 | 0 | } |
141 | | |
142 | 0 | return 1; |
143 | 0 | } |
144 | | |
145 | | /* Set chansession command to the one forced |
146 | | * by any 'command' public key option. */ |
147 | 0 | void svr_pubkey_set_forced_command(struct ChanSess *chansess) { |
148 | 0 | if (ses.authstate.pubkey_options && ses.authstate.pubkey_options->forced_command) { |
149 | 0 | TRACE(("Forced command '%s'", ses.authstate.pubkey_options->forced_command)) |
150 | 0 | if (chansess->cmd) { |
151 | | /* original_command takes ownership */ |
152 | 0 | chansess->original_command = chansess->cmd; |
153 | 0 | chansess->cmd = NULL; |
154 | 0 | } else { |
155 | 0 | chansess->original_command = m_strdup(""); |
156 | 0 | } |
157 | 0 | chansess->cmd = m_strdup(ses.authstate.pubkey_options->forced_command); |
158 | | #if LOG_COMMANDS |
159 | | dropbear_log(LOG_INFO, "Command forced to '%s'", chansess->original_command); |
160 | | #endif |
161 | 0 | } |
162 | 0 | } |
163 | | |
164 | | /* Free potential public key options */ |
165 | 0 | void svr_pubkey_options_cleanup(struct PubKeyOptions *pubkey_options) { |
166 | 0 | if (pubkey_options) { |
167 | 0 | m_free(pubkey_options->forced_command); |
168 | 0 | if (pubkey_options->permit_open_destinations) { |
169 | 0 | m_list_elem *iter = pubkey_options->permit_open_destinations->first; |
170 | 0 | while (iter) { |
171 | 0 | struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)list_remove(iter); |
172 | 0 | m_free(entry->host); |
173 | 0 | m_free(entry); |
174 | 0 | iter = pubkey_options->permit_open_destinations->first; |
175 | 0 | } |
176 | 0 | m_free(pubkey_options->permit_open_destinations); |
177 | 0 | } |
178 | 0 | if (pubkey_options->permit_listens) { |
179 | 0 | m_list_elem *iter = pubkey_options->permit_listens->first; |
180 | 0 | while (iter) { |
181 | 0 | struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)list_remove(iter); |
182 | 0 | m_free(entry->host); |
183 | 0 | m_free(entry); |
184 | 0 | iter = pubkey_options->permit_listens->first; |
185 | 0 | } |
186 | 0 | m_free(pubkey_options->permit_listens); |
187 | 0 | } |
188 | 0 | m_free(pubkey_options->info_env); |
189 | 0 | m_free(pubkey_options); |
190 | 0 | } |
191 | 0 | } |
192 | | |
193 | | /* helper for svr_add_pubkey_options. returns DROPBEAR_SUCCESS if the option is matched, |
194 | | and increments the options_buf */ |
195 | 0 | static int match_option(buffer *options_buf, const char *opt_name) { |
196 | 0 | const unsigned int len = strlen(opt_name); |
197 | 0 | if (options_buf->len - options_buf->pos < len) { |
198 | 0 | return DROPBEAR_FAILURE; |
199 | 0 | } |
200 | 0 | if (strncasecmp((const char *) buf_getptr(options_buf, len), opt_name, len) == 0) { |
201 | 0 | buf_incrpos(options_buf, len); |
202 | 0 | return DROPBEAR_SUCCESS; |
203 | 0 | } |
204 | 0 | return DROPBEAR_FAILURE; |
205 | 0 | } |
206 | | |
207 | | /* Parse pubkey options and return a malloced struct. |
208 | | * options_buf may be NULL, treated as empty. |
209 | | * Returns NULL on failure */ |
210 | | struct PubKeyOptions* |
211 | 0 | svr_parse_pubkey_options(buffer *options_buf, int line_num, const char* filename) { |
212 | 0 | struct PubKeyOptions *pubkey_options = NULL; |
213 | |
|
214 | 0 | TRACE(("enter addpubkeyoptions")) |
215 | |
|
216 | 0 | pubkey_options = (struct PubKeyOptions*)m_malloc(sizeof( struct PubKeyOptions )); |
217 | |
|
218 | 0 | if (!options_buf) { |
219 | 0 | TRACE(("leave addpubkeyoptions, null options")) |
220 | 0 | return pubkey_options; |
221 | 0 | } |
222 | | |
223 | 0 | buf_setpos(options_buf, 0); |
224 | 0 | while (options_buf->pos < options_buf->len) { |
225 | 0 | if (match_option(options_buf, "no-port-forwarding") == DROPBEAR_SUCCESS) { |
226 | 0 | dropbear_log(LOG_WARNING, "Port forwarding disabled."); |
227 | 0 | pubkey_options->no_port_forwarding_flag = 1; |
228 | 0 | goto next_option; |
229 | 0 | } |
230 | 0 | if (match_option(options_buf, "no-agent-forwarding") == DROPBEAR_SUCCESS) { |
231 | 0 | #if DROPBEAR_SVR_AGENTFWD |
232 | 0 | dropbear_log(LOG_WARNING, "Agent forwarding disabled."); |
233 | 0 | pubkey_options->no_agent_forwarding_flag = 1; |
234 | 0 | #endif |
235 | 0 | goto next_option; |
236 | 0 | } |
237 | 0 | if (match_option(options_buf, "no-X11-forwarding") == DROPBEAR_SUCCESS) { |
238 | | #if DROPBEAR_X11FWD |
239 | | dropbear_log(LOG_WARNING, "X11 forwarding disabled."); |
240 | | pubkey_options->no_x11_forwarding_flag = 1; |
241 | | #endif |
242 | 0 | goto next_option; |
243 | 0 | } |
244 | 0 | if (match_option(options_buf, "no-pty") == DROPBEAR_SUCCESS) { |
245 | 0 | dropbear_log(LOG_WARNING, "Pty allocation disabled."); |
246 | 0 | pubkey_options->no_pty_flag = 1; |
247 | 0 | goto next_option; |
248 | 0 | } |
249 | 0 | if (match_option(options_buf, "restrict") == DROPBEAR_SUCCESS) { |
250 | 0 | dropbear_log(LOG_WARNING, "Restrict option set"); |
251 | 0 | pubkey_options->no_port_forwarding_flag = 1; |
252 | 0 | #if DROPBEAR_SVR_AGENTFWD |
253 | 0 | pubkey_options->no_agent_forwarding_flag = 1; |
254 | 0 | #endif |
255 | | #if DROPBEAR_X11FWD |
256 | | pubkey_options->no_x11_forwarding_flag = 1; |
257 | | #endif |
258 | 0 | pubkey_options->no_pty_flag = 1; |
259 | 0 | goto next_option; |
260 | 0 | } |
261 | 0 | if (match_option(options_buf, "command=\"") == DROPBEAR_SUCCESS) { |
262 | 0 | int escaped = 0; |
263 | 0 | const unsigned char* command_start = buf_getptr(options_buf, 0); |
264 | |
|
265 | 0 | if (pubkey_options->forced_command) { |
266 | | /* multiple command= options */ |
267 | 0 | goto bad_option; |
268 | 0 | } |
269 | | |
270 | 0 | while (options_buf->pos < options_buf->len) { |
271 | 0 | const char c = buf_getbyte(options_buf); |
272 | 0 | if (!escaped && c == '"') { |
273 | 0 | const int command_len = buf_getptr(options_buf, 0) - command_start; |
274 | 0 | pubkey_options->forced_command = m_malloc(command_len); |
275 | 0 | memcpy(pubkey_options->forced_command, |
276 | 0 | command_start, command_len-1); |
277 | 0 | pubkey_options->forced_command[command_len-1] = '\0'; |
278 | 0 | goto next_option; |
279 | 0 | } |
280 | 0 | escaped = (!escaped && c == '\\'); |
281 | 0 | } |
282 | 0 | dropbear_log(LOG_WARNING, "Badly formatted command= authorized_keys option"); |
283 | 0 | goto bad_option; |
284 | 0 | } |
285 | | |
286 | 0 | if (match_option(options_buf, "permitopen=\"") == DROPBEAR_SUCCESS) { |
287 | 0 | int valid_option = 0; |
288 | 0 | const unsigned char* permitopen_start = buf_getptr(options_buf, 0); |
289 | |
|
290 | 0 | if (!pubkey_options->permit_open_destinations) { |
291 | 0 | pubkey_options->permit_open_destinations = list_new(); |
292 | 0 | } |
293 | |
|
294 | 0 | while (options_buf->pos < options_buf->len) { |
295 | 0 | const char c = buf_getbyte(options_buf); |
296 | 0 | if (c == '"') { |
297 | 0 | char *spec = NULL; |
298 | 0 | char *portstring = NULL; |
299 | 0 | const int permitopen_len = buf_getptr(options_buf, 0) - permitopen_start; |
300 | 0 | struct PermitTCPFwdEntry *entry = |
301 | 0 | (struct PermitTCPFwdEntry*)m_malloc(sizeof(struct PermitTCPFwdEntry)); |
302 | |
|
303 | 0 | list_append(pubkey_options->permit_open_destinations, entry); |
304 | 0 | spec = m_malloc(permitopen_len); |
305 | 0 | memcpy(spec, permitopen_start, permitopen_len - 1); |
306 | 0 | spec[permitopen_len - 1] = '\0'; |
307 | 0 | if ((split_address_port(spec, &entry->host, &portstring) == DROPBEAR_SUCCESS) |
308 | 0 | && entry->host && portstring) { |
309 | 0 | if (strcmp(portstring, "*") == 0) { |
310 | 0 | valid_option = 1; |
311 | 0 | entry->port = PUBKEY_OPTIONS_ANY_PORT; |
312 | 0 | TRACE(("local port forwarding allowed to host '%s'", entry->host)); |
313 | 0 | } else if (m_str_to_uint(portstring, &entry->port) == DROPBEAR_SUCCESS |
314 | 0 | && entry->port <= 65535) { |
315 | 0 | valid_option = 1; |
316 | 0 | TRACE(("local port forwarding allowed to host '%s' and port '%u'", |
317 | 0 | entry->host, entry->port)); |
318 | 0 | } |
319 | 0 | } |
320 | |
|
321 | 0 | m_free(spec); |
322 | 0 | m_free(portstring); |
323 | 0 | break; |
324 | 0 | } |
325 | 0 | } |
326 | |
|
327 | 0 | if (valid_option) { |
328 | 0 | goto next_option; |
329 | 0 | } else { |
330 | 0 | dropbear_log(LOG_WARNING, "Badly formatted permitopen= authorized_keys option"); |
331 | 0 | goto bad_option; |
332 | 0 | } |
333 | 0 | } |
334 | | |
335 | 0 | if (match_option(options_buf, "permitlisten=\"") == DROPBEAR_SUCCESS) { |
336 | 0 | int valid_option = 0; |
337 | 0 | const unsigned char* permitlisten_start = buf_getptr(options_buf, 0); |
338 | |
|
339 | 0 | if (!pubkey_options->permit_listens) { |
340 | 0 | pubkey_options->permit_listens = list_new(); |
341 | 0 | } |
342 | |
|
343 | 0 | while (options_buf->pos < options_buf->len) { |
344 | 0 | const char c = buf_getbyte(options_buf); |
345 | 0 | if (c == '"') { |
346 | 0 | char *spec = NULL; |
347 | 0 | const int permitlisten_len = buf_getptr(options_buf, 0) - permitlisten_start; |
348 | 0 | struct PermitTCPFwdEntry *entry = |
349 | 0 | (struct PermitTCPFwdEntry*)m_malloc(sizeof(struct PermitTCPFwdEntry)); |
350 | |
|
351 | 0 | list_append(pubkey_options->permit_listens, entry); |
352 | | /* permitlisten_len includes trailing '"' */ |
353 | 0 | spec = m_malloc(permitlisten_len); |
354 | 0 | memcpy(spec, permitlisten_start, permitlisten_len - 1); |
355 | 0 | spec[permitlisten_len - 1] = '\0'; |
356 | | |
357 | | /* Only a plain port accepted. |
358 | | * OpenSSH supports [host:]port, but that isn't implemented. |
359 | | * port="*" isn't supported either, since it only is useful |
360 | | * with a host: part. */ |
361 | |
|
362 | 0 | if (m_str_to_uint(spec, &entry->port) == DROPBEAR_SUCCESS) { |
363 | 0 | valid_option = 1; |
364 | 0 | TRACE(("remote forwarding allows listening on port %u", |
365 | 0 | entry->port)); |
366 | 0 | } |
367 | |
|
368 | 0 | m_free(spec); |
369 | 0 | break; |
370 | 0 | } |
371 | 0 | } |
372 | |
|
373 | 0 | if (valid_option) { |
374 | 0 | goto next_option; |
375 | 0 | } else { |
376 | 0 | dropbear_log(LOG_WARNING, "Badly formatted permitlisten= authorized_keys option"); |
377 | 0 | goto bad_option; |
378 | 0 | } |
379 | 0 | } |
380 | | |
381 | 0 | if (match_option(options_buf, "no-touch-required") == DROPBEAR_SUCCESS) { |
382 | 0 | #if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 |
383 | 0 | dropbear_log(LOG_WARNING, "No user presence check required for U2F/FIDO key."); |
384 | 0 | pubkey_options->no_touch_required_flag = 1; |
385 | 0 | #endif |
386 | 0 | goto next_option; |
387 | 0 | } |
388 | 0 | if (match_option(options_buf, "verify-required") == DROPBEAR_SUCCESS) { |
389 | 0 | #if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 |
390 | 0 | dropbear_log(LOG_WARNING, "User verification required for U2F/FIDO key."); |
391 | 0 | pubkey_options->verify_required_flag = 1; |
392 | 0 | #endif |
393 | 0 | goto next_option; |
394 | 0 | } |
395 | | |
396 | 0 | next_option: |
397 | | /* |
398 | | * Skip the comma, and move to the next option |
399 | | * (or break out if there are no more). |
400 | | */ |
401 | 0 | if (options_buf->pos < options_buf->len |
402 | 0 | && buf_getbyte(options_buf) != ',') { |
403 | 0 | goto bad_option; |
404 | 0 | } |
405 | | /* Process the next option. */ |
406 | 0 | } |
407 | | /* parsed all options with no problem */ |
408 | 0 | goto end; |
409 | | |
410 | 0 | bad_option: |
411 | 0 | svr_pubkey_options_cleanup(pubkey_options); |
412 | 0 | pubkey_options = NULL; |
413 | 0 | dropbear_log(LOG_WARNING, "Bad public key options at %s:%d", filename, line_num); |
414 | |
|
415 | 0 | end: |
416 | 0 | TRACE(("leave addpubkeyoptions")) |
417 | 0 | return pubkey_options; |
418 | 0 | } |
419 | | |
420 | | #endif |