xref: /openbsd-src/usr.bin/ssh/auth-options.c (revision ae3cb403620ab940fbaabb3055fac045a63d56b7)
1 /* $OpenBSD: auth-options.c,v 1.74 2017/09/12 06:32:07 djm Exp $ */
2 /*
3  * Author: Tatu Ylonen <ylo@cs.hut.fi>
4  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
5  *                    All rights reserved
6  * As far as I am concerned, the code I have written for this software
7  * can be used freely for any purpose.  Any derived versions of this
8  * software must be clearly marked as such, and if the derived work is
9  * incompatible with the protocol description in the RFC file, it must be
10  * called by a name other than "ssh" or "Secure Shell".
11  */
12 
13 #include <sys/types.h>
14 #include <sys/queue.h>
15 
16 #include <netdb.h>
17 #include <pwd.h>
18 #include <string.h>
19 #include <stdio.h>
20 #include <stdarg.h>
21 
22 #include "key.h"	/* XXX for typedef */
23 #include "buffer.h"	/* XXX for typedef */
24 #include "xmalloc.h"
25 #include "match.h"
26 #include "ssherr.h"
27 #include "log.h"
28 #include "canohost.h"
29 #include "packet.h"
30 #include "sshbuf.h"
31 #include "misc.h"
32 #include "channels.h"
33 #include "servconf.h"
34 #include "sshkey.h"
35 #include "auth-options.h"
36 #include "hostfile.h"
37 #include "auth.h"
38 
39 /* Flags set authorized_keys flags */
40 int no_port_forwarding_flag = 0;
41 int no_agent_forwarding_flag = 0;
42 int no_x11_forwarding_flag = 0;
43 int no_pty_flag = 0;
44 int no_user_rc = 0;
45 int key_is_cert_authority = 0;
46 
47 /* "command=" option. */
48 char *forced_command = NULL;
49 
50 /* "environment=" options. */
51 struct envstring *custom_environment = NULL;
52 
53 /* "tunnel=" option. */
54 int forced_tun_device = -1;
55 
56 /* "principals=" option. */
57 char *authorized_principals = NULL;
58 
59 extern ServerOptions options;
60 
61 /* XXX refactor to be stateless */
62 
63 void
64 auth_clear_options(void)
65 {
66 	struct ssh *ssh = active_state;		/* XXX */
67 
68 	no_agent_forwarding_flag = 0;
69 	no_port_forwarding_flag = 0;
70 	no_pty_flag = 0;
71 	no_x11_forwarding_flag = 0;
72 	no_user_rc = 0;
73 	key_is_cert_authority = 0;
74 	while (custom_environment) {
75 		struct envstring *ce = custom_environment;
76 		custom_environment = ce->next;
77 		free(ce->s);
78 		free(ce);
79 	}
80 	free(forced_command);
81 	forced_command = NULL;
82 	free(authorized_principals);
83 	authorized_principals = NULL;
84 	forced_tun_device = -1;
85 	channel_clear_permitted_opens(ssh);
86 }
87 
88 /*
89  * Match flag 'opt' in *optsp, and if allow_negate is set then also match
90  * 'no-opt'. Returns -1 if option not matched, 1 if option matches or 0
91  * if negated option matches.
92  * If the option or negated option matches, then *optsp is updated to
93  * point to the first character after the option and, if 'msg' is not NULL
94  * then a message based on it added via auth_debug_add().
95  */
96 static int
97 match_flag(const char *opt, int allow_negate, char **optsp, const char *msg)
98 {
99 	size_t opt_len = strlen(opt);
100 	char *opts = *optsp;
101 	int negate = 0;
102 
103 	if (allow_negate && strncasecmp(opts, "no-", 3) == 0) {
104 		opts += 3;
105 		negate = 1;
106 	}
107 	if (strncasecmp(opts, opt, opt_len) == 0) {
108 		*optsp = opts + opt_len;
109 		if (msg != NULL) {
110 			auth_debug_add("%s %s.", msg,
111 			    negate ? "disabled" : "enabled");
112 		}
113 		return negate ? 0 : 1;
114 	}
115 	return -1;
116 }
117 
118 /*
119  * return 1 if access is granted, 0 if not.
120  * side effect: sets key option flags
121  * XXX remove side effects; fill structure instead.
122  */
123 int
124 auth_parse_options(struct passwd *pw, char *opts, const char *file,
125     u_long linenum)
126 {
127 	struct ssh *ssh = active_state;		/* XXX */
128 	const char *cp;
129 	int i, r;
130 
131 	/* reset options */
132 	auth_clear_options();
133 
134 	if (!opts)
135 		return 1;
136 
137 	while (*opts && *opts != ' ' && *opts != '\t') {
138 		if ((r = match_flag("cert-authority", 0, &opts, NULL)) != -1) {
139 			key_is_cert_authority = r;
140 			goto next_option;
141 		}
142 		if ((r = match_flag("restrict", 0, &opts, NULL)) != -1) {
143 			auth_debug_add("Key is restricted.");
144 			no_port_forwarding_flag = 1;
145 			no_agent_forwarding_flag = 1;
146 			no_x11_forwarding_flag = 1;
147 			no_pty_flag = 1;
148 			no_user_rc = 1;
149 			goto next_option;
150 		}
151 		if ((r = match_flag("port-forwarding", 1, &opts,
152 		    "Port forwarding")) != -1) {
153 			no_port_forwarding_flag = r != 1;
154 			goto next_option;
155 		}
156 		if ((r = match_flag("agent-forwarding", 1, &opts,
157 		    "Agent forwarding")) != -1) {
158 			no_agent_forwarding_flag = r != 1;
159 			goto next_option;
160 		}
161 		if ((r = match_flag("x11-forwarding", 1, &opts,
162 		    "X11 forwarding")) != -1) {
163 			no_x11_forwarding_flag = r != 1;
164 			goto next_option;
165 		}
166 		if ((r = match_flag("pty", 1, &opts,
167 		    "PTY allocation")) != -1) {
168 			no_pty_flag = r != 1;
169 			goto next_option;
170 		}
171 		if ((r = match_flag("user-rc", 1, &opts,
172 		    "User rc execution")) != -1) {
173 			no_user_rc = r != 1;
174 			goto next_option;
175 		}
176 		cp = "command=\"";
177 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
178 			opts += strlen(cp);
179 			free(forced_command);
180 			forced_command = xmalloc(strlen(opts) + 1);
181 			i = 0;
182 			while (*opts) {
183 				if (*opts == '"')
184 					break;
185 				if (*opts == '\\' && opts[1] == '"') {
186 					opts += 2;
187 					forced_command[i++] = '"';
188 					continue;
189 				}
190 				forced_command[i++] = *opts++;
191 			}
192 			if (!*opts) {
193 				debug("%.100s, line %lu: missing end quote",
194 				    file, linenum);
195 				auth_debug_add("%.100s, line %lu: missing end quote",
196 				    file, linenum);
197 				free(forced_command);
198 				forced_command = NULL;
199 				goto bad_option;
200 			}
201 			forced_command[i] = '\0';
202 			auth_debug_add("Forced command.");
203 			opts++;
204 			goto next_option;
205 		}
206 		cp = "principals=\"";
207 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
208 			opts += strlen(cp);
209 			free(authorized_principals);
210 			authorized_principals = xmalloc(strlen(opts) + 1);
211 			i = 0;
212 			while (*opts) {
213 				if (*opts == '"')
214 					break;
215 				if (*opts == '\\' && opts[1] == '"') {
216 					opts += 2;
217 					authorized_principals[i++] = '"';
218 					continue;
219 				}
220 				authorized_principals[i++] = *opts++;
221 			}
222 			if (!*opts) {
223 				debug("%.100s, line %lu: missing end quote",
224 				    file, linenum);
225 				auth_debug_add("%.100s, line %lu: missing end quote",
226 				    file, linenum);
227 				free(authorized_principals);
228 				authorized_principals = NULL;
229 				goto bad_option;
230 			}
231 			authorized_principals[i] = '\0';
232 			auth_debug_add("principals: %.900s",
233 			    authorized_principals);
234 			opts++;
235 			goto next_option;
236 		}
237 		cp = "environment=\"";
238 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
239 			char *s;
240 			struct envstring *new_envstring;
241 
242 			opts += strlen(cp);
243 			s = xmalloc(strlen(opts) + 1);
244 			i = 0;
245 			while (*opts) {
246 				if (*opts == '"')
247 					break;
248 				if (*opts == '\\' && opts[1] == '"') {
249 					opts += 2;
250 					s[i++] = '"';
251 					continue;
252 				}
253 				s[i++] = *opts++;
254 			}
255 			if (!*opts) {
256 				debug("%.100s, line %lu: missing end quote",
257 				    file, linenum);
258 				auth_debug_add("%.100s, line %lu: missing end quote",
259 				    file, linenum);
260 				free(s);
261 				goto bad_option;
262 			}
263 			s[i] = '\0';
264 			opts++;
265 			if (options.permit_user_env) {
266 				auth_debug_add("Adding to environment: "
267 				    "%.900s", s);
268 				debug("Adding to environment: %.900s", s);
269 				new_envstring = xcalloc(1,
270 				    sizeof(*new_envstring));
271 				new_envstring->s = s;
272 				new_envstring->next = custom_environment;
273 				custom_environment = new_envstring;
274 				s = NULL;
275 			}
276 			free(s);
277 			goto next_option;
278 		}
279 		cp = "from=\"";
280 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
281 			const char *remote_ip = ssh_remote_ipaddr(ssh);
282 			const char *remote_host = auth_get_canonical_hostname(
283 			    ssh, options.use_dns);
284 			char *patterns = xmalloc(strlen(opts) + 1);
285 
286 			opts += strlen(cp);
287 			i = 0;
288 			while (*opts) {
289 				if (*opts == '"')
290 					break;
291 				if (*opts == '\\' && opts[1] == '"') {
292 					opts += 2;
293 					patterns[i++] = '"';
294 					continue;
295 				}
296 				patterns[i++] = *opts++;
297 			}
298 			if (!*opts) {
299 				debug("%.100s, line %lu: missing end quote",
300 				    file, linenum);
301 				auth_debug_add("%.100s, line %lu: missing end quote",
302 				    file, linenum);
303 				free(patterns);
304 				goto bad_option;
305 			}
306 			patterns[i] = '\0';
307 			opts++;
308 			switch (match_host_and_ip(remote_host, remote_ip,
309 			    patterns)) {
310 			case 1:
311 				free(patterns);
312 				/* Host name matches. */
313 				goto next_option;
314 			case -1:
315 				debug("%.100s, line %lu: invalid criteria",
316 				    file, linenum);
317 				auth_debug_add("%.100s, line %lu: "
318 				    "invalid criteria", file, linenum);
319 				/* FALLTHROUGH */
320 			case 0:
321 				free(patterns);
322 				logit("Authentication tried for %.100s with "
323 				    "correct key but not from a permitted "
324 				    "host (host=%.200s, ip=%.200s).",
325 				    pw->pw_name, remote_host, remote_ip);
326 				auth_debug_add("Your host '%.200s' is not "
327 				    "permitted to use this key for login.",
328 				    remote_host);
329 				break;
330 			}
331 			/* deny access */
332 			return 0;
333 		}
334 		cp = "permitopen=\"";
335 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
336 			char *host, *p;
337 			int port;
338 			char *patterns = xmalloc(strlen(opts) + 1);
339 
340 			opts += strlen(cp);
341 			i = 0;
342 			while (*opts) {
343 				if (*opts == '"')
344 					break;
345 				if (*opts == '\\' && opts[1] == '"') {
346 					opts += 2;
347 					patterns[i++] = '"';
348 					continue;
349 				}
350 				patterns[i++] = *opts++;
351 			}
352 			if (!*opts) {
353 				debug("%.100s, line %lu: missing end quote",
354 				    file, linenum);
355 				auth_debug_add("%.100s, line %lu: missing "
356 				    "end quote", file, linenum);
357 				free(patterns);
358 				goto bad_option;
359 			}
360 			patterns[i] = '\0';
361 			opts++;
362 			p = patterns;
363 			/* XXX - add streamlocal support */
364 			host = hpdelim(&p);
365 			if (host == NULL || strlen(host) >= NI_MAXHOST) {
366 				debug("%.100s, line %lu: Bad permitopen "
367 				    "specification <%.100s>", file, linenum,
368 				    patterns);
369 				auth_debug_add("%.100s, line %lu: "
370 				    "Bad permitopen specification", file,
371 				    linenum);
372 				free(patterns);
373 				goto bad_option;
374 			}
375 			host = cleanhostname(host);
376 			if (p == NULL || (port = permitopen_port(p)) < 0) {
377 				debug("%.100s, line %lu: Bad permitopen port "
378 				    "<%.100s>", file, linenum, p ? p : "");
379 				auth_debug_add("%.100s, line %lu: "
380 				    "Bad permitopen port", file, linenum);
381 				free(patterns);
382 				goto bad_option;
383 			}
384 			if ((options.allow_tcp_forwarding & FORWARD_LOCAL) != 0)
385 				channel_add_permitted_opens(ssh, host, port);
386 			free(patterns);
387 			goto next_option;
388 		}
389 		cp = "tunnel=\"";
390 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
391 			char *tun = NULL;
392 			opts += strlen(cp);
393 			tun = xmalloc(strlen(opts) + 1);
394 			i = 0;
395 			while (*opts) {
396 				if (*opts == '"')
397 					break;
398 				tun[i++] = *opts++;
399 			}
400 			if (!*opts) {
401 				debug("%.100s, line %lu: missing end quote",
402 				    file, linenum);
403 				auth_debug_add("%.100s, line %lu: missing end quote",
404 				    file, linenum);
405 				free(tun);
406 				forced_tun_device = -1;
407 				goto bad_option;
408 			}
409 			tun[i] = '\0';
410 			forced_tun_device = a2tun(tun, NULL);
411 			free(tun);
412 			if (forced_tun_device == SSH_TUNID_ERR) {
413 				debug("%.100s, line %lu: invalid tun device",
414 				    file, linenum);
415 				auth_debug_add("%.100s, line %lu: invalid tun device",
416 				    file, linenum);
417 				forced_tun_device = -1;
418 				goto bad_option;
419 			}
420 			auth_debug_add("Forced tun device: %d", forced_tun_device);
421 			opts++;
422 			goto next_option;
423 		}
424 next_option:
425 		/*
426 		 * Skip the comma, and move to the next option
427 		 * (or break out if there are no more).
428 		 */
429 		if (!*opts)
430 			fatal("Bugs in auth-options.c option processing.");
431 		if (*opts == ' ' || *opts == '\t')
432 			break;		/* End of options. */
433 		if (*opts != ',')
434 			goto bad_option;
435 		opts++;
436 		/* Process the next option. */
437 	}
438 
439 	/* grant access */
440 	return 1;
441 
442 bad_option:
443 	logit("Bad options in %.100s file, line %lu: %.50s",
444 	    file, linenum, opts);
445 	auth_debug_add("Bad options in %.100s file, line %lu: %.50s",
446 	    file, linenum, opts);
447 
448 	/* deny access */
449 	return 0;
450 }
451 
452 #define OPTIONS_CRITICAL	1
453 #define OPTIONS_EXTENSIONS	2
454 static int
455 parse_option_list(struct sshbuf *oblob, struct passwd *pw,
456     u_int which, int crit,
457     int *cert_no_port_forwarding_flag,
458     int *cert_no_agent_forwarding_flag,
459     int *cert_no_x11_forwarding_flag,
460     int *cert_no_pty_flag,
461     int *cert_no_user_rc,
462     char **cert_forced_command,
463     int *cert_source_address_done)
464 {
465 	struct ssh *ssh = active_state;		/* XXX */
466 	char *command, *allowed;
467 	const char *remote_ip;
468 	char *name = NULL;
469 	struct sshbuf *c = NULL, *data = NULL;
470 	int r, ret = -1, result, found;
471 
472 	if ((c = sshbuf_fromb(oblob)) == NULL) {
473 		error("%s: sshbuf_fromb failed", __func__);
474 		goto out;
475 	}
476 
477 	while (sshbuf_len(c) > 0) {
478 		sshbuf_free(data);
479 		data = NULL;
480 		if ((r = sshbuf_get_cstring(c, &name, NULL)) != 0 ||
481 		    (r = sshbuf_froms(c, &data)) != 0) {
482 			error("Unable to parse certificate options: %s",
483 			    ssh_err(r));
484 			goto out;
485 		}
486 		debug3("found certificate option \"%.100s\" len %zu",
487 		    name, sshbuf_len(data));
488 		found = 0;
489 		if ((which & OPTIONS_EXTENSIONS) != 0) {
490 			if (strcmp(name, "permit-X11-forwarding") == 0) {
491 				*cert_no_x11_forwarding_flag = 0;
492 				found = 1;
493 			} else if (strcmp(name,
494 			    "permit-agent-forwarding") == 0) {
495 				*cert_no_agent_forwarding_flag = 0;
496 				found = 1;
497 			} else if (strcmp(name,
498 			    "permit-port-forwarding") == 0) {
499 				*cert_no_port_forwarding_flag = 0;
500 				found = 1;
501 			} else if (strcmp(name, "permit-pty") == 0) {
502 				*cert_no_pty_flag = 0;
503 				found = 1;
504 			} else if (strcmp(name, "permit-user-rc") == 0) {
505 				*cert_no_user_rc = 0;
506 				found = 1;
507 			}
508 		}
509 		if (!found && (which & OPTIONS_CRITICAL) != 0) {
510 			if (strcmp(name, "force-command") == 0) {
511 				if ((r = sshbuf_get_cstring(data, &command,
512 				    NULL)) != 0) {
513 					error("Unable to parse \"%s\" "
514 					    "section: %s", name, ssh_err(r));
515 					goto out;
516 				}
517 				if (*cert_forced_command != NULL) {
518 					error("Certificate has multiple "
519 					    "force-command options");
520 					free(command);
521 					goto out;
522 				}
523 				*cert_forced_command = command;
524 				found = 1;
525 			}
526 			if (strcmp(name, "source-address") == 0) {
527 				if ((r = sshbuf_get_cstring(data, &allowed,
528 				    NULL)) != 0) {
529 					error("Unable to parse \"%s\" "
530 					    "section: %s", name, ssh_err(r));
531 					goto out;
532 				}
533 				if ((*cert_source_address_done)++) {
534 					error("Certificate has multiple "
535 					    "source-address options");
536 					free(allowed);
537 					goto out;
538 				}
539 				remote_ip = ssh_remote_ipaddr(ssh);
540 				result = addr_match_cidr_list(remote_ip,
541 				    allowed);
542 				free(allowed);
543 				switch (result) {
544 				case 1:
545 					/* accepted */
546 					break;
547 				case 0:
548 					/* no match */
549 					logit("Authentication tried for %.100s "
550 					    "with valid certificate but not "
551 					    "from a permitted host "
552 					    "(ip=%.200s).", pw->pw_name,
553 					    remote_ip);
554 					auth_debug_add("Your address '%.200s' "
555 					    "is not permitted to use this "
556 					    "certificate for login.",
557 					    remote_ip);
558 					goto out;
559 				case -1:
560 				default:
561 					error("Certificate source-address "
562 					    "contents invalid");
563 					goto out;
564 				}
565 				found = 1;
566 			}
567 		}
568 
569 		if (!found) {
570 			if (crit) {
571 				error("Certificate critical option \"%s\" "
572 				    "is not supported", name);
573 				goto out;
574 			} else {
575 				logit("Certificate extension \"%s\" "
576 				    "is not supported", name);
577 			}
578 		} else if (sshbuf_len(data) != 0) {
579 			error("Certificate option \"%s\" corrupt "
580 			    "(extra data)", name);
581 			goto out;
582 		}
583 		free(name);
584 		name = NULL;
585 	}
586 	/* successfully parsed all options */
587 	ret = 0;
588 
589  out:
590 	if (ret != 0 &&
591 	    cert_forced_command != NULL &&
592 	    *cert_forced_command != NULL) {
593 		free(*cert_forced_command);
594 		*cert_forced_command = NULL;
595 	}
596 	free(name);
597 	sshbuf_free(data);
598 	sshbuf_free(c);
599 	return ret;
600 }
601 
602 /*
603  * Set options from critical certificate options. These supersede user key
604  * options so this must be called after auth_parse_options().
605  */
606 int
607 auth_cert_options(struct sshkey *k, struct passwd *pw, const char **reason)
608 {
609 	int cert_no_port_forwarding_flag = 1;
610 	int cert_no_agent_forwarding_flag = 1;
611 	int cert_no_x11_forwarding_flag = 1;
612 	int cert_no_pty_flag = 1;
613 	int cert_no_user_rc = 1;
614 	char *cert_forced_command = NULL;
615 	int cert_source_address_done = 0;
616 
617 	*reason = "invalid certificate options";
618 
619 	/* Separate options and extensions for v01 certs */
620 	if (parse_option_list(k->cert->critical, pw,
621 	    OPTIONS_CRITICAL, 1, NULL, NULL, NULL, NULL, NULL,
622 	    &cert_forced_command,
623 	    &cert_source_address_done) == -1)
624 		return -1;
625 	if (parse_option_list(k->cert->extensions, pw,
626 	    OPTIONS_EXTENSIONS, 0,
627 	    &cert_no_port_forwarding_flag,
628 	    &cert_no_agent_forwarding_flag,
629 	    &cert_no_x11_forwarding_flag,
630 	    &cert_no_pty_flag,
631 	    &cert_no_user_rc,
632 	    NULL, NULL) == -1)
633 		return -1;
634 
635 	no_port_forwarding_flag |= cert_no_port_forwarding_flag;
636 	no_agent_forwarding_flag |= cert_no_agent_forwarding_flag;
637 	no_x11_forwarding_flag |= cert_no_x11_forwarding_flag;
638 	no_pty_flag |= cert_no_pty_flag;
639 	no_user_rc |= cert_no_user_rc;
640 	/*
641 	 * Only permit both CA and key option forced-command if they match.
642 	 * Otherwise refuse the certificate.
643 	 */
644 	if (cert_forced_command != NULL && forced_command != NULL) {
645 		if (strcmp(forced_command, cert_forced_command) == 0) {
646 			free(forced_command);
647 			forced_command = cert_forced_command;
648 		} else {
649 			*reason = "certificate and key options forced command "
650 			    "do not match";
651 			free(cert_forced_command);
652 			return -1;
653 		}
654 	} else if (cert_forced_command != NULL)
655 		forced_command = cert_forced_command;
656 	/* success */
657 	*reason = NULL;
658 	return 0;
659 }
660 
661