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