xref: /netbsd-src/crypto/external/bsd/openssh/dist/auth-options.c (revision d909946ca08dceb44d7d0f22ec9488679695d976)
1 /*	$NetBSD: auth-options.c,v 1.13 2016/08/02 13:45:12 christos Exp $	*/
2 /* $OpenBSD: auth-options.c,v 1.71 2016/03/07 19:02:43 djm Exp $ */
3 /*
4  * Author: Tatu Ylonen <ylo@cs.hut.fi>
5  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
6  *                    All rights reserved
7  * As far as I am concerned, the code I have written for this software
8  * can be used freely for any purpose.  Any derived versions of this
9  * software must be clearly marked as such, and if the derived work is
10  * incompatible with the protocol description in the RFC file, it must be
11  * called by a name other than "ssh" or "Secure Shell".
12  */
13 
14 #include "includes.h"
15 __RCSID("$NetBSD: auth-options.c,v 1.13 2016/08/02 13:45:12 christos Exp $");
16 #include <sys/types.h>
17 #include <sys/queue.h>
18 
19 #include <netdb.h>
20 #include <pwd.h>
21 #include <string.h>
22 #include <stdio.h>
23 #include <stdarg.h>
24 #include <time.h>
25 
26 #include "key.h"	/* XXX for typedef */
27 #include "buffer.h"	/* XXX for typedef */
28 #include "xmalloc.h"
29 #include "match.h"
30 #include "ssherr.h"
31 #include "log.h"
32 #include "canohost.h"
33 #include "packet.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 	struct ssh *ssh = active_state;		/* XXX */
127 	const char *cp;
128 	int i, r;
129 
130 	/* reset options */
131 	auth_clear_options();
132 
133 	if (!opts)
134 		return 1;
135 
136 	while (*opts && *opts != ' ' && *opts != '\t') {
137 		if ((r = match_flag("cert-authority", 0, &opts, NULL)) != -1) {
138 			key_is_cert_authority = r;
139 			goto next_option;
140 		}
141 		if ((r = match_flag("restrict", 0, &opts, NULL)) != -1) {
142 			auth_debug_add("Key is restricted.");
143 			no_port_forwarding_flag = 1;
144 			no_agent_forwarding_flag = 1;
145 			no_x11_forwarding_flag = 1;
146 			no_pty_flag = 1;
147 			no_user_rc = 1;
148 			goto next_option;
149 		}
150 		if ((r = match_flag("port-forwarding", 1, &opts,
151 		    "Port forwarding")) != -1) {
152 			no_port_forwarding_flag = r != 1;
153 			goto next_option;
154 		}
155 		if ((r = match_flag("agent-forwarding", 1, &opts,
156 		    "Agent forwarding")) != -1) {
157 			no_agent_forwarding_flag = r != 1;
158 			goto next_option;
159 		}
160 		if ((r = match_flag("x11-forwarding", 1, &opts,
161 		    "X11 forwarding")) != -1) {
162 			no_x11_forwarding_flag = r != 1;
163 			goto next_option;
164 		}
165 		if ((r = match_flag("pty", 1, &opts,
166 		    "PTY allocation")) != -1) {
167 			no_pty_flag = r != 1;
168 			goto next_option;
169 		}
170 		if ((r = match_flag("user-rc", 1, &opts,
171 		    "User rc execution")) != -1) {
172 			no_user_rc = r != 1;
173 			goto next_option;
174 		}
175 		cp = "command=\"";
176 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
177 			opts += strlen(cp);
178 			free(forced_command);
179 			forced_command = xmalloc(strlen(opts) + 1);
180 			i = 0;
181 			while (*opts) {
182 				if (*opts == '"')
183 					break;
184 				if (*opts == '\\' && opts[1] == '"') {
185 					opts += 2;
186 					forced_command[i++] = '"';
187 					continue;
188 				}
189 				forced_command[i++] = *opts++;
190 			}
191 			if (!*opts) {
192 				debug("%.100s, line %lu: missing end quote",
193 				    file, linenum);
194 				auth_debug_add("%.100s, line %lu: missing end quote",
195 				    file, linenum);
196 				free(forced_command);
197 				forced_command = NULL;
198 				goto bad_option;
199 			}
200 			forced_command[i] = '\0';
201 			auth_debug_add("Forced command.");
202 			opts++;
203 			goto next_option;
204 		}
205 		cp = "principals=\"";
206 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
207 			opts += strlen(cp);
208 			free(authorized_principals);
209 			authorized_principals = xmalloc(strlen(opts) + 1);
210 			i = 0;
211 			while (*opts) {
212 				if (*opts == '"')
213 					break;
214 				if (*opts == '\\' && opts[1] == '"') {
215 					opts += 2;
216 					authorized_principals[i++] = '"';
217 					continue;
218 				}
219 				authorized_principals[i++] = *opts++;
220 			}
221 			if (!*opts) {
222 				debug("%.100s, line %lu: missing end quote",
223 				    file, linenum);
224 				auth_debug_add("%.100s, line %lu: missing end quote",
225 				    file, linenum);
226 				free(authorized_principals);
227 				authorized_principals = NULL;
228 				goto bad_option;
229 			}
230 			authorized_principals[i] = '\0';
231 			auth_debug_add("principals: %.900s",
232 			    authorized_principals);
233 			opts++;
234 			goto next_option;
235 		}
236 		cp = "environment=\"";
237 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
238 			char *s;
239 			struct envstring *new_envstring;
240 
241 			opts += strlen(cp);
242 			s = xmalloc(strlen(opts) + 1);
243 			i = 0;
244 			while (*opts) {
245 				if (*opts == '"')
246 					break;
247 				if (*opts == '\\' && opts[1] == '"') {
248 					opts += 2;
249 					s[i++] = '"';
250 					continue;
251 				}
252 				s[i++] = *opts++;
253 			}
254 			if (!*opts) {
255 				debug("%.100s, line %lu: missing end quote",
256 				    file, linenum);
257 				auth_debug_add("%.100s, line %lu: missing end quote",
258 				    file, linenum);
259 				free(s);
260 				goto bad_option;
261 			}
262 			s[i] = '\0';
263 			opts++;
264 			if (options.permit_user_env) {
265 				auth_debug_add("Adding to environment: "
266 				    "%.900s", s);
267 				debug("Adding to environment: %.900s", s);
268 				new_envstring = xcalloc(1,
269 				    sizeof(*new_envstring));
270 				new_envstring->s = s;
271 				new_envstring->next = custom_environment;
272 				custom_environment = new_envstring;
273 				s = NULL;
274 			}
275 			free(s);
276 			goto next_option;
277 		}
278 		cp = "from=\"";
279 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
280 			const char *remote_ip = ssh_remote_ipaddr(ssh);
281 			const char *remote_host = auth_get_canonical_hostname(
282 			    ssh, options.use_dns);
283 			char *patterns = xmalloc(strlen(opts) + 1);
284 
285 			opts += strlen(cp);
286 			i = 0;
287 			while (*opts) {
288 				if (*opts == '"')
289 					break;
290 				if (*opts == '\\' && opts[1] == '"') {
291 					opts += 2;
292 					patterns[i++] = '"';
293 					continue;
294 				}
295 				patterns[i++] = *opts++;
296 			}
297 			if (!*opts) {
298 				debug("%.100s, line %lu: missing end quote",
299 				    file, linenum);
300 				auth_debug_add("%.100s, line %lu: missing end quote",
301 				    file, linenum);
302 				free(patterns);
303 				goto bad_option;
304 			}
305 			patterns[i] = '\0';
306 			opts++;
307 			switch (match_host_and_ip(remote_host, remote_ip,
308 			    patterns)) {
309 			case 1:
310 				free(patterns);
311 				/* Host name matches. */
312 				goto next_option;
313 			case -1:
314 				debug("%.100s, line %lu: invalid criteria",
315 				    file, linenum);
316 				auth_debug_add("%.100s, line %lu: "
317 				    "invalid criteria", file, linenum);
318 				/* FALLTHROUGH */
319 			case 0:
320 				free(patterns);
321 				logit("Authentication tried for %.100s with "
322 				    "correct key but not from a permitted "
323 				    "host (host=%.200s, ip=%.200s).",
324 				    pw->pw_name, remote_host, remote_ip);
325 				auth_debug_add("Your host '%.200s' is not "
326 				    "permitted to use this key for login.",
327 				    remote_host);
328 				break;
329 			}
330 			/* deny access */
331 			return 0;
332 		}
333 		cp = "permitopen=\"";
334 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
335 			char *host, *p;
336 			int port;
337 			char *patterns = xmalloc(strlen(opts) + 1);
338 
339 			opts += strlen(cp);
340 			i = 0;
341 			while (*opts) {
342 				if (*opts == '"')
343 					break;
344 				if (*opts == '\\' && opts[1] == '"') {
345 					opts += 2;
346 					patterns[i++] = '"';
347 					continue;
348 				}
349 				patterns[i++] = *opts++;
350 			}
351 			if (!*opts) {
352 				debug("%.100s, line %lu: missing end quote",
353 				    file, linenum);
354 				auth_debug_add("%.100s, line %lu: missing "
355 				    "end quote", file, linenum);
356 				free(patterns);
357 				goto bad_option;
358 			}
359 			patterns[i] = '\0';
360 			opts++;
361 			p = patterns;
362 			/* XXX - add streamlocal support */
363 			host = hpdelim(&p);
364 			if (host == NULL || strlen(host) >= NI_MAXHOST) {
365 				debug("%.100s, line %lu: Bad permitopen "
366 				    "specification <%.100s>", file, linenum,
367 				    patterns);
368 				auth_debug_add("%.100s, line %lu: "
369 				    "Bad permitopen specification", file,
370 				    linenum);
371 				free(patterns);
372 				goto bad_option;
373 			}
374 			host = cleanhostname(host);
375 			if (p == NULL || (port = permitopen_port(p)) < 0) {
376 				debug("%.100s, line %lu: Bad permitopen port "
377 				    "<%.100s>", file, linenum, p ? p : "");
378 				auth_debug_add("%.100s, line %lu: "
379 				    "Bad permitopen port", file, linenum);
380 				free(patterns);
381 				goto bad_option;
382 			}
383 			if ((options.allow_tcp_forwarding & FORWARD_LOCAL) != 0)
384 				channel_add_permitted_opens(host, port);
385 			free(patterns);
386 			goto next_option;
387 		}
388 		cp = "tunnel=\"";
389 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
390 			char *tun = NULL;
391 			opts += strlen(cp);
392 			tun = xmalloc(strlen(opts) + 1);
393 			i = 0;
394 			while (*opts) {
395 				if (*opts == '"')
396 					break;
397 				tun[i++] = *opts++;
398 			}
399 			if (!*opts) {
400 				debug("%.100s, line %lu: missing end quote",
401 				    file, linenum);
402 				auth_debug_add("%.100s, line %lu: missing end quote",
403 				    file, linenum);
404 				free(tun);
405 				forced_tun_device = -1;
406 				goto bad_option;
407 			}
408 			tun[i] = '\0';
409 			forced_tun_device = a2tun(tun, NULL);
410 			free(tun);
411 			if (forced_tun_device == SSH_TUNID_ERR) {
412 				debug("%.100s, line %lu: invalid tun device",
413 				    file, linenum);
414 				auth_debug_add("%.100s, line %lu: invalid tun device",
415 				    file, linenum);
416 				forced_tun_device = -1;
417 				goto bad_option;
418 			}
419 			auth_debug_add("Forced tun device: %d", forced_tun_device);
420 			opts++;
421 			goto next_option;
422 		}
423 next_option:
424 		/*
425 		 * Skip the comma, and move to the next option
426 		 * (or break out if there are no more).
427 		 */
428 		if (!*opts)
429 			fatal("Bugs in auth-options.c option processing.");
430 		if (*opts == ' ' || *opts == '\t')
431 			break;		/* End of options. */
432 		if (*opts != ',')
433 			goto bad_option;
434 		opts++;
435 		/* Process the next option. */
436 	}
437 
438 	/* grant access */
439 	return 1;
440 
441 bad_option:
442 	logit("Bad options in %.100s file, line %lu: %.50s",
443 	    file, linenum, opts);
444 	auth_debug_add("Bad options in %.100s file, line %lu: %.50s",
445 	    file, linenum, opts);
446 
447 	/* deny access */
448 	return 0;
449 }
450 
451 #define OPTIONS_CRITICAL	1
452 #define OPTIONS_EXTENSIONS	2
453 static int
454 parse_option_list(struct sshbuf *oblob, struct passwd *pw,
455     u_int which, int crit,
456     int *cert_no_port_forwarding_flag,
457     int *cert_no_agent_forwarding_flag,
458     int *cert_no_x11_forwarding_flag,
459     int *cert_no_pty_flag,
460     int *cert_no_user_rc,
461     char **cert_forced_command,
462     int *cert_source_address_done)
463 {
464 	struct ssh *ssh = active_state;		/* XXX */
465 	char *command, *allowed;
466 	const char *remote_ip;
467 	char *name = NULL;
468 	struct sshbuf *c = NULL, *data = NULL;
469 	int r, ret = -1, result, found;
470 
471 	if ((c = sshbuf_fromb(oblob)) == NULL) {
472 		error("%s: sshbuf_fromb failed", __func__);
473 		goto out;
474 	}
475 
476 	while (sshbuf_len(c) > 0) {
477 		sshbuf_free(data);
478 		data = NULL;
479 		if ((r = sshbuf_get_cstring(c, &name, NULL)) != 0 ||
480 		    (r = sshbuf_froms(c, &data)) != 0) {
481 			error("Unable to parse certificate options: %s",
482 			    ssh_err(r));
483 			goto out;
484 		}
485 		debug3("found certificate option \"%.100s\" len %zu",
486 		    name, sshbuf_len(data));
487 		found = 0;
488 		if ((which & OPTIONS_EXTENSIONS) != 0) {
489 			if (strcmp(name, "permit-X11-forwarding") == 0) {
490 				*cert_no_x11_forwarding_flag = 0;
491 				found = 1;
492 			} else if (strcmp(name,
493 			    "permit-agent-forwarding") == 0) {
494 				*cert_no_agent_forwarding_flag = 0;
495 				found = 1;
496 			} else if (strcmp(name,
497 			    "permit-port-forwarding") == 0) {
498 				*cert_no_port_forwarding_flag = 0;
499 				found = 1;
500 			} else if (strcmp(name, "permit-pty") == 0) {
501 				*cert_no_pty_flag = 0;
502 				found = 1;
503 			} else if (strcmp(name, "permit-user-rc") == 0) {
504 				*cert_no_user_rc = 0;
505 				found = 1;
506 			}
507 		}
508 		if (!found && (which & OPTIONS_CRITICAL) != 0) {
509 			if (strcmp(name, "force-command") == 0) {
510 				if ((r = sshbuf_get_cstring(data, &command,
511 				    NULL)) != 0) {
512 					error("Unable to parse \"%s\" "
513 					    "section: %s", name, ssh_err(r));
514 					goto out;
515 				}
516 				if (*cert_forced_command != NULL) {
517 					error("Certificate has multiple "
518 					    "force-command options");
519 					free(command);
520 					goto out;
521 				}
522 				*cert_forced_command = command;
523 				found = 1;
524 			}
525 			if (strcmp(name, "source-address") == 0) {
526 				if ((r = sshbuf_get_cstring(data, &allowed,
527 				    NULL)) != 0) {
528 					error("Unable to parse \"%s\" "
529 					    "section: %s", name, ssh_err(r));
530 					goto out;
531 				}
532 				if ((*cert_source_address_done)++) {
533 					error("Certificate has multiple "
534 					    "source-address options");
535 					free(allowed);
536 					goto out;
537 				}
538 				remote_ip = ssh_remote_ipaddr(ssh);
539 				result = addr_match_cidr_list(remote_ip,
540 				    allowed);
541 				free(allowed);
542 				switch (result) {
543 				case 1:
544 					/* accepted */
545 					break;
546 				case 0:
547 					/* no match */
548 					logit("Authentication tried for %.100s "
549 					    "with valid certificate but not "
550 					    "from a permitted host "
551 					    "(ip=%.200s).", pw->pw_name,
552 					    remote_ip);
553 					auth_debug_add("Your address '%.200s' "
554 					    "is not permitted to use this "
555 					    "certificate for login.",
556 					    remote_ip);
557 					goto out;
558 				case -1:
559 				default:
560 					error("Certificate source-address "
561 					    "contents invalid");
562 					goto out;
563 				}
564 				found = 1;
565 			}
566 		}
567 
568 		if (!found) {
569 			if (crit) {
570 				error("Certificate critical option \"%s\" "
571 				    "is not supported", name);
572 				goto out;
573 			} else {
574 				logit("Certificate extension \"%s\" "
575 				    "is not supported", name);
576 			}
577 		} else if (sshbuf_len(data) != 0) {
578 			error("Certificate option \"%s\" corrupt "
579 			    "(extra data)", name);
580 			goto out;
581 		}
582 		free(name);
583 		name = NULL;
584 	}
585 	/* successfully parsed all options */
586 	ret = 0;
587 
588  out:
589 	if (ret != 0 &&
590 	    cert_forced_command != NULL &&
591 	    *cert_forced_command != NULL) {
592 		free(*cert_forced_command);
593 		*cert_forced_command = NULL;
594 	}
595 	free(name);
596 	sshbuf_free(data);
597 	sshbuf_free(c);
598 	return ret;
599 }
600 
601 /*
602  * Set options from critical certificate options. These supersede user key
603  * options so this must be called after auth_parse_options().
604  */
605 int
606 auth_cert_options(struct sshkey *k, struct passwd *pw)
607 {
608 	int cert_no_port_forwarding_flag = 1;
609 	int cert_no_agent_forwarding_flag = 1;
610 	int cert_no_x11_forwarding_flag = 1;
611 	int cert_no_pty_flag = 1;
612 	int cert_no_user_rc = 1;
613 	char *cert_forced_command = NULL;
614 	int cert_source_address_done = 0;
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 	/* CA-specified forced command supersedes key option */
638 	if (cert_forced_command != NULL) {
639 		free(forced_command);
640 		forced_command = cert_forced_command;
641 	}
642 	return 0;
643 }
644 
645