xref: /netbsd-src/crypto/external/bsd/openssh/dist/auth-options.c (revision a24efa7dea9f1f56c3bdb15a927d3516792ace1c)
1 /*	$NetBSD: auth-options.c,v 1.12 2016/03/11 01:55:00 christos Exp $	*/
2 /* $OpenBSD: auth-options.c,v 1.70 2015/12/10 17:08:40 mmcc Exp $ */
3 
4 /*
5  * Author: Tatu Ylonen <ylo@cs.hut.fi>
6  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
7  *                    All rights reserved
8  * As far as I am concerned, the code I have written for this software
9  * can be used freely for any purpose.  Any derived versions of this
10  * software must be clearly marked as such, and if the derived work is
11  * incompatible with the protocol description in the RFC file, it must be
12  * called by a name other than "ssh" or "Secure Shell".
13  */
14 
15 #include "includes.h"
16 __RCSID("$NetBSD: auth-options.c,v 1.12 2016/03/11 01:55:00 christos Exp $");
17 #include <sys/types.h>
18 #include <sys/queue.h>
19 
20 #include <netdb.h>
21 #include <pwd.h>
22 #include <string.h>
23 #include <stdio.h>
24 #include <stdarg.h>
25 #include <time.h>
26 
27 #include "key.h"	/* XXX for typedef */
28 #include "buffer.h"	/* XXX for typedef */
29 #include "xmalloc.h"
30 #include "match.h"
31 #include "ssherr.h"
32 #include "log.h"
33 #include "canohost.h"
34 #include "sshbuf.h"
35 #include "misc.h"
36 #include "channels.h"
37 #include "servconf.h"
38 #include "sshkey.h"
39 #include "auth-options.h"
40 #include "hostfile.h"
41 #include "auth.h"
42 
43 /* Flags set authorized_keys flags */
44 int no_port_forwarding_flag = 0;
45 int no_agent_forwarding_flag = 0;
46 int no_x11_forwarding_flag = 0;
47 int no_pty_flag = 0;
48 int no_user_rc = 0;
49 int key_is_cert_authority = 0;
50 
51 /* "command=" option. */
52 char *forced_command = NULL;
53 
54 /* "environment=" options. */
55 struct envstring *custom_environment = NULL;
56 
57 /* "tunnel=" option. */
58 int forced_tun_device = -1;
59 
60 /* "principals=" option. */
61 char *authorized_principals = NULL;
62 
63 extern ServerOptions options;
64 
65 void
66 auth_clear_options(void)
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();
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, const char **optsp, const char *msg)
98 {
99 	size_t opt_len = strlen(opt);
100 	const 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  */
122 int
123 auth_parse_options(struct passwd *pw, const char *opts, const char *file,
124     u_long linenum)
125 {
126 	const char *cp;
127 	int i, r;
128 
129 	/* reset options */
130 	auth_clear_options();
131 
132 	if (!opts)
133 		return 1;
134 
135 	while (*opts && *opts != ' ' && *opts != '\t') {
136 		if ((r = match_flag("cert-authority", 0, &opts, NULL)) != -1) {
137 			key_is_cert_authority = r;
138 			goto next_option;
139 		}
140 		if ((r = match_flag("restrict", 0, &opts, NULL)) != -1) {
141 			auth_debug_add("Key is restricted.");
142 			no_port_forwarding_flag = 1;
143 			no_agent_forwarding_flag = 1;
144 			no_x11_forwarding_flag = 1;
145 			no_pty_flag = 1;
146 			no_user_rc = 1;
147 			goto next_option;
148 		}
149 		if ((r = match_flag("port-forwarding", 1, &opts,
150 		    "Port forwarding")) != -1) {
151 			no_port_forwarding_flag = r != 1;
152 			goto next_option;
153 		}
154 		if ((r = match_flag("agent-forwarding", 1, &opts,
155 		    "Agent forwarding")) != -1) {
156 			no_agent_forwarding_flag = r != 1;
157 			goto next_option;
158 		}
159 		if ((r = match_flag("x11-forwarding", 1, &opts,
160 		    "X11 forwarding")) != -1) {
161 			no_x11_forwarding_flag = r != 1;
162 			goto next_option;
163 		}
164 		if ((r = match_flag("pty", 1, &opts,
165 		    "PTY allocation")) != -1) {
166 			no_pty_flag = r != 1;
167 			goto next_option;
168 		}
169 		if ((r = match_flag("user-rc", 1, &opts,
170 		    "User rc execution")) != -1) {
171 			no_user_rc = r != 1;
172 			goto next_option;
173 		}
174 		cp = "command=\"";
175 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
176 			opts += strlen(cp);
177 			free(forced_command);
178 			forced_command = xmalloc(strlen(opts) + 1);
179 			i = 0;
180 			while (*opts) {
181 				if (*opts == '"')
182 					break;
183 				if (*opts == '\\' && opts[1] == '"') {
184 					opts += 2;
185 					forced_command[i++] = '"';
186 					continue;
187 				}
188 				forced_command[i++] = *opts++;
189 			}
190 			if (!*opts) {
191 				debug("%.100s, line %lu: missing end quote",
192 				    file, linenum);
193 				auth_debug_add("%.100s, line %lu: missing end quote",
194 				    file, linenum);
195 				free(forced_command);
196 				forced_command = NULL;
197 				goto bad_option;
198 			}
199 			forced_command[i] = '\0';
200 			auth_debug_add("Forced command.");
201 			opts++;
202 			goto next_option;
203 		}
204 		cp = "principals=\"";
205 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
206 			opts += strlen(cp);
207 			free(authorized_principals);
208 			authorized_principals = xmalloc(strlen(opts) + 1);
209 			i = 0;
210 			while (*opts) {
211 				if (*opts == '"')
212 					break;
213 				if (*opts == '\\' && opts[1] == '"') {
214 					opts += 2;
215 					authorized_principals[i++] = '"';
216 					continue;
217 				}
218 				authorized_principals[i++] = *opts++;
219 			}
220 			if (!*opts) {
221 				debug("%.100s, line %lu: missing end quote",
222 				    file, linenum);
223 				auth_debug_add("%.100s, line %lu: missing end quote",
224 				    file, linenum);
225 				free(authorized_principals);
226 				authorized_principals = NULL;
227 				goto bad_option;
228 			}
229 			authorized_principals[i] = '\0';
230 			auth_debug_add("principals: %.900s",
231 			    authorized_principals);
232 			opts++;
233 			goto next_option;
234 		}
235 		cp = "environment=\"";
236 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
237 			char *s;
238 			struct envstring *new_envstring;
239 
240 			opts += strlen(cp);
241 			s = xmalloc(strlen(opts) + 1);
242 			i = 0;
243 			while (*opts) {
244 				if (*opts == '"')
245 					break;
246 				if (*opts == '\\' && opts[1] == '"') {
247 					opts += 2;
248 					s[i++] = '"';
249 					continue;
250 				}
251 				s[i++] = *opts++;
252 			}
253 			if (!*opts) {
254 				debug("%.100s, line %lu: missing end quote",
255 				    file, linenum);
256 				auth_debug_add("%.100s, line %lu: missing end quote",
257 				    file, linenum);
258 				free(s);
259 				goto bad_option;
260 			}
261 			s[i] = '\0';
262 			opts++;
263 			if (options.permit_user_env) {
264 				auth_debug_add("Adding to environment: "
265 				    "%.900s", s);
266 				debug("Adding to environment: %.900s", s);
267 				new_envstring = xcalloc(1,
268 				    sizeof(*new_envstring));
269 				new_envstring->s = s;
270 				new_envstring->next = custom_environment;
271 				custom_environment = new_envstring;
272 				s = NULL;
273 			}
274 			free(s);
275 			goto next_option;
276 		}
277 		cp = "from=\"";
278 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
279 			const char *remote_ip = get_remote_ipaddr();
280 			const char *remote_host = get_canonical_hostname(
281 			    options.use_dns);
282 			char *patterns = xmalloc(strlen(opts) + 1);
283 
284 			opts += strlen(cp);
285 			i = 0;
286 			while (*opts) {
287 				if (*opts == '"')
288 					break;
289 				if (*opts == '\\' && opts[1] == '"') {
290 					opts += 2;
291 					patterns[i++] = '"';
292 					continue;
293 				}
294 				patterns[i++] = *opts++;
295 			}
296 			if (!*opts) {
297 				debug("%.100s, line %lu: missing end quote",
298 				    file, linenum);
299 				auth_debug_add("%.100s, line %lu: missing end quote",
300 				    file, linenum);
301 				free(patterns);
302 				goto bad_option;
303 			}
304 			patterns[i] = '\0';
305 			opts++;
306 			switch (match_host_and_ip(remote_host, remote_ip,
307 			    patterns)) {
308 			case 1:
309 				free(patterns);
310 				/* Host name matches. */
311 				goto next_option;
312 			case -1:
313 				debug("%.100s, line %lu: invalid criteria",
314 				    file, linenum);
315 				auth_debug_add("%.100s, line %lu: "
316 				    "invalid criteria", file, linenum);
317 				/* FALLTHROUGH */
318 			case 0:
319 				free(patterns);
320 				logit("Authentication tried for %.100s with "
321 				    "correct key but not from a permitted "
322 				    "host (host=%.200s, ip=%.200s).",
323 				    pw->pw_name, remote_host, remote_ip);
324 				auth_debug_add("Your host '%.200s' is not "
325 				    "permitted to use this key for login.",
326 				    remote_host);
327 				break;
328 			}
329 			/* deny access */
330 			return 0;
331 		}
332 		cp = "permitopen=\"";
333 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
334 			char *host, *p;
335 			int port;
336 			char *patterns = xmalloc(strlen(opts) + 1);
337 
338 			opts += strlen(cp);
339 			i = 0;
340 			while (*opts) {
341 				if (*opts == '"')
342 					break;
343 				if (*opts == '\\' && opts[1] == '"') {
344 					opts += 2;
345 					patterns[i++] = '"';
346 					continue;
347 				}
348 				patterns[i++] = *opts++;
349 			}
350 			if (!*opts) {
351 				debug("%.100s, line %lu: missing end quote",
352 				    file, linenum);
353 				auth_debug_add("%.100s, line %lu: missing "
354 				    "end quote", file, linenum);
355 				free(patterns);
356 				goto bad_option;
357 			}
358 			patterns[i] = '\0';
359 			opts++;
360 			p = patterns;
361 			/* XXX - add streamlocal support */
362 			host = hpdelim(&p);
363 			if (host == NULL || strlen(host) >= NI_MAXHOST) {
364 				debug("%.100s, line %lu: Bad permitopen "
365 				    "specification <%.100s>", file, linenum,
366 				    patterns);
367 				auth_debug_add("%.100s, line %lu: "
368 				    "Bad permitopen specification", file,
369 				    linenum);
370 				free(patterns);
371 				goto bad_option;
372 			}
373 			host = cleanhostname(host);
374 			if (p == NULL || (port = permitopen_port(p)) < 0) {
375 				debug("%.100s, line %lu: Bad permitopen port "
376 				    "<%.100s>", file, linenum, p ? p : "");
377 				auth_debug_add("%.100s, line %lu: "
378 				    "Bad permitopen port", file, linenum);
379 				free(patterns);
380 				goto bad_option;
381 			}
382 			if ((options.allow_tcp_forwarding & FORWARD_LOCAL) != 0)
383 				channel_add_permitted_opens(host, port);
384 			free(patterns);
385 			goto next_option;
386 		}
387 		cp = "tunnel=\"";
388 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
389 			char *tun = NULL;
390 			opts += strlen(cp);
391 			tun = xmalloc(strlen(opts) + 1);
392 			i = 0;
393 			while (*opts) {
394 				if (*opts == '"')
395 					break;
396 				tun[i++] = *opts++;
397 			}
398 			if (!*opts) {
399 				debug("%.100s, line %lu: missing end quote",
400 				    file, linenum);
401 				auth_debug_add("%.100s, line %lu: missing end quote",
402 				    file, linenum);
403 				free(tun);
404 				forced_tun_device = -1;
405 				goto bad_option;
406 			}
407 			tun[i] = '\0';
408 			forced_tun_device = a2tun(tun, NULL);
409 			free(tun);
410 			if (forced_tun_device == SSH_TUNID_ERR) {
411 				debug("%.100s, line %lu: invalid tun device",
412 				    file, linenum);
413 				auth_debug_add("%.100s, line %lu: invalid tun device",
414 				    file, linenum);
415 				forced_tun_device = -1;
416 				goto bad_option;
417 			}
418 			auth_debug_add("Forced tun device: %d", forced_tun_device);
419 			opts++;
420 			goto next_option;
421 		}
422 next_option:
423 		/*
424 		 * Skip the comma, and move to the next option
425 		 * (or break out if there are no more).
426 		 */
427 		if (!*opts)
428 			fatal("Bugs in auth-options.c option processing.");
429 		if (*opts == ' ' || *opts == '\t')
430 			break;		/* End of options. */
431 		if (*opts != ',')
432 			goto bad_option;
433 		opts++;
434 		/* Process the next option. */
435 	}
436 
437 	/* grant access */
438 	return 1;
439 
440 bad_option:
441 	logit("Bad options in %.100s file, line %lu: %.50s",
442 	    file, linenum, opts);
443 	auth_debug_add("Bad options in %.100s file, line %lu: %.50s",
444 	    file, linenum, opts);
445 
446 	/* deny access */
447 	return 0;
448 }
449 
450 #define OPTIONS_CRITICAL	1
451 #define OPTIONS_EXTENSIONS	2
452 static int
453 parse_option_list(struct sshbuf *oblob, struct passwd *pw,
454     u_int which, int crit,
455     int *cert_no_port_forwarding_flag,
456     int *cert_no_agent_forwarding_flag,
457     int *cert_no_x11_forwarding_flag,
458     int *cert_no_pty_flag,
459     int *cert_no_user_rc,
460     char **cert_forced_command,
461     int *cert_source_address_done)
462 {
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 = get_remote_ipaddr();
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
604 auth_cert_options(struct sshkey *k, struct passwd *pw)
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 	/* Separate options and extensions for v01 certs */
615 	if (parse_option_list(k->cert->critical, pw,
616 	    OPTIONS_CRITICAL, 1, NULL, NULL, NULL, NULL, NULL,
617 	    &cert_forced_command,
618 	    &cert_source_address_done) == -1)
619 		return -1;
620 	if (parse_option_list(k->cert->extensions, pw,
621 	    OPTIONS_EXTENSIONS, 0,
622 	    &cert_no_port_forwarding_flag,
623 	    &cert_no_agent_forwarding_flag,
624 	    &cert_no_x11_forwarding_flag,
625 	    &cert_no_pty_flag,
626 	    &cert_no_user_rc,
627 	    NULL, NULL) == -1)
628 		return -1;
629 
630 	no_port_forwarding_flag |= cert_no_port_forwarding_flag;
631 	no_agent_forwarding_flag |= cert_no_agent_forwarding_flag;
632 	no_x11_forwarding_flag |= cert_no_x11_forwarding_flag;
633 	no_pty_flag |= cert_no_pty_flag;
634 	no_user_rc |= cert_no_user_rc;
635 	/* CA-specified forced command supersedes key option */
636 	if (cert_forced_command != NULL) {
637 		free(forced_command);
638 		forced_command = cert_forced_command;
639 	}
640 	return 0;
641 }
642 
643