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