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