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