diff options
Diffstat (limited to 'auth-options.c')
-rw-r--r-- | auth-options.c | 1199 |
1 files changed, 704 insertions, 495 deletions
diff --git a/auth-options.c b/auth-options.c index bed00eef0..b528c197a 100644 --- a/auth-options.c +++ b/auth-options.c | |||
@@ -1,13 +1,18 @@ | |||
1 | /* $OpenBSD: auth-options.c,v 1.74 2017/09/12 06:32:07 djm Exp $ */ | 1 | /* $OpenBSD: auth-options.c,v 1.78 2018/03/14 05:35:40 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Copyright (c) 2018 Damien Miller <djm@mindrot.org> |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * |
5 | * All rights reserved | 5 | * Permission to use, copy, modify, and distribute this software for any |
6 | * As far as I am concerned, the code I have written for this software | 6 | * purpose with or without fee is hereby granted, provided that the above |
7 | * can be used freely for any purpose. Any derived versions of this | 7 | * copyright notice and this permission notice appear in all copies. |
8 | * software must be clearly marked as such, and if the derived work is | 8 | * |
9 | * incompatible with the protocol description in the RFC file, it must be | 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
10 | * called by a name other than "ssh" or "Secure Shell". | 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
11 | */ | 16 | */ |
12 | 17 | ||
13 | #include "includes.h" | 18 | #include "includes.h" |
@@ -19,88 +24,33 @@ | |||
19 | #include <string.h> | 24 | #include <string.h> |
20 | #include <stdio.h> | 25 | #include <stdio.h> |
21 | #include <stdarg.h> | 26 | #include <stdarg.h> |
27 | #include <ctype.h> | ||
28 | #include <limits.h> | ||
22 | 29 | ||
23 | #include "openbsd-compat/sys-queue.h" | 30 | #include "openbsd-compat/sys-queue.h" |
24 | 31 | ||
25 | #include "key.h" /* XXX for typedef */ | ||
26 | #include "buffer.h" /* XXX for typedef */ | ||
27 | #include "xmalloc.h" | 32 | #include "xmalloc.h" |
28 | #include "match.h" | ||
29 | #include "ssherr.h" | 33 | #include "ssherr.h" |
30 | #include "log.h" | 34 | #include "log.h" |
31 | #include "canohost.h" | ||
32 | #include "packet.h" | ||
33 | #include "sshbuf.h" | 35 | #include "sshbuf.h" |
34 | #include "misc.h" | 36 | #include "misc.h" |
35 | #include "channels.h" | ||
36 | #include "servconf.h" | ||
37 | #include "sshkey.h" | 37 | #include "sshkey.h" |
38 | #include "match.h" | ||
39 | #include "ssh2.h" | ||
38 | #include "auth-options.h" | 40 | #include "auth-options.h" |
39 | #include "hostfile.h" | ||
40 | #include "auth.h" | ||
41 | |||
42 | /* Flags set authorized_keys flags */ | ||
43 | int no_port_forwarding_flag = 0; | ||
44 | int no_agent_forwarding_flag = 0; | ||
45 | int no_x11_forwarding_flag = 0; | ||
46 | int no_pty_flag = 0; | ||
47 | int no_user_rc = 0; | ||
48 | int key_is_cert_authority = 0; | ||
49 | |||
50 | /* "command=" option. */ | ||
51 | char *forced_command = NULL; | ||
52 | |||
53 | /* "environment=" options. */ | ||
54 | struct envstring *custom_environment = NULL; | ||
55 | |||
56 | /* "tunnel=" option. */ | ||
57 | int forced_tun_device = -1; | ||
58 | |||
59 | /* "principals=" option. */ | ||
60 | char *authorized_principals = NULL; | ||
61 | |||
62 | extern ServerOptions options; | ||
63 | |||
64 | /* XXX refactor to be stateless */ | ||
65 | |||
66 | void | ||
67 | auth_clear_options(void) | ||
68 | { | ||
69 | struct ssh *ssh = active_state; /* XXX */ | ||
70 | |||
71 | no_agent_forwarding_flag = 0; | ||
72 | no_port_forwarding_flag = 0; | ||
73 | no_pty_flag = 0; | ||
74 | no_x11_forwarding_flag = 0; | ||
75 | no_user_rc = 0; | ||
76 | key_is_cert_authority = 0; | ||
77 | while (custom_environment) { | ||
78 | struct envstring *ce = custom_environment; | ||
79 | custom_environment = ce->next; | ||
80 | free(ce->s); | ||
81 | free(ce); | ||
82 | } | ||
83 | free(forced_command); | ||
84 | forced_command = NULL; | ||
85 | free(authorized_principals); | ||
86 | authorized_principals = NULL; | ||
87 | forced_tun_device = -1; | ||
88 | channel_clear_permitted_opens(ssh); | ||
89 | } | ||
90 | 41 | ||
91 | /* | 42 | /* |
92 | * Match flag 'opt' in *optsp, and if allow_negate is set then also match | 43 | * Match flag 'opt' in *optsp, and if allow_negate is set then also match |
93 | * 'no-opt'. Returns -1 if option not matched, 1 if option matches or 0 | 44 | * 'no-opt'. Returns -1 if option not matched, 1 if option matches or 0 |
94 | * if negated option matches. | 45 | * if negated option matches. |
95 | * If the option or negated option matches, then *optsp is updated to | 46 | * If the option or negated option matches, then *optsp is updated to |
96 | * point to the first character after the option and, if 'msg' is not NULL | 47 | * point to the first character after the option. |
97 | * then a message based on it added via auth_debug_add(). | ||
98 | */ | 48 | */ |
99 | static int | 49 | static int |
100 | match_flag(const char *opt, int allow_negate, char **optsp, const char *msg) | 50 | opt_flag(const char *opt, int allow_negate, const char **optsp) |
101 | { | 51 | { |
102 | size_t opt_len = strlen(opt); | 52 | size_t opt_len = strlen(opt); |
103 | char *opts = *optsp; | 53 | const char *opts = *optsp; |
104 | int negate = 0; | 54 | int negate = 0; |
105 | 55 | ||
106 | if (allow_negate && strncasecmp(opts, "no-", 3) == 0) { | 56 | if (allow_negate && strncasecmp(opts, "no-", 3) == 0) { |
@@ -109,368 +59,92 @@ match_flag(const char *opt, int allow_negate, char **optsp, const char *msg) | |||
109 | } | 59 | } |
110 | if (strncasecmp(opts, opt, opt_len) == 0) { | 60 | if (strncasecmp(opts, opt, opt_len) == 0) { |
111 | *optsp = opts + opt_len; | 61 | *optsp = opts + opt_len; |
112 | if (msg != NULL) { | ||
113 | auth_debug_add("%s %s.", msg, | ||
114 | negate ? "disabled" : "enabled"); | ||
115 | } | ||
116 | return negate ? 0 : 1; | 62 | return negate ? 0 : 1; |
117 | } | 63 | } |
118 | return -1; | 64 | return -1; |
119 | } | 65 | } |
120 | 66 | ||
121 | /* | 67 | static char * |
122 | * return 1 if access is granted, 0 if not. | 68 | opt_dequote(const char **sp, const char **errstrp) |
123 | * side effect: sets key option flags | ||
124 | * XXX remove side effects; fill structure instead. | ||
125 | */ | ||
126 | int | ||
127 | auth_parse_options(struct passwd *pw, char *opts, const char *file, | ||
128 | u_long linenum) | ||
129 | { | 69 | { |
130 | struct ssh *ssh = active_state; /* XXX */ | 70 | const char *s = *sp; |
131 | const char *cp; | 71 | char *ret; |
132 | int i, r; | 72 | size_t i; |
133 | 73 | ||
134 | /* reset options */ | 74 | *errstrp = NULL; |
135 | auth_clear_options(); | 75 | if (*s != '"') { |
76 | *errstrp = "missing start quote"; | ||
77 | return NULL; | ||
78 | } | ||
79 | s++; | ||
80 | if ((ret = malloc(strlen((s)) + 1)) == NULL) { | ||
81 | *errstrp = "memory allocation failed"; | ||
82 | return NULL; | ||
83 | } | ||
84 | for (i = 0; *s != '\0' && *s != '"';) { | ||
85 | if (s[0] == '\\' && s[1] == '"') | ||
86 | s++; | ||
87 | ret[i++] = *s++; | ||
88 | } | ||
89 | if (*s == '\0') { | ||
90 | *errstrp = "missing end quote"; | ||
91 | free(ret); | ||
92 | return NULL; | ||
93 | } | ||
94 | ret[i] = '\0'; | ||
95 | s++; | ||
96 | *sp = s; | ||
97 | return ret; | ||
98 | } | ||
136 | 99 | ||
137 | if (!opts) | 100 | static int |
101 | opt_match(const char **opts, const char *term) | ||
102 | { | ||
103 | if (strncasecmp((*opts), term, strlen(term)) == 0 && | ||
104 | (*opts)[strlen(term)] == '=') { | ||
105 | *opts += strlen(term) + 1; | ||
138 | return 1; | 106 | return 1; |
139 | |||
140 | while (*opts && *opts != ' ' && *opts != '\t') { | ||
141 | if ((r = match_flag("cert-authority", 0, &opts, NULL)) != -1) { | ||
142 | key_is_cert_authority = r; | ||
143 | goto next_option; | ||
144 | } | ||
145 | if ((r = match_flag("restrict", 0, &opts, NULL)) != -1) { | ||
146 | auth_debug_add("Key is restricted."); | ||
147 | no_port_forwarding_flag = 1; | ||
148 | no_agent_forwarding_flag = 1; | ||
149 | no_x11_forwarding_flag = 1; | ||
150 | no_pty_flag = 1; | ||
151 | no_user_rc = 1; | ||
152 | goto next_option; | ||
153 | } | ||
154 | if ((r = match_flag("port-forwarding", 1, &opts, | ||
155 | "Port forwarding")) != -1) { | ||
156 | no_port_forwarding_flag = r != 1; | ||
157 | goto next_option; | ||
158 | } | ||
159 | if ((r = match_flag("agent-forwarding", 1, &opts, | ||
160 | "Agent forwarding")) != -1) { | ||
161 | no_agent_forwarding_flag = r != 1; | ||
162 | goto next_option; | ||
163 | } | ||
164 | if ((r = match_flag("x11-forwarding", 1, &opts, | ||
165 | "X11 forwarding")) != -1) { | ||
166 | no_x11_forwarding_flag = r != 1; | ||
167 | goto next_option; | ||
168 | } | ||
169 | if ((r = match_flag("pty", 1, &opts, | ||
170 | "PTY allocation")) != -1) { | ||
171 | no_pty_flag = r != 1; | ||
172 | goto next_option; | ||
173 | } | ||
174 | if ((r = match_flag("user-rc", 1, &opts, | ||
175 | "User rc execution")) != -1) { | ||
176 | no_user_rc = r != 1; | ||
177 | goto next_option; | ||
178 | } | ||
179 | cp = "command=\""; | ||
180 | if (strncasecmp(opts, cp, strlen(cp)) == 0) { | ||
181 | opts += strlen(cp); | ||
182 | free(forced_command); | ||
183 | forced_command = xmalloc(strlen(opts) + 1); | ||
184 | i = 0; | ||
185 | while (*opts) { | ||
186 | if (*opts == '"') | ||
187 | break; | ||
188 | if (*opts == '\\' && opts[1] == '"') { | ||
189 | opts += 2; | ||
190 | forced_command[i++] = '"'; | ||
191 | continue; | ||
192 | } | ||
193 | forced_command[i++] = *opts++; | ||
194 | } | ||
195 | if (!*opts) { | ||
196 | debug("%.100s, line %lu: missing end quote", | ||
197 | file, linenum); | ||
198 | auth_debug_add("%.100s, line %lu: missing end quote", | ||
199 | file, linenum); | ||
200 | free(forced_command); | ||
201 | forced_command = NULL; | ||
202 | goto bad_option; | ||
203 | } | ||
204 | forced_command[i] = '\0'; | ||
205 | auth_debug_add("Forced command."); | ||
206 | opts++; | ||
207 | goto next_option; | ||
208 | } | ||
209 | cp = "principals=\""; | ||
210 | if (strncasecmp(opts, cp, strlen(cp)) == 0) { | ||
211 | opts += strlen(cp); | ||
212 | free(authorized_principals); | ||
213 | authorized_principals = xmalloc(strlen(opts) + 1); | ||
214 | i = 0; | ||
215 | while (*opts) { | ||
216 | if (*opts == '"') | ||
217 | break; | ||
218 | if (*opts == '\\' && opts[1] == '"') { | ||
219 | opts += 2; | ||
220 | authorized_principals[i++] = '"'; | ||
221 | continue; | ||
222 | } | ||
223 | authorized_principals[i++] = *opts++; | ||
224 | } | ||
225 | if (!*opts) { | ||
226 | debug("%.100s, line %lu: missing end quote", | ||
227 | file, linenum); | ||
228 | auth_debug_add("%.100s, line %lu: missing end quote", | ||
229 | file, linenum); | ||
230 | free(authorized_principals); | ||
231 | authorized_principals = NULL; | ||
232 | goto bad_option; | ||
233 | } | ||
234 | authorized_principals[i] = '\0'; | ||
235 | auth_debug_add("principals: %.900s", | ||
236 | authorized_principals); | ||
237 | opts++; | ||
238 | goto next_option; | ||
239 | } | ||
240 | cp = "environment=\""; | ||
241 | if (strncasecmp(opts, cp, strlen(cp)) == 0) { | ||
242 | char *s; | ||
243 | struct envstring *new_envstring; | ||
244 | |||
245 | opts += strlen(cp); | ||
246 | s = xmalloc(strlen(opts) + 1); | ||
247 | i = 0; | ||
248 | while (*opts) { | ||
249 | if (*opts == '"') | ||
250 | break; | ||
251 | if (*opts == '\\' && opts[1] == '"') { | ||
252 | opts += 2; | ||
253 | s[i++] = '"'; | ||
254 | continue; | ||
255 | } | ||
256 | s[i++] = *opts++; | ||
257 | } | ||
258 | if (!*opts) { | ||
259 | debug("%.100s, line %lu: missing end quote", | ||
260 | file, linenum); | ||
261 | auth_debug_add("%.100s, line %lu: missing end quote", | ||
262 | file, linenum); | ||
263 | free(s); | ||
264 | goto bad_option; | ||
265 | } | ||
266 | s[i] = '\0'; | ||
267 | opts++; | ||
268 | if (options.permit_user_env) { | ||
269 | auth_debug_add("Adding to environment: " | ||
270 | "%.900s", s); | ||
271 | debug("Adding to environment: %.900s", s); | ||
272 | new_envstring = xcalloc(1, | ||
273 | sizeof(*new_envstring)); | ||
274 | new_envstring->s = s; | ||
275 | new_envstring->next = custom_environment; | ||
276 | custom_environment = new_envstring; | ||
277 | s = NULL; | ||
278 | } | ||
279 | free(s); | ||
280 | goto next_option; | ||
281 | } | ||
282 | cp = "from=\""; | ||
283 | if (strncasecmp(opts, cp, strlen(cp)) == 0) { | ||
284 | const char *remote_ip = ssh_remote_ipaddr(ssh); | ||
285 | const char *remote_host = auth_get_canonical_hostname( | ||
286 | ssh, options.use_dns); | ||
287 | char *patterns = xmalloc(strlen(opts) + 1); | ||
288 | |||
289 | opts += strlen(cp); | ||
290 | i = 0; | ||
291 | while (*opts) { | ||
292 | if (*opts == '"') | ||
293 | break; | ||
294 | if (*opts == '\\' && opts[1] == '"') { | ||
295 | opts += 2; | ||
296 | patterns[i++] = '"'; | ||
297 | continue; | ||
298 | } | ||
299 | patterns[i++] = *opts++; | ||
300 | } | ||
301 | if (!*opts) { | ||
302 | debug("%.100s, line %lu: missing end quote", | ||
303 | file, linenum); | ||
304 | auth_debug_add("%.100s, line %lu: missing end quote", | ||
305 | file, linenum); | ||
306 | free(patterns); | ||
307 | goto bad_option; | ||
308 | } | ||
309 | patterns[i] = '\0'; | ||
310 | opts++; | ||
311 | switch (match_host_and_ip(remote_host, remote_ip, | ||
312 | patterns)) { | ||
313 | case 1: | ||
314 | free(patterns); | ||
315 | /* Host name matches. */ | ||
316 | goto next_option; | ||
317 | case -1: | ||
318 | debug("%.100s, line %lu: invalid criteria", | ||
319 | file, linenum); | ||
320 | auth_debug_add("%.100s, line %lu: " | ||
321 | "invalid criteria", file, linenum); | ||
322 | /* FALLTHROUGH */ | ||
323 | case 0: | ||
324 | free(patterns); | ||
325 | logit("Authentication tried for %.100s with " | ||
326 | "correct key but not from a permitted " | ||
327 | "host (host=%.200s, ip=%.200s).", | ||
328 | pw->pw_name, remote_host, remote_ip); | ||
329 | auth_debug_add("Your host '%.200s' is not " | ||
330 | "permitted to use this key for login.", | ||
331 | remote_host); | ||
332 | break; | ||
333 | } | ||
334 | /* deny access */ | ||
335 | return 0; | ||
336 | } | ||
337 | cp = "permitopen=\""; | ||
338 | if (strncasecmp(opts, cp, strlen(cp)) == 0) { | ||
339 | char *host, *p; | ||
340 | int port; | ||
341 | char *patterns = xmalloc(strlen(opts) + 1); | ||
342 | |||
343 | opts += strlen(cp); | ||
344 | i = 0; | ||
345 | while (*opts) { | ||
346 | if (*opts == '"') | ||
347 | break; | ||
348 | if (*opts == '\\' && opts[1] == '"') { | ||
349 | opts += 2; | ||
350 | patterns[i++] = '"'; | ||
351 | continue; | ||
352 | } | ||
353 | patterns[i++] = *opts++; | ||
354 | } | ||
355 | if (!*opts) { | ||
356 | debug("%.100s, line %lu: missing end quote", | ||
357 | file, linenum); | ||
358 | auth_debug_add("%.100s, line %lu: missing " | ||
359 | "end quote", file, linenum); | ||
360 | free(patterns); | ||
361 | goto bad_option; | ||
362 | } | ||
363 | patterns[i] = '\0'; | ||
364 | opts++; | ||
365 | p = patterns; | ||
366 | /* XXX - add streamlocal support */ | ||
367 | host = hpdelim(&p); | ||
368 | if (host == NULL || strlen(host) >= NI_MAXHOST) { | ||
369 | debug("%.100s, line %lu: Bad permitopen " | ||
370 | "specification <%.100s>", file, linenum, | ||
371 | patterns); | ||
372 | auth_debug_add("%.100s, line %lu: " | ||
373 | "Bad permitopen specification", file, | ||
374 | linenum); | ||
375 | free(patterns); | ||
376 | goto bad_option; | ||
377 | } | ||
378 | host = cleanhostname(host); | ||
379 | if (p == NULL || (port = permitopen_port(p)) < 0) { | ||
380 | debug("%.100s, line %lu: Bad permitopen port " | ||
381 | "<%.100s>", file, linenum, p ? p : ""); | ||
382 | auth_debug_add("%.100s, line %lu: " | ||
383 | "Bad permitopen port", file, linenum); | ||
384 | free(patterns); | ||
385 | goto bad_option; | ||
386 | } | ||
387 | if ((options.allow_tcp_forwarding & FORWARD_LOCAL) != 0) | ||
388 | channel_add_permitted_opens(ssh, host, port); | ||
389 | free(patterns); | ||
390 | goto next_option; | ||
391 | } | ||
392 | cp = "tunnel=\""; | ||
393 | if (strncasecmp(opts, cp, strlen(cp)) == 0) { | ||
394 | char *tun = NULL; | ||
395 | opts += strlen(cp); | ||
396 | tun = xmalloc(strlen(opts) + 1); | ||
397 | i = 0; | ||
398 | while (*opts) { | ||
399 | if (*opts == '"') | ||
400 | break; | ||
401 | tun[i++] = *opts++; | ||
402 | } | ||
403 | if (!*opts) { | ||
404 | debug("%.100s, line %lu: missing end quote", | ||
405 | file, linenum); | ||
406 | auth_debug_add("%.100s, line %lu: missing end quote", | ||
407 | file, linenum); | ||
408 | free(tun); | ||
409 | forced_tun_device = -1; | ||
410 | goto bad_option; | ||
411 | } | ||
412 | tun[i] = '\0'; | ||
413 | forced_tun_device = a2tun(tun, NULL); | ||
414 | free(tun); | ||
415 | if (forced_tun_device == SSH_TUNID_ERR) { | ||
416 | debug("%.100s, line %lu: invalid tun device", | ||
417 | file, linenum); | ||
418 | auth_debug_add("%.100s, line %lu: invalid tun device", | ||
419 | file, linenum); | ||
420 | forced_tun_device = -1; | ||
421 | goto bad_option; | ||
422 | } | ||
423 | auth_debug_add("Forced tun device: %d", forced_tun_device); | ||
424 | opts++; | ||
425 | goto next_option; | ||
426 | } | ||
427 | next_option: | ||
428 | /* | ||
429 | * Skip the comma, and move to the next option | ||
430 | * (or break out if there are no more). | ||
431 | */ | ||
432 | if (!*opts) | ||
433 | fatal("Bugs in auth-options.c option processing."); | ||
434 | if (*opts == ' ' || *opts == '\t') | ||
435 | break; /* End of options. */ | ||
436 | if (*opts != ',') | ||
437 | goto bad_option; | ||
438 | opts++; | ||
439 | /* Process the next option. */ | ||
440 | } | 107 | } |
108 | return 0; | ||
109 | } | ||
441 | 110 | ||
442 | /* grant access */ | 111 | static int |
443 | return 1; | 112 | dup_strings(char ***dstp, size_t *ndstp, char **src, size_t nsrc) |
113 | { | ||
114 | char **dst; | ||
115 | size_t i, j; | ||
444 | 116 | ||
445 | bad_option: | 117 | *dstp = NULL; |
446 | logit("Bad options in %.100s file, line %lu: %.50s", | 118 | *ndstp = 0; |
447 | file, linenum, opts); | 119 | if (nsrc == 0) |
448 | auth_debug_add("Bad options in %.100s file, line %lu: %.50s", | 120 | return 0; |
449 | file, linenum, opts); | ||
450 | 121 | ||
451 | /* deny access */ | 122 | if ((dst = calloc(nsrc, sizeof(*src))) == NULL) |
123 | return -1; | ||
124 | for (i = 0; i < nsrc; i++) { | ||
125 | if ((dst[i] = strdup(src[i])) == NULL) { | ||
126 | for (j = 0; j < i; j++) | ||
127 | free(dst[j]); | ||
128 | free(dst); | ||
129 | return -1; | ||
130 | } | ||
131 | } | ||
132 | /* success */ | ||
133 | *dstp = dst; | ||
134 | *ndstp = nsrc; | ||
452 | return 0; | 135 | return 0; |
453 | } | 136 | } |
454 | 137 | ||
455 | #define OPTIONS_CRITICAL 1 | 138 | #define OPTIONS_CRITICAL 1 |
456 | #define OPTIONS_EXTENSIONS 2 | 139 | #define OPTIONS_EXTENSIONS 2 |
457 | static int | 140 | static int |
458 | parse_option_list(struct sshbuf *oblob, struct passwd *pw, | 141 | cert_option_list(struct sshauthopt *opts, struct sshbuf *oblob, |
459 | u_int which, int crit, | 142 | u_int which, int crit) |
460 | int *cert_no_port_forwarding_flag, | ||
461 | int *cert_no_agent_forwarding_flag, | ||
462 | int *cert_no_x11_forwarding_flag, | ||
463 | int *cert_no_pty_flag, | ||
464 | int *cert_no_user_rc, | ||
465 | char **cert_forced_command, | ||
466 | int *cert_source_address_done) | ||
467 | { | 143 | { |
468 | struct ssh *ssh = active_state; /* XXX */ | ||
469 | char *command, *allowed; | 144 | char *command, *allowed; |
470 | const char *remote_ip; | ||
471 | char *name = NULL; | 145 | char *name = NULL; |
472 | struct sshbuf *c = NULL, *data = NULL; | 146 | struct sshbuf *c = NULL, *data = NULL; |
473 | int r, ret = -1, result, found; | 147 | int r, ret = -1, found; |
474 | 148 | ||
475 | if ((c = sshbuf_fromb(oblob)) == NULL) { | 149 | if ((c = sshbuf_fromb(oblob)) == NULL) { |
476 | error("%s: sshbuf_fromb failed", __func__); | 150 | error("%s: sshbuf_fromb failed", __func__); |
@@ -491,21 +165,21 @@ parse_option_list(struct sshbuf *oblob, struct passwd *pw, | |||
491 | found = 0; | 165 | found = 0; |
492 | if ((which & OPTIONS_EXTENSIONS) != 0) { | 166 | if ((which & OPTIONS_EXTENSIONS) != 0) { |
493 | if (strcmp(name, "permit-X11-forwarding") == 0) { | 167 | if (strcmp(name, "permit-X11-forwarding") == 0) { |
494 | *cert_no_x11_forwarding_flag = 0; | 168 | opts->permit_x11_forwarding_flag = 1; |
495 | found = 1; | 169 | found = 1; |
496 | } else if (strcmp(name, | 170 | } else if (strcmp(name, |
497 | "permit-agent-forwarding") == 0) { | 171 | "permit-agent-forwarding") == 0) { |
498 | *cert_no_agent_forwarding_flag = 0; | 172 | opts->permit_agent_forwarding_flag = 1; |
499 | found = 1; | 173 | found = 1; |
500 | } else if (strcmp(name, | 174 | } else if (strcmp(name, |
501 | "permit-port-forwarding") == 0) { | 175 | "permit-port-forwarding") == 0) { |
502 | *cert_no_port_forwarding_flag = 0; | 176 | opts->permit_port_forwarding_flag = 1; |
503 | found = 1; | 177 | found = 1; |
504 | } else if (strcmp(name, "permit-pty") == 0) { | 178 | } else if (strcmp(name, "permit-pty") == 0) { |
505 | *cert_no_pty_flag = 0; | 179 | opts->permit_pty_flag = 1; |
506 | found = 1; | 180 | found = 1; |
507 | } else if (strcmp(name, "permit-user-rc") == 0) { | 181 | } else if (strcmp(name, "permit-user-rc") == 0) { |
508 | *cert_no_user_rc = 0; | 182 | opts->permit_user_rc = 1; |
509 | found = 1; | 183 | found = 1; |
510 | } | 184 | } |
511 | } | 185 | } |
@@ -517,13 +191,13 @@ parse_option_list(struct sshbuf *oblob, struct passwd *pw, | |||
517 | "section: %s", name, ssh_err(r)); | 191 | "section: %s", name, ssh_err(r)); |
518 | goto out; | 192 | goto out; |
519 | } | 193 | } |
520 | if (*cert_forced_command != NULL) { | 194 | if (opts->force_command != NULL) { |
521 | error("Certificate has multiple " | 195 | error("Certificate has multiple " |
522 | "force-command options"); | 196 | "force-command options"); |
523 | free(command); | 197 | free(command); |
524 | goto out; | 198 | goto out; |
525 | } | 199 | } |
526 | *cert_forced_command = command; | 200 | opts->force_command = command; |
527 | found = 1; | 201 | found = 1; |
528 | } | 202 | } |
529 | if (strcmp(name, "source-address") == 0) { | 203 | if (strcmp(name, "source-address") == 0) { |
@@ -533,38 +207,19 @@ parse_option_list(struct sshbuf *oblob, struct passwd *pw, | |||
533 | "section: %s", name, ssh_err(r)); | 207 | "section: %s", name, ssh_err(r)); |
534 | goto out; | 208 | goto out; |
535 | } | 209 | } |
536 | if ((*cert_source_address_done)++) { | 210 | if (opts->required_from_host_cert != NULL) { |
537 | error("Certificate has multiple " | 211 | error("Certificate has multiple " |
538 | "source-address options"); | 212 | "source-address options"); |
539 | free(allowed); | 213 | free(allowed); |
540 | goto out; | 214 | goto out; |
541 | } | 215 | } |
542 | remote_ip = ssh_remote_ipaddr(ssh); | 216 | /* Check syntax */ |
543 | result = addr_match_cidr_list(remote_ip, | 217 | if (addr_match_cidr_list(NULL, allowed) == -1) { |
544 | allowed); | ||
545 | free(allowed); | ||
546 | switch (result) { | ||
547 | case 1: | ||
548 | /* accepted */ | ||
549 | break; | ||
550 | case 0: | ||
551 | /* no match */ | ||
552 | logit("Authentication tried for %.100s " | ||
553 | "with valid certificate but not " | ||
554 | "from a permitted host " | ||
555 | "(ip=%.200s).", pw->pw_name, | ||
556 | remote_ip); | ||
557 | auth_debug_add("Your address '%.200s' " | ||
558 | "is not permitted to use this " | ||
559 | "certificate for login.", | ||
560 | remote_ip); | ||
561 | goto out; | ||
562 | case -1: | ||
563 | default: | ||
564 | error("Certificate source-address " | 218 | error("Certificate source-address " |
565 | "contents invalid"); | 219 | "contents invalid"); |
566 | goto out; | 220 | goto out; |
567 | } | 221 | } |
222 | opts->required_from_host_cert = allowed; | ||
568 | found = 1; | 223 | found = 1; |
569 | } | 224 | } |
570 | } | 225 | } |
@@ -590,74 +245,628 @@ parse_option_list(struct sshbuf *oblob, struct passwd *pw, | |||
590 | ret = 0; | 245 | ret = 0; |
591 | 246 | ||
592 | out: | 247 | out: |
593 | if (ret != 0 && | ||
594 | cert_forced_command != NULL && | ||
595 | *cert_forced_command != NULL) { | ||
596 | free(*cert_forced_command); | ||
597 | *cert_forced_command = NULL; | ||
598 | } | ||
599 | free(name); | 248 | free(name); |
600 | sshbuf_free(data); | 249 | sshbuf_free(data); |
601 | sshbuf_free(c); | 250 | sshbuf_free(c); |
602 | return ret; | 251 | return ret; |
603 | } | 252 | } |
604 | 253 | ||
254 | struct sshauthopt * | ||
255 | sshauthopt_new(void) | ||
256 | { | ||
257 | struct sshauthopt *ret; | ||
258 | |||
259 | if ((ret = calloc(1, sizeof(*ret))) == NULL) | ||
260 | return NULL; | ||
261 | ret->force_tun_device = -1; | ||
262 | return ret; | ||
263 | } | ||
264 | |||
265 | void | ||
266 | sshauthopt_free(struct sshauthopt *opts) | ||
267 | { | ||
268 | size_t i; | ||
269 | |||
270 | if (opts == NULL) | ||
271 | return; | ||
272 | |||
273 | free(opts->cert_principals); | ||
274 | free(opts->force_command); | ||
275 | free(opts->required_from_host_cert); | ||
276 | free(opts->required_from_host_keys); | ||
277 | |||
278 | for (i = 0; i < opts->nenv; i++) | ||
279 | free(opts->env[i]); | ||
280 | free(opts->env); | ||
281 | |||
282 | for (i = 0; i < opts->npermitopen; i++) | ||
283 | free(opts->permitopen[i]); | ||
284 | free(opts->permitopen); | ||
285 | |||
286 | explicit_bzero(opts, sizeof(*opts)); | ||
287 | free(opts); | ||
288 | } | ||
289 | |||
290 | struct sshauthopt * | ||
291 | sshauthopt_new_with_keys_defaults(void) | ||
292 | { | ||
293 | struct sshauthopt *ret = NULL; | ||
294 | |||
295 | if ((ret = sshauthopt_new()) == NULL) | ||
296 | return NULL; | ||
297 | |||
298 | /* Defaults for authorized_keys flags */ | ||
299 | ret->permit_port_forwarding_flag = 1; | ||
300 | ret->permit_agent_forwarding_flag = 1; | ||
301 | ret->permit_x11_forwarding_flag = 1; | ||
302 | ret->permit_pty_flag = 1; | ||
303 | ret->permit_user_rc = 1; | ||
304 | return ret; | ||
305 | } | ||
306 | |||
307 | struct sshauthopt * | ||
308 | sshauthopt_parse(const char *opts, const char **errstrp) | ||
309 | { | ||
310 | char **oarray, *opt, *cp, *tmp, *host; | ||
311 | int r; | ||
312 | struct sshauthopt *ret = NULL; | ||
313 | const char *errstr = "unknown error"; | ||
314 | uint64_t valid_before; | ||
315 | |||
316 | if (errstrp != NULL) | ||
317 | *errstrp = NULL; | ||
318 | if ((ret = sshauthopt_new_with_keys_defaults()) == NULL) | ||
319 | goto alloc_fail; | ||
320 | |||
321 | if (opts == NULL) | ||
322 | return ret; | ||
323 | |||
324 | while (*opts && *opts != ' ' && *opts != '\t') { | ||
325 | /* flag options */ | ||
326 | if ((r = opt_flag("restrict", 0, &opts)) != -1) { | ||
327 | ret->restricted = 1; | ||
328 | ret->permit_port_forwarding_flag = 0; | ||
329 | ret->permit_agent_forwarding_flag = 0; | ||
330 | ret->permit_x11_forwarding_flag = 0; | ||
331 | ret->permit_pty_flag = 0; | ||
332 | ret->permit_user_rc = 0; | ||
333 | } else if ((r = opt_flag("cert-authority", 0, &opts)) != -1) { | ||
334 | ret->cert_authority = r; | ||
335 | } else if ((r = opt_flag("port-forwarding", 1, &opts)) != -1) { | ||
336 | ret->permit_port_forwarding_flag = r == 1; | ||
337 | } else if ((r = opt_flag("agent-forwarding", 1, &opts)) != -1) { | ||
338 | ret->permit_agent_forwarding_flag = r == 1; | ||
339 | } else if ((r = opt_flag("x11-forwarding", 1, &opts)) != -1) { | ||
340 | ret->permit_x11_forwarding_flag = r == 1; | ||
341 | } else if ((r = opt_flag("pty", 1, &opts)) != -1) { | ||
342 | ret->permit_pty_flag = r == 1; | ||
343 | } else if ((r = opt_flag("user-rc", 1, &opts)) != -1) { | ||
344 | ret->permit_user_rc = r == 1; | ||
345 | } else if (opt_match(&opts, "command")) { | ||
346 | if (ret->force_command != NULL) { | ||
347 | errstr = "multiple \"command\" clauses"; | ||
348 | goto fail; | ||
349 | } | ||
350 | ret->force_command = opt_dequote(&opts, &errstr); | ||
351 | if (ret->force_command == NULL) | ||
352 | goto fail; | ||
353 | } else if (opt_match(&opts, "principals")) { | ||
354 | if (ret->cert_principals != NULL) { | ||
355 | errstr = "multiple \"principals\" clauses"; | ||
356 | goto fail; | ||
357 | } | ||
358 | ret->cert_principals = opt_dequote(&opts, &errstr); | ||
359 | if (ret->cert_principals == NULL) | ||
360 | goto fail; | ||
361 | } else if (opt_match(&opts, "from")) { | ||
362 | if (ret->required_from_host_keys != NULL) { | ||
363 | errstr = "multiple \"from\" clauses"; | ||
364 | goto fail; | ||
365 | } | ||
366 | ret->required_from_host_keys = opt_dequote(&opts, | ||
367 | &errstr); | ||
368 | if (ret->required_from_host_keys == NULL) | ||
369 | goto fail; | ||
370 | } else if (opt_match(&opts, "expiry-time")) { | ||
371 | if ((opt = opt_dequote(&opts, &errstr)) == NULL) | ||
372 | goto fail; | ||
373 | if (parse_absolute_time(opt, &valid_before) != 0 || | ||
374 | valid_before == 0) { | ||
375 | free(opt); | ||
376 | errstr = "invalid expires time"; | ||
377 | goto fail; | ||
378 | } | ||
379 | free(opt); | ||
380 | if (ret->valid_before == 0 || | ||
381 | valid_before < ret->valid_before) | ||
382 | ret->valid_before = valid_before; | ||
383 | } else if (opt_match(&opts, "environment")) { | ||
384 | if (ret->nenv > INT_MAX) { | ||
385 | errstr = "too many environment strings"; | ||
386 | goto fail; | ||
387 | } | ||
388 | if ((opt = opt_dequote(&opts, &errstr)) == NULL) | ||
389 | goto fail; | ||
390 | /* env name must be alphanumeric and followed by '=' */ | ||
391 | if ((tmp = strchr(opt, '=')) == NULL) { | ||
392 | free(opt); | ||
393 | errstr = "invalid environment string"; | ||
394 | goto fail; | ||
395 | } | ||
396 | for (cp = opt; cp < tmp; cp++) { | ||
397 | if (!isalnum((u_char)*cp)) { | ||
398 | free(opt); | ||
399 | errstr = "invalid environment string"; | ||
400 | goto fail; | ||
401 | } | ||
402 | } | ||
403 | /* Append it. */ | ||
404 | oarray = ret->env; | ||
405 | if ((ret->env = recallocarray(ret->env, ret->nenv, | ||
406 | ret->nenv + 1, sizeof(*ret->env))) == NULL) { | ||
407 | free(opt); | ||
408 | ret->env = oarray; /* put it back for cleanup */ | ||
409 | goto alloc_fail; | ||
410 | } | ||
411 | ret->env[ret->nenv++] = opt; | ||
412 | } else if (opt_match(&opts, "permitopen")) { | ||
413 | if (ret->npermitopen > INT_MAX) { | ||
414 | errstr = "too many permitopens"; | ||
415 | goto fail; | ||
416 | } | ||
417 | if ((opt = opt_dequote(&opts, &errstr)) == NULL) | ||
418 | goto fail; | ||
419 | if ((tmp = strdup(opt)) == NULL) { | ||
420 | free(opt); | ||
421 | goto alloc_fail; | ||
422 | } | ||
423 | cp = tmp; | ||
424 | /* validate syntax of permitopen before recording it. */ | ||
425 | host = hpdelim(&cp); | ||
426 | if (host == NULL || strlen(host) >= NI_MAXHOST) { | ||
427 | free(tmp); | ||
428 | free(opt); | ||
429 | errstr = "invalid permitopen hostname"; | ||
430 | goto fail; | ||
431 | } | ||
432 | /* | ||
433 | * don't want to use permitopen_port to avoid | ||
434 | * dependency on channels.[ch] here. | ||
435 | */ | ||
436 | if (cp == NULL || | ||
437 | (strcmp(cp, "*") != 0 && a2port(cp) <= 0)) { | ||
438 | free(tmp); | ||
439 | free(opt); | ||
440 | errstr = "invalid permitopen port"; | ||
441 | goto fail; | ||
442 | } | ||
443 | /* XXX - add streamlocal support */ | ||
444 | free(tmp); | ||
445 | /* Record it */ | ||
446 | oarray = ret->permitopen; | ||
447 | if ((ret->permitopen = recallocarray(ret->permitopen, | ||
448 | ret->npermitopen, ret->npermitopen + 1, | ||
449 | sizeof(*ret->permitopen))) == NULL) { | ||
450 | free(opt); | ||
451 | ret->permitopen = oarray; | ||
452 | goto alloc_fail; | ||
453 | } | ||
454 | ret->permitopen[ret->npermitopen++] = opt; | ||
455 | } else if (opt_match(&opts, "tunnel")) { | ||
456 | if ((opt = opt_dequote(&opts, &errstr)) == NULL) | ||
457 | goto fail; | ||
458 | ret->force_tun_device = a2tun(opt, NULL); | ||
459 | free(opt); | ||
460 | if (ret->force_tun_device == SSH_TUNID_ERR) { | ||
461 | errstr = "invalid tun device"; | ||
462 | goto fail; | ||
463 | } | ||
464 | } | ||
465 | /* | ||
466 | * Skip the comma, and move to the next option | ||
467 | * (or break out if there are no more). | ||
468 | */ | ||
469 | if (*opts == '\0' || *opts == ' ' || *opts == '\t') | ||
470 | break; /* End of options. */ | ||
471 | /* Anything other than a comma is an unknown option */ | ||
472 | if (*opts != ',') { | ||
473 | errstr = "unknown key option"; | ||
474 | goto fail; | ||
475 | } | ||
476 | opts++; | ||
477 | if (*opts == '\0') { | ||
478 | errstr = "unexpected end-of-options"; | ||
479 | goto fail; | ||
480 | } | ||
481 | } | ||
482 | |||
483 | /* success */ | ||
484 | if (errstrp != NULL) | ||
485 | *errstrp = NULL; | ||
486 | return ret; | ||
487 | |||
488 | alloc_fail: | ||
489 | errstr = "memory allocation failed"; | ||
490 | fail: | ||
491 | sshauthopt_free(ret); | ||
492 | if (errstrp != NULL) | ||
493 | *errstrp = errstr; | ||
494 | return NULL; | ||
495 | } | ||
496 | |||
497 | struct sshauthopt * | ||
498 | sshauthopt_from_cert(struct sshkey *k) | ||
499 | { | ||
500 | struct sshauthopt *ret; | ||
501 | |||
502 | if (k == NULL || !sshkey_type_is_cert(k->type) || k->cert == NULL || | ||
503 | k->cert->type != SSH2_CERT_TYPE_USER) | ||
504 | return NULL; | ||
505 | |||
506 | if ((ret = sshauthopt_new()) == NULL) | ||
507 | return NULL; | ||
508 | |||
509 | /* Handle options and critical extensions separately */ | ||
510 | if (cert_option_list(ret, k->cert->critical, | ||
511 | OPTIONS_CRITICAL, 1) == -1) { | ||
512 | sshauthopt_free(ret); | ||
513 | return NULL; | ||
514 | } | ||
515 | if (cert_option_list(ret, k->cert->extensions, | ||
516 | OPTIONS_EXTENSIONS, 0) == -1) { | ||
517 | sshauthopt_free(ret); | ||
518 | return NULL; | ||
519 | } | ||
520 | /* success */ | ||
521 | return ret; | ||
522 | } | ||
523 | |||
605 | /* | 524 | /* |
606 | * Set options from critical certificate options. These supersede user key | 525 | * Merges "additional" options to "primary" and returns the result. |
607 | * options so this must be called after auth_parse_options(). | 526 | * NB. Some options from primary have primacy. |
608 | */ | 527 | */ |
609 | int | 528 | struct sshauthopt * |
610 | auth_cert_options(struct sshkey *k, struct passwd *pw, const char **reason) | 529 | sshauthopt_merge(const struct sshauthopt *primary, |
530 | const struct sshauthopt *additional, const char **errstrp) | ||
611 | { | 531 | { |
612 | int cert_no_port_forwarding_flag = 1; | 532 | struct sshauthopt *ret; |
613 | int cert_no_agent_forwarding_flag = 1; | 533 | const char *errstr = "internal error"; |
614 | int cert_no_x11_forwarding_flag = 1; | 534 | const char *tmp; |
615 | int cert_no_pty_flag = 1; | 535 | |
616 | int cert_no_user_rc = 1; | 536 | if (errstrp != NULL) |
617 | char *cert_forced_command = NULL; | 537 | *errstrp = NULL; |
618 | int cert_source_address_done = 0; | 538 | |
619 | 539 | if ((ret = sshauthopt_new()) == NULL) | |
620 | *reason = "invalid certificate options"; | 540 | goto alloc_fail; |
621 | 541 | ||
622 | /* Separate options and extensions for v01 certs */ | 542 | /* cert_authority and cert_principals are cleared in result */ |
623 | if (parse_option_list(k->cert->critical, pw, | 543 | |
624 | OPTIONS_CRITICAL, 1, NULL, NULL, NULL, NULL, NULL, | 544 | /* Prefer access lists from primary. */ |
625 | &cert_forced_command, | 545 | /* XXX err is both set and mismatch? */ |
626 | &cert_source_address_done) == -1) | 546 | tmp = primary->required_from_host_cert; |
627 | return -1; | 547 | if (tmp == NULL) |
628 | if (parse_option_list(k->cert->extensions, pw, | 548 | tmp = additional->required_from_host_cert; |
629 | OPTIONS_EXTENSIONS, 0, | 549 | if (tmp != NULL && (ret->required_from_host_cert = strdup(tmp)) == NULL) |
630 | &cert_no_port_forwarding_flag, | 550 | goto alloc_fail; |
631 | &cert_no_agent_forwarding_flag, | 551 | tmp = primary->required_from_host_keys; |
632 | &cert_no_x11_forwarding_flag, | 552 | if (tmp == NULL) |
633 | &cert_no_pty_flag, | 553 | tmp = additional->required_from_host_keys; |
634 | &cert_no_user_rc, | 554 | if (tmp != NULL && (ret->required_from_host_keys = strdup(tmp)) == NULL) |
635 | NULL, NULL) == -1) | 555 | goto alloc_fail; |
636 | return -1; | 556 | |
557 | /* force_tun_device, permitopen and environment prefer the primary. */ | ||
558 | ret->force_tun_device = primary->force_tun_device; | ||
559 | if (ret->force_tun_device == -1) | ||
560 | ret->force_tun_device = additional->force_tun_device; | ||
561 | if (primary->nenv > 0) { | ||
562 | if (dup_strings(&ret->env, &ret->nenv, | ||
563 | primary->env, primary->nenv) != 0) | ||
564 | goto alloc_fail; | ||
565 | } else if (additional->nenv) { | ||
566 | if (dup_strings(&ret->env, &ret->nenv, | ||
567 | additional->env, additional->nenv) != 0) | ||
568 | goto alloc_fail; | ||
569 | } | ||
570 | if (primary->npermitopen > 0) { | ||
571 | if (dup_strings(&ret->permitopen, &ret->npermitopen, | ||
572 | primary->permitopen, primary->npermitopen) != 0) | ||
573 | goto alloc_fail; | ||
574 | } else if (additional->npermitopen > 0) { | ||
575 | if (dup_strings(&ret->permitopen, &ret->npermitopen, | ||
576 | additional->permitopen, additional->npermitopen) != 0) | ||
577 | goto alloc_fail; | ||
578 | } | ||
579 | |||
580 | /* Flags are logical-AND (i.e. must be set in both for permission) */ | ||
581 | #define OPTFLAG(x) ret->x = (primary->x == 1) && (additional->x == 1) | ||
582 | OPTFLAG(permit_port_forwarding_flag); | ||
583 | OPTFLAG(permit_agent_forwarding_flag); | ||
584 | OPTFLAG(permit_x11_forwarding_flag); | ||
585 | OPTFLAG(permit_pty_flag); | ||
586 | OPTFLAG(permit_user_rc); | ||
587 | #undef OPTFLAG | ||
588 | |||
589 | /* Earliest expiry time should win */ | ||
590 | if (primary->valid_before != 0) | ||
591 | ret->valid_before = primary->valid_before; | ||
592 | if (additional->valid_before != 0 && | ||
593 | additional->valid_before < ret->valid_before) | ||
594 | ret->valid_before = additional->valid_before; | ||
637 | 595 | ||
638 | no_port_forwarding_flag |= cert_no_port_forwarding_flag; | ||
639 | no_agent_forwarding_flag |= cert_no_agent_forwarding_flag; | ||
640 | no_x11_forwarding_flag |= cert_no_x11_forwarding_flag; | ||
641 | no_pty_flag |= cert_no_pty_flag; | ||
642 | no_user_rc |= cert_no_user_rc; | ||
643 | /* | 596 | /* |
644 | * Only permit both CA and key option forced-command if they match. | 597 | * When both multiple forced-command are specified, only |
645 | * Otherwise refuse the certificate. | 598 | * proceed if they are identical, otherwise fail. |
646 | */ | 599 | */ |
647 | if (cert_forced_command != NULL && forced_command != NULL) { | 600 | if (primary->force_command != NULL && |
648 | if (strcmp(forced_command, cert_forced_command) == 0) { | 601 | additional->force_command != NULL) { |
649 | free(forced_command); | 602 | if (strcmp(primary->force_command, |
650 | forced_command = cert_forced_command; | 603 | additional->force_command) == 0) { |
604 | /* ok */ | ||
605 | ret->force_command = strdup(primary->force_command); | ||
606 | if (ret->force_command == NULL) | ||
607 | goto alloc_fail; | ||
651 | } else { | 608 | } else { |
652 | *reason = "certificate and key options forced command " | 609 | errstr = "forced command options do not match"; |
653 | "do not match"; | 610 | goto fail; |
654 | free(cert_forced_command); | ||
655 | return -1; | ||
656 | } | 611 | } |
657 | } else if (cert_forced_command != NULL) | 612 | } else if (primary->force_command != NULL) { |
658 | forced_command = cert_forced_command; | 613 | if ((ret->force_command = strdup( |
614 | primary->force_command)) == NULL) | ||
615 | goto alloc_fail; | ||
616 | } else if (additional->force_command != NULL) { | ||
617 | if ((ret->force_command = strdup( | ||
618 | additional->force_command)) == NULL) | ||
619 | goto alloc_fail; | ||
620 | } | ||
621 | /* success */ | ||
622 | if (errstrp != NULL) | ||
623 | *errstrp = NULL; | ||
624 | return ret; | ||
625 | |||
626 | alloc_fail: | ||
627 | errstr = "memory allocation failed"; | ||
628 | fail: | ||
629 | if (errstrp != NULL) | ||
630 | *errstrp = errstr; | ||
631 | sshauthopt_free(ret); | ||
632 | return NULL; | ||
633 | } | ||
634 | |||
635 | /* | ||
636 | * Copy options | ||
637 | */ | ||
638 | struct sshauthopt * | ||
639 | sshauthopt_copy(const struct sshauthopt *orig) | ||
640 | { | ||
641 | struct sshauthopt *ret; | ||
642 | |||
643 | if ((ret = sshauthopt_new()) == NULL) | ||
644 | return NULL; | ||
645 | |||
646 | #define OPTSCALAR(x) ret->x = orig->x | ||
647 | OPTSCALAR(permit_port_forwarding_flag); | ||
648 | OPTSCALAR(permit_agent_forwarding_flag); | ||
649 | OPTSCALAR(permit_x11_forwarding_flag); | ||
650 | OPTSCALAR(permit_pty_flag); | ||
651 | OPTSCALAR(permit_user_rc); | ||
652 | OPTSCALAR(restricted); | ||
653 | OPTSCALAR(cert_authority); | ||
654 | OPTSCALAR(force_tun_device); | ||
655 | OPTSCALAR(valid_before); | ||
656 | #undef OPTSCALAR | ||
657 | #define OPTSTRING(x) \ | ||
658 | do { \ | ||
659 | if (orig->x != NULL && (ret->x = strdup(orig->x)) == NULL) { \ | ||
660 | sshauthopt_free(ret); \ | ||
661 | return NULL; \ | ||
662 | } \ | ||
663 | } while (0) | ||
664 | OPTSTRING(cert_principals); | ||
665 | OPTSTRING(force_command); | ||
666 | OPTSTRING(required_from_host_cert); | ||
667 | OPTSTRING(required_from_host_keys); | ||
668 | #undef OPTSTRING | ||
669 | |||
670 | if (dup_strings(&ret->env, &ret->nenv, orig->env, orig->nenv) != 0 || | ||
671 | dup_strings(&ret->permitopen, &ret->npermitopen, | ||
672 | orig->permitopen, orig->npermitopen) != 0) { | ||
673 | sshauthopt_free(ret); | ||
674 | return NULL; | ||
675 | } | ||
676 | return ret; | ||
677 | } | ||
678 | |||
679 | static int | ||
680 | serialise_array(struct sshbuf *m, char **a, size_t n) | ||
681 | { | ||
682 | struct sshbuf *b; | ||
683 | size_t i; | ||
684 | int r; | ||
685 | |||
686 | if (n > INT_MAX) | ||
687 | return SSH_ERR_INTERNAL_ERROR; | ||
688 | |||
689 | if ((b = sshbuf_new()) == NULL) { | ||
690 | return SSH_ERR_ALLOC_FAIL; | ||
691 | } | ||
692 | for (i = 0; i < n; i++) { | ||
693 | if ((r = sshbuf_put_cstring(b, a[i])) != 0) { | ||
694 | sshbuf_free(b); | ||
695 | return r; | ||
696 | } | ||
697 | } | ||
698 | if ((r = sshbuf_put_u32(m, n)) != 0 || | ||
699 | (r = sshbuf_put_stringb(m, b)) != 0) { | ||
700 | sshbuf_free(b); | ||
701 | return r; | ||
702 | } | ||
659 | /* success */ | 703 | /* success */ |
660 | *reason = NULL; | ||
661 | return 0; | 704 | return 0; |
662 | } | 705 | } |
663 | 706 | ||
707 | static int | ||
708 | deserialise_array(struct sshbuf *m, char ***ap, size_t *np) | ||
709 | { | ||
710 | char **a = NULL; | ||
711 | size_t i, n = 0; | ||
712 | struct sshbuf *b = NULL; | ||
713 | u_int tmp; | ||
714 | int r = SSH_ERR_INTERNAL_ERROR; | ||
715 | |||
716 | if ((r = sshbuf_get_u32(m, &tmp)) != 0 || | ||
717 | (r = sshbuf_froms(m, &b)) != 0) | ||
718 | goto out; | ||
719 | if (tmp > INT_MAX) { | ||
720 | r = SSH_ERR_INVALID_FORMAT; | ||
721 | goto out; | ||
722 | } | ||
723 | n = tmp; | ||
724 | if (n > 0 && (a = calloc(n, sizeof(*a))) == NULL) { | ||
725 | r = SSH_ERR_ALLOC_FAIL; | ||
726 | goto out; | ||
727 | } | ||
728 | for (i = 0; i < n; i++) { | ||
729 | if ((r = sshbuf_get_cstring(b, &a[i], NULL)) != 0) | ||
730 | goto out; | ||
731 | } | ||
732 | /* success */ | ||
733 | r = 0; | ||
734 | *ap = a; | ||
735 | a = NULL; | ||
736 | *np = n; | ||
737 | n = 0; | ||
738 | out: | ||
739 | for (i = 0; i < n; i++) | ||
740 | free(a[i]); | ||
741 | free(a); | ||
742 | sshbuf_free(b); | ||
743 | return r; | ||
744 | } | ||
745 | |||
746 | static int | ||
747 | serialise_nullable_string(struct sshbuf *m, const char *s) | ||
748 | { | ||
749 | int r; | ||
750 | |||
751 | if ((r = sshbuf_put_u8(m, s == NULL)) != 0 || | ||
752 | (r = sshbuf_put_cstring(m, s)) != 0) | ||
753 | return r; | ||
754 | return 0; | ||
755 | } | ||
756 | |||
757 | static int | ||
758 | deserialise_nullable_string(struct sshbuf *m, char **sp) | ||
759 | { | ||
760 | int r; | ||
761 | u_char flag; | ||
762 | |||
763 | *sp = NULL; | ||
764 | if ((r = sshbuf_get_u8(m, &flag)) != 0 || | ||
765 | (r = sshbuf_get_cstring(m, flag ? NULL : sp, NULL)) != 0) | ||
766 | return r; | ||
767 | return 0; | ||
768 | } | ||
769 | |||
770 | int | ||
771 | sshauthopt_serialise(const struct sshauthopt *opts, struct sshbuf *m, | ||
772 | int untrusted) | ||
773 | { | ||
774 | int r = SSH_ERR_INTERNAL_ERROR; | ||
775 | |||
776 | /* Flag and simple integer options */ | ||
777 | if ((r = sshbuf_put_u8(m, opts->permit_port_forwarding_flag)) != 0 || | ||
778 | (r = sshbuf_put_u8(m, opts->permit_agent_forwarding_flag)) != 0 || | ||
779 | (r = sshbuf_put_u8(m, opts->permit_x11_forwarding_flag)) != 0 || | ||
780 | (r = sshbuf_put_u8(m, opts->permit_pty_flag)) != 0 || | ||
781 | (r = sshbuf_put_u8(m, opts->permit_user_rc)) != 0 || | ||
782 | (r = sshbuf_put_u8(m, opts->restricted)) != 0 || | ||
783 | (r = sshbuf_put_u8(m, opts->cert_authority)) != 0 || | ||
784 | (r = sshbuf_put_u64(m, opts->valid_before)) != 0) | ||
785 | return r; | ||
786 | |||
787 | /* tunnel number can be negative to indicate "unset" */ | ||
788 | if ((r = sshbuf_put_u8(m, opts->force_tun_device == -1)) != 0 || | ||
789 | (r = sshbuf_put_u32(m, (opts->force_tun_device < 0) ? | ||
790 | 0 : (u_int)opts->force_tun_device)) != 0) | ||
791 | return r; | ||
792 | |||
793 | /* String options; these may be NULL */ | ||
794 | if ((r = serialise_nullable_string(m, | ||
795 | untrusted ? "yes" : opts->cert_principals)) != 0 || | ||
796 | (r = serialise_nullable_string(m, | ||
797 | untrusted ? "true" : opts->force_command)) != 0 || | ||
798 | (r = serialise_nullable_string(m, | ||
799 | untrusted ? NULL : opts->required_from_host_cert)) != 0 || | ||
800 | (r = serialise_nullable_string(m, | ||
801 | untrusted ? NULL : opts->required_from_host_keys)) != 0) | ||
802 | return r; | ||
803 | |||
804 | /* Array options */ | ||
805 | if ((r = serialise_array(m, opts->env, | ||
806 | untrusted ? 0 : opts->nenv)) != 0 || | ||
807 | (r = serialise_array(m, opts->permitopen, | ||
808 | untrusted ? 0 : opts->npermitopen)) != 0) | ||
809 | return r; | ||
810 | |||
811 | /* success */ | ||
812 | return 0; | ||
813 | } | ||
814 | |||
815 | int | ||
816 | sshauthopt_deserialise(struct sshbuf *m, struct sshauthopt **optsp) | ||
817 | { | ||
818 | struct sshauthopt *opts = NULL; | ||
819 | int r = SSH_ERR_INTERNAL_ERROR; | ||
820 | u_char f; | ||
821 | u_int tmp; | ||
822 | |||
823 | if ((opts = calloc(1, sizeof(*opts))) == NULL) | ||
824 | return SSH_ERR_ALLOC_FAIL; | ||
825 | |||
826 | #define OPT_FLAG(x) \ | ||
827 | do { \ | ||
828 | if ((r = sshbuf_get_u8(m, &f)) != 0) \ | ||
829 | goto out; \ | ||
830 | opts->x = f; \ | ||
831 | } while (0) | ||
832 | OPT_FLAG(permit_port_forwarding_flag); | ||
833 | OPT_FLAG(permit_agent_forwarding_flag); | ||
834 | OPT_FLAG(permit_x11_forwarding_flag); | ||
835 | OPT_FLAG(permit_pty_flag); | ||
836 | OPT_FLAG(permit_user_rc); | ||
837 | OPT_FLAG(restricted); | ||
838 | OPT_FLAG(cert_authority); | ||
839 | #undef OPT_FLAG | ||
840 | |||
841 | if ((r = sshbuf_get_u64(m, &opts->valid_before)) != 0) | ||
842 | goto out; | ||
843 | |||
844 | /* tunnel number can be negative to indicate "unset" */ | ||
845 | if ((r = sshbuf_get_u8(m, &f)) != 0 || | ||
846 | (r = sshbuf_get_u32(m, &tmp)) != 0) | ||
847 | goto out; | ||
848 | opts->force_tun_device = f ? -1 : (int)tmp; | ||
849 | |||
850 | /* String options may be NULL */ | ||
851 | if ((r = deserialise_nullable_string(m, &opts->cert_principals)) != 0 || | ||
852 | (r = deserialise_nullable_string(m, &opts->force_command)) != 0 || | ||
853 | (r = deserialise_nullable_string(m, | ||
854 | &opts->required_from_host_cert)) != 0 || | ||
855 | (r = deserialise_nullable_string(m, | ||
856 | &opts->required_from_host_keys)) != 0) | ||
857 | goto out; | ||
858 | |||
859 | /* Array options */ | ||
860 | if ((r = deserialise_array(m, &opts->env, &opts->nenv)) != 0 || | ||
861 | (r = deserialise_array(m, | ||
862 | &opts->permitopen, &opts->npermitopen)) != 0) | ||
863 | goto out; | ||
864 | |||
865 | /* success */ | ||
866 | r = 0; | ||
867 | *optsp = opts; | ||
868 | opts = NULL; | ||
869 | out: | ||
870 | sshauthopt_free(opts); | ||
871 | return r; | ||
872 | } | ||