/src/dropbear/src/svr-authpubkeyoptions.c
Line | Count | Source (jump to first uncovered line) |
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 pubkey allows x11 forwarding, |
74 | | * 0 otherwise */ |
75 | 0 | int svr_pubkey_allows_x11fwd() { |
76 | 0 | if (ses.authstate.pubkey_options |
77 | 0 | && ses.authstate.pubkey_options->no_x11_forwarding_flag) { |
78 | 0 | return 0; |
79 | 0 | } |
80 | 0 | return 1; |
81 | 0 | } |
82 | | |
83 | | /* Returns 1 if pubkey allows pty, 0 otherwise */ |
84 | 0 | int svr_pubkey_allows_pty() { |
85 | 0 | if (ses.authstate.pubkey_options |
86 | 0 | && ses.authstate.pubkey_options->no_pty_flag) { |
87 | 0 | return 0; |
88 | 0 | } |
89 | 0 | return 1; |
90 | 0 | } |
91 | | |
92 | | /* Returns 1 if pubkey allows local tcp fowarding to the provided destination, |
93 | | * 0 otherwise */ |
94 | 0 | int svr_pubkey_allows_local_tcpfwd(const char *host, unsigned int port) { |
95 | 0 | if (ses.authstate.pubkey_options |
96 | 0 | && ses.authstate.pubkey_options->permit_open_destinations) { |
97 | 0 | m_list_elem *iter = ses.authstate.pubkey_options->permit_open_destinations->first; |
98 | 0 | while (iter) { |
99 | 0 | struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)iter->item; |
100 | 0 | if (strcmp(entry->host, host) == 0) { |
101 | 0 | if ((entry->port == PUBKEY_OPTIONS_ANY_PORT) || (entry->port == port)) { |
102 | 0 | return 1; |
103 | 0 | } |
104 | 0 | } |
105 | | |
106 | 0 | iter = iter->next; |
107 | 0 | } |
108 | | |
109 | 0 | return 0; |
110 | 0 | } |
111 | | |
112 | 0 | return 1; |
113 | 0 | } |
114 | | |
115 | | /* Set chansession command to the one forced |
116 | | * by any 'command' public key option. */ |
117 | 0 | void svr_pubkey_set_forced_command(struct ChanSess *chansess) { |
118 | 0 | if (ses.authstate.pubkey_options && ses.authstate.pubkey_options->forced_command) { |
119 | 0 | TRACE(("Forced command '%s'", ses.authstate.pubkey_options->forced_command)) |
120 | 0 | if (chansess->cmd) { |
121 | | /* original_command takes ownership */ |
122 | 0 | chansess->original_command = chansess->cmd; |
123 | 0 | chansess->cmd = NULL; |
124 | 0 | } else { |
125 | 0 | chansess->original_command = m_strdup(""); |
126 | 0 | } |
127 | 0 | chansess->cmd = m_strdup(ses.authstate.pubkey_options->forced_command); |
128 | | #if LOG_COMMANDS |
129 | | dropbear_log(LOG_INFO, "Command forced to '%s'", chansess->original_command); |
130 | | #endif |
131 | 0 | } |
132 | 0 | } |
133 | | |
134 | | /* Free potential public key options */ |
135 | 3.86k | void svr_pubkey_options_cleanup() { |
136 | 3.86k | if (ses.authstate.pubkey_options) { |
137 | 0 | if (ses.authstate.pubkey_options->forced_command) { |
138 | 0 | m_free(ses.authstate.pubkey_options->forced_command); |
139 | 0 | } |
140 | 0 | if (ses.authstate.pubkey_options->permit_open_destinations) { |
141 | 0 | m_list_elem *iter = ses.authstate.pubkey_options->permit_open_destinations->first; |
142 | 0 | while (iter) { |
143 | 0 | struct PermitTCPFwdEntry *entry = (struct PermitTCPFwdEntry*)list_remove(iter); |
144 | 0 | m_free(entry->host); |
145 | 0 | m_free(entry); |
146 | 0 | iter = ses.authstate.pubkey_options->permit_open_destinations->first; |
147 | 0 | } |
148 | 0 | m_free(ses.authstate.pubkey_options->permit_open_destinations); |
149 | 0 | } |
150 | 0 | m_free(ses.authstate.pubkey_options); |
151 | 0 | } |
152 | 3.86k | if (ses.authstate.pubkey_info) { |
153 | 0 | m_free(ses.authstate.pubkey_info); |
154 | 0 | } |
155 | 3.86k | } |
156 | | |
157 | | /* helper for svr_add_pubkey_options. returns DROPBEAR_SUCCESS if the option is matched, |
158 | | and increments the options_buf */ |
159 | 0 | static int match_option(buffer *options_buf, const char *opt_name) { |
160 | 0 | const unsigned int len = strlen(opt_name); |
161 | 0 | if (options_buf->len - options_buf->pos < len) { |
162 | 0 | return DROPBEAR_FAILURE; |
163 | 0 | } |
164 | 0 | if (strncasecmp((const char *) buf_getptr(options_buf, len), opt_name, len) == 0) { |
165 | 0 | buf_incrpos(options_buf, len); |
166 | 0 | return DROPBEAR_SUCCESS; |
167 | 0 | } |
168 | 0 | return DROPBEAR_FAILURE; |
169 | 0 | } |
170 | | |
171 | | /* Parse pubkey options and set ses.authstate.pubkey_options accordingly. |
172 | | * Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */ |
173 | 0 | int svr_add_pubkey_options(buffer *options_buf, int line_num, const char* filename) { |
174 | 0 | int ret = DROPBEAR_FAILURE; |
175 | |
|
176 | 0 | TRACE(("enter addpubkeyoptions")) |
177 | |
|
178 | 0 | ses.authstate.pubkey_options = (struct PubKeyOptions*)m_malloc(sizeof( struct PubKeyOptions )); |
179 | |
|
180 | 0 | buf_setpos(options_buf, 0); |
181 | 0 | while (options_buf->pos < options_buf->len) { |
182 | 0 | if (match_option(options_buf, "no-port-forwarding") == DROPBEAR_SUCCESS) { |
183 | 0 | dropbear_log(LOG_WARNING, "Port forwarding disabled."); |
184 | 0 | ses.authstate.pubkey_options->no_port_forwarding_flag = 1; |
185 | 0 | goto next_option; |
186 | 0 | } |
187 | 0 | if (match_option(options_buf, "no-agent-forwarding") == DROPBEAR_SUCCESS) { |
188 | 0 | #if DROPBEAR_SVR_AGENTFWD |
189 | 0 | dropbear_log(LOG_WARNING, "Agent forwarding disabled."); |
190 | 0 | ses.authstate.pubkey_options->no_agent_forwarding_flag = 1; |
191 | 0 | #endif |
192 | 0 | goto next_option; |
193 | 0 | } |
194 | 0 | if (match_option(options_buf, "no-X11-forwarding") == DROPBEAR_SUCCESS) { |
195 | | #if DROPBEAR_X11FWD |
196 | | dropbear_log(LOG_WARNING, "X11 forwarding disabled."); |
197 | | ses.authstate.pubkey_options->no_x11_forwarding_flag = 1; |
198 | | #endif |
199 | 0 | goto next_option; |
200 | 0 | } |
201 | 0 | if (match_option(options_buf, "no-pty") == DROPBEAR_SUCCESS) { |
202 | 0 | dropbear_log(LOG_WARNING, "Pty allocation disabled."); |
203 | 0 | ses.authstate.pubkey_options->no_pty_flag = 1; |
204 | 0 | goto next_option; |
205 | 0 | } |
206 | 0 | if (match_option(options_buf, "restrict") == DROPBEAR_SUCCESS) { |
207 | 0 | dropbear_log(LOG_WARNING, "Restrict option set"); |
208 | 0 | ses.authstate.pubkey_options->no_port_forwarding_flag = 1; |
209 | 0 | #if DROPBEAR_SVR_AGENTFWD |
210 | 0 | ses.authstate.pubkey_options->no_agent_forwarding_flag = 1; |
211 | 0 | #endif |
212 | | #if DROPBEAR_X11FWD |
213 | | ses.authstate.pubkey_options->no_x11_forwarding_flag = 1; |
214 | | #endif |
215 | 0 | ses.authstate.pubkey_options->no_pty_flag = 1; |
216 | 0 | goto next_option; |
217 | 0 | } |
218 | 0 | if (match_option(options_buf, "command=\"") == DROPBEAR_SUCCESS) { |
219 | 0 | int escaped = 0; |
220 | 0 | const unsigned char* command_start = buf_getptr(options_buf, 0); |
221 | |
|
222 | 0 | if (ses.authstate.pubkey_options->forced_command) { |
223 | | /* multiple command= options */ |
224 | 0 | goto bad_option; |
225 | 0 | } |
226 | | |
227 | 0 | while (options_buf->pos < options_buf->len) { |
228 | 0 | const char c = buf_getbyte(options_buf); |
229 | 0 | if (!escaped && c == '"') { |
230 | 0 | const int command_len = buf_getptr(options_buf, 0) - command_start; |
231 | 0 | ses.authstate.pubkey_options->forced_command = m_malloc(command_len); |
232 | 0 | memcpy(ses.authstate.pubkey_options->forced_command, |
233 | 0 | command_start, command_len-1); |
234 | 0 | ses.authstate.pubkey_options->forced_command[command_len-1] = '\0'; |
235 | 0 | goto next_option; |
236 | 0 | } |
237 | 0 | escaped = (!escaped && c == '\\'); |
238 | 0 | } |
239 | 0 | dropbear_log(LOG_WARNING, "Badly formatted command= authorized_keys option"); |
240 | 0 | goto bad_option; |
241 | 0 | } |
242 | | |
243 | 0 | if (match_option(options_buf, "permitopen=\"") == DROPBEAR_SUCCESS) { |
244 | 0 | int valid_option = 0; |
245 | 0 | const unsigned char* permitopen_start = buf_getptr(options_buf, 0); |
246 | |
|
247 | 0 | if (!ses.authstate.pubkey_options->permit_open_destinations) { |
248 | 0 | ses.authstate.pubkey_options->permit_open_destinations = list_new(); |
249 | 0 | } |
250 | |
|
251 | 0 | while (options_buf->pos < options_buf->len) { |
252 | 0 | const char c = buf_getbyte(options_buf); |
253 | 0 | if (c == '"') { |
254 | 0 | char *spec = NULL; |
255 | 0 | char *portstring = NULL; |
256 | 0 | const int permitopen_len = buf_getptr(options_buf, 0) - permitopen_start; |
257 | 0 | struct PermitTCPFwdEntry *entry = |
258 | 0 | (struct PermitTCPFwdEntry*)m_malloc(sizeof(struct PermitTCPFwdEntry)); |
259 | |
|
260 | 0 | list_append(ses.authstate.pubkey_options->permit_open_destinations, entry); |
261 | 0 | spec = m_malloc(permitopen_len); |
262 | 0 | memcpy(spec, permitopen_start, permitopen_len - 1); |
263 | 0 | spec[permitopen_len - 1] = '\0'; |
264 | 0 | if ((split_address_port(spec, &entry->host, &portstring) == DROPBEAR_SUCCESS) |
265 | 0 | && entry->host && portstring) { |
266 | 0 | if (strcmp(portstring, "*") == 0) { |
267 | 0 | valid_option = 1; |
268 | 0 | entry->port = PUBKEY_OPTIONS_ANY_PORT; |
269 | 0 | TRACE(("local port forwarding allowed to host '%s'", entry->host)); |
270 | 0 | } else if (m_str_to_uint(portstring, &entry->port) == DROPBEAR_SUCCESS) { |
271 | 0 | valid_option = 1; |
272 | 0 | TRACE(("local port forwarding allowed to host '%s' and port '%u'", |
273 | 0 | entry->host, entry->port)); |
274 | 0 | } |
275 | 0 | } |
276 | |
|
277 | 0 | m_free(spec); |
278 | 0 | m_free(portstring); |
279 | 0 | break; |
280 | 0 | } |
281 | 0 | } |
282 | |
|
283 | 0 | if (valid_option) { |
284 | 0 | goto next_option; |
285 | 0 | } else { |
286 | 0 | dropbear_log(LOG_WARNING, "Badly formatted permitopen= authorized_keys option"); |
287 | 0 | goto bad_option; |
288 | 0 | } |
289 | 0 | } |
290 | | |
291 | 0 | if (match_option(options_buf, "no-touch-required") == DROPBEAR_SUCCESS) { |
292 | 0 | #if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 |
293 | 0 | dropbear_log(LOG_WARNING, "No user presence check required for U2F/FIDO key."); |
294 | 0 | ses.authstate.pubkey_options->no_touch_required_flag = 1; |
295 | 0 | #endif |
296 | 0 | goto next_option; |
297 | 0 | } |
298 | 0 | if (match_option(options_buf, "verify-required") == DROPBEAR_SUCCESS) { |
299 | 0 | #if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519 |
300 | 0 | dropbear_log(LOG_WARNING, "User verification required for U2F/FIDO key."); |
301 | 0 | ses.authstate.pubkey_options->verify_required_flag = 1; |
302 | 0 | #endif |
303 | 0 | goto next_option; |
304 | 0 | } |
305 | | |
306 | 0 | next_option: |
307 | | /* |
308 | | * Skip the comma, and move to the next option |
309 | | * (or break out if there are no more). |
310 | | */ |
311 | 0 | if (options_buf->pos < options_buf->len |
312 | 0 | && buf_getbyte(options_buf) != ',') { |
313 | 0 | goto bad_option; |
314 | 0 | } |
315 | | /* Process the next option. */ |
316 | 0 | } |
317 | | /* parsed all options with no problem */ |
318 | 0 | ret = DROPBEAR_SUCCESS; |
319 | 0 | goto end; |
320 | | |
321 | 0 | bad_option: |
322 | 0 | ret = DROPBEAR_FAILURE; |
323 | 0 | svr_pubkey_options_cleanup(); |
324 | 0 | dropbear_log(LOG_WARNING, "Bad public key options at %s:%d", filename, line_num); |
325 | |
|
326 | 0 | end: |
327 | 0 | TRACE(("leave addpubkeyoptions")) |
328 | 0 | return ret; |
329 | 0 | } |
330 | | |
331 | | #endif |