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