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