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