Annotation of src/usr.bin/ssh/auth-options.c, Revision 1.66
1.66 ! djm 1: /* $OpenBSD: auth-options.c,v 1.65 2015/01/14 10:30:34 markus 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.26 markus 209: if (options.permit_user_env &&
210: strncasecmp(opts, cp, strlen(cp)) == 0) {
1.1 markus 211: char *s;
212: struct envstring *new_envstring;
1.15 markus 213:
1.12 markus 214: opts += strlen(cp);
215: s = xmalloc(strlen(opts) + 1);
1.1 markus 216: i = 0;
1.12 markus 217: while (*opts) {
218: if (*opts == '"')
1.1 markus 219: break;
1.12 markus 220: if (*opts == '\\' && opts[1] == '"') {
221: opts += 2;
1.1 markus 222: s[i++] = '"';
223: continue;
224: }
1.12 markus 225: s[i++] = *opts++;
1.1 markus 226: }
1.12 markus 227: if (!*opts) {
1.1 markus 228: debug("%.100s, line %lu: missing end quote",
1.10 markus 229: file, linenum);
1.24 markus 230: auth_debug_add("%.100s, line %lu: missing end quote",
1.10 markus 231: file, linenum);
1.58 djm 232: free(s);
1.14 markus 233: goto bad_option;
1.1 markus 234: }
1.38 dtucker 235: s[i] = '\0';
1.24 markus 236: auth_debug_add("Adding to environment: %.900s", s);
1.1 markus 237: debug("Adding to environment: %.900s", s);
1.12 markus 238: opts++;
1.61 djm 239: new_envstring = xcalloc(1, sizeof(struct envstring));
1.1 markus 240: new_envstring->s = s;
241: new_envstring->next = custom_environment;
242: custom_environment = new_envstring;
243: goto next_option;
244: }
245: cp = "from=\"";
1.12 markus 246: if (strncasecmp(opts, cp, strlen(cp)) == 0) {
247: const char *remote_ip = get_remote_ipaddr();
248: const char *remote_host = get_canonical_hostname(
1.28 markus 249: options.use_dns);
1.12 markus 250: char *patterns = xmalloc(strlen(opts) + 1);
1.15 markus 251:
1.12 markus 252: opts += strlen(cp);
1.1 markus 253: i = 0;
1.12 markus 254: while (*opts) {
255: if (*opts == '"')
1.1 markus 256: break;
1.12 markus 257: if (*opts == '\\' && opts[1] == '"') {
258: opts += 2;
1.1 markus 259: patterns[i++] = '"';
260: continue;
261: }
1.12 markus 262: patterns[i++] = *opts++;
1.1 markus 263: }
1.12 markus 264: if (!*opts) {
1.1 markus 265: debug("%.100s, line %lu: missing end quote",
1.10 markus 266: file, linenum);
1.24 markus 267: auth_debug_add("%.100s, line %lu: missing end quote",
1.10 markus 268: file, linenum);
1.58 djm 269: free(patterns);
1.14 markus 270: goto bad_option;
1.1 markus 271: }
1.38 dtucker 272: patterns[i] = '\0';
1.12 markus 273: opts++;
1.43 djm 274: switch (match_host_and_ip(remote_host, remote_ip,
275: patterns)) {
276: case 1:
1.58 djm 277: free(patterns);
1.43 djm 278: /* Host name matches. */
279: goto next_option;
280: case -1:
281: debug("%.100s, line %lu: invalid criteria",
282: file, linenum);
283: auth_debug_add("%.100s, line %lu: "
284: "invalid criteria", file, linenum);
285: /* FALLTHROUGH */
286: case 0:
1.58 djm 287: free(patterns);
1.27 itojun 288: logit("Authentication tried for %.100s with "
1.12 markus 289: "correct key but not from a permitted "
290: "host (host=%.200s, ip=%.200s).",
291: pw->pw_name, remote_host, remote_ip);
1.24 markus 292: auth_debug_add("Your host '%.200s' is not "
1.12 markus 293: "permitted to use this key for login.",
294: remote_host);
1.43 djm 295: break;
1.1 markus 296: }
1.43 djm 297: /* deny access */
298: return 0;
1.15 markus 299: }
300: cp = "permitopen=\"";
301: if (strncasecmp(opts, cp, strlen(cp)) == 0) {
1.29 djm 302: char *host, *p;
1.44 djm 303: int port;
1.15 markus 304: char *patterns = xmalloc(strlen(opts) + 1);
305:
306: opts += strlen(cp);
307: i = 0;
308: while (*opts) {
309: if (*opts == '"')
310: break;
311: if (*opts == '\\' && opts[1] == '"') {
312: opts += 2;
313: patterns[i++] = '"';
314: continue;
315: }
316: patterns[i++] = *opts++;
317: }
318: if (!*opts) {
319: debug("%.100s, line %lu: missing end quote",
320: file, linenum);
1.29 djm 321: auth_debug_add("%.100s, line %lu: missing "
322: "end quote", file, linenum);
1.58 djm 323: free(patterns);
1.15 markus 324: goto bad_option;
325: }
1.38 dtucker 326: patterns[i] = '\0';
1.15 markus 327: opts++;
1.29 djm 328: p = patterns;
1.64 millert 329: /* XXX - add streamlocal support */
1.29 djm 330: host = hpdelim(&p);
331: if (host == NULL || strlen(host) >= NI_MAXHOST) {
332: debug("%.100s, line %lu: Bad permitopen "
1.31 deraadt 333: "specification <%.100s>", file, linenum,
1.29 djm 334: patterns);
1.24 markus 335: auth_debug_add("%.100s, line %lu: "
1.29 djm 336: "Bad permitopen specification", file,
337: linenum);
1.58 djm 338: free(patterns);
1.15 markus 339: goto bad_option;
340: }
1.30 deraadt 341: host = cleanhostname(host);
1.55 dtucker 342: if (p == NULL || (port = permitopen_port(p)) < 0) {
1.29 djm 343: debug("%.100s, line %lu: Bad permitopen port "
344: "<%.100s>", file, linenum, p ? p : "");
1.24 markus 345: auth_debug_add("%.100s, line %lu: "
1.20 stevesk 346: "Bad permitopen port", file, linenum);
1.58 djm 347: free(patterns);
1.15 markus 348: goto bad_option;
349: }
1.57 djm 350: if ((options.allow_tcp_forwarding & FORWARD_LOCAL) != 0)
1.20 stevesk 351: channel_add_permitted_opens(host, port);
1.58 djm 352: free(patterns);
1.32 reyk 353: goto next_option;
354: }
355: cp = "tunnel=\"";
356: if (strncasecmp(opts, cp, strlen(cp)) == 0) {
357: char *tun = NULL;
358: opts += strlen(cp);
359: tun = xmalloc(strlen(opts) + 1);
360: i = 0;
361: while (*opts) {
362: if (*opts == '"')
363: break;
364: tun[i++] = *opts++;
365: }
366: if (!*opts) {
367: debug("%.100s, line %lu: missing end quote",
368: file, linenum);
369: auth_debug_add("%.100s, line %lu: missing end quote",
370: file, linenum);
1.58 djm 371: free(tun);
1.32 reyk 372: forced_tun_device = -1;
373: goto bad_option;
374: }
1.38 dtucker 375: tun[i] = '\0';
1.32 reyk 376: forced_tun_device = a2tun(tun, NULL);
1.58 djm 377: free(tun);
1.33 reyk 378: if (forced_tun_device == SSH_TUNID_ERR) {
1.32 reyk 379: debug("%.100s, line %lu: invalid tun device",
380: file, linenum);
381: auth_debug_add("%.100s, line %lu: invalid tun device",
382: file, linenum);
383: forced_tun_device = -1;
384: goto bad_option;
385: }
386: auth_debug_add("Forced tun device: %d", forced_tun_device);
387: opts++;
1.1 markus 388: goto next_option;
389: }
390: next_option:
391: /*
392: * Skip the comma, and move to the next option
393: * (or break out if there are no more).
394: */
1.12 markus 395: if (!*opts)
1.1 markus 396: fatal("Bugs in auth-options.c option processing.");
1.12 markus 397: if (*opts == ' ' || *opts == '\t')
1.1 markus 398: break; /* End of options. */
1.12 markus 399: if (*opts != ',')
1.1 markus 400: goto bad_option;
1.12 markus 401: opts++;
1.1 markus 402: /* Process the next option. */
403: }
1.22 provos 404:
1.1 markus 405: /* grant access */
406: return 1;
407:
408: bad_option:
1.27 itojun 409: logit("Bad options in %.100s file, line %lu: %.50s",
1.12 markus 410: file, linenum, opts);
1.24 markus 411: auth_debug_add("Bad options in %.100s file, line %lu: %.50s",
1.12 markus 412: file, linenum, opts);
1.22 provos 413:
1.1 markus 414: /* deny access */
415: return 0;
416: }
1.45 djm 417:
1.52 djm 418: #define OPTIONS_CRITICAL 1
419: #define OPTIONS_EXTENSIONS 2
420: static int
1.65 markus 421: parse_option_list(struct sshbuf *oblob, struct passwd *pw,
1.52 djm 422: u_int which, int crit,
423: int *cert_no_port_forwarding_flag,
424: int *cert_no_agent_forwarding_flag,
425: int *cert_no_x11_forwarding_flag,
426: int *cert_no_pty_flag,
427: int *cert_no_user_rc,
428: char **cert_forced_command,
429: int *cert_source_address_done)
1.45 djm 430: {
1.52 djm 431: char *command, *allowed;
432: const char *remote_ip;
1.59 djm 433: char *name = NULL;
1.65 markus 434: struct sshbuf *c = NULL, *data = NULL;
435: int r, ret = -1, result, found;
436:
437: if ((c = sshbuf_fromb(oblob)) == NULL) {
438: error("%s: sshbuf_fromb failed", __func__);
439: goto out;
440: }
441:
442: while (sshbuf_len(c) > 0) {
443: sshbuf_free(data);
444: data = NULL;
445: if ((r = sshbuf_get_cstring(c, &name, NULL)) != 0 ||
446: (r = sshbuf_froms(c, &data)) != 0) {
447: error("Unable to parse certificate options: %s",
448: ssh_err(r));
1.45 djm 449: goto out;
450: }
1.65 markus 451: debug3("found certificate option \"%.100s\" len %zu",
452: name, sshbuf_len(data));
1.52 djm 453: found = 0;
454: if ((which & OPTIONS_EXTENSIONS) != 0) {
455: if (strcmp(name, "permit-X11-forwarding") == 0) {
456: *cert_no_x11_forwarding_flag = 0;
457: found = 1;
458: } else if (strcmp(name,
459: "permit-agent-forwarding") == 0) {
460: *cert_no_agent_forwarding_flag = 0;
461: found = 1;
462: } else if (strcmp(name,
463: "permit-port-forwarding") == 0) {
464: *cert_no_port_forwarding_flag = 0;
465: found = 1;
466: } else if (strcmp(name, "permit-pty") == 0) {
467: *cert_no_pty_flag = 0;
468: found = 1;
469: } else if (strcmp(name, "permit-user-rc") == 0) {
470: *cert_no_user_rc = 0;
471: found = 1;
472: }
473: }
474: if (!found && (which & OPTIONS_CRITICAL) != 0) {
475: if (strcmp(name, "force-command") == 0) {
1.65 markus 476: if ((r = sshbuf_get_cstring(data, &command,
477: NULL)) != 0) {
478: error("Unable to parse \"%s\" "
479: "section: %s", name, ssh_err(r));
1.52 djm 480: goto out;
481: }
482: if (*cert_forced_command != NULL) {
483: error("Certificate has multiple "
484: "force-command options");
1.58 djm 485: free(command);
1.52 djm 486: goto out;
487: }
488: *cert_forced_command = command;
489: found = 1;
1.45 djm 490: }
1.52 djm 491: if (strcmp(name, "source-address") == 0) {
1.65 markus 492: if ((r = sshbuf_get_cstring(data, &allowed,
493: NULL)) != 0) {
494: error("Unable to parse \"%s\" "
495: "section: %s", name, ssh_err(r));
1.52 djm 496: goto out;
497: }
498: if ((*cert_source_address_done)++) {
499: error("Certificate has multiple "
500: "source-address options");
1.58 djm 501: free(allowed);
1.52 djm 502: goto out;
503: }
504: remote_ip = get_remote_ipaddr();
1.62 djm 505: result = addr_match_cidr_list(remote_ip,
506: allowed);
507: free(allowed);
508: switch (result) {
1.52 djm 509: case 1:
510: /* accepted */
511: break;
512: case 0:
513: /* no match */
514: logit("Authentication tried for %.100s "
515: "with valid certificate but not "
516: "from a permitted host "
517: "(ip=%.200s).", pw->pw_name,
518: remote_ip);
519: auth_debug_add("Your address '%.200s' "
520: "is not permitted to use this "
521: "certificate for login.",
522: remote_ip);
523: goto out;
524: case -1:
1.62 djm 525: default:
1.52 djm 526: error("Certificate source-address "
527: "contents invalid");
528: goto out;
529: }
530: found = 1;
1.46 djm 531: }
1.52 djm 532: }
533:
534: if (!found) {
535: if (crit) {
536: error("Certificate critical option \"%s\" "
537: "is not supported", name);
1.45 djm 538: goto out;
1.52 djm 539: } else {
540: logit("Certificate extension \"%s\" "
541: "is not supported", name);
1.45 djm 542: }
1.65 markus 543: } else if (sshbuf_len(data) != 0) {
1.52 djm 544: error("Certificate option \"%s\" corrupt "
1.45 djm 545: "(extra data)", name);
546: goto out;
547: }
1.58 djm 548: free(name);
1.59 djm 549: name = NULL;
1.45 djm 550: }
1.50 djm 551: /* successfully parsed all options */
1.45 djm 552: ret = 0;
553:
1.52 djm 554: out:
555: if (ret != 0 &&
556: cert_forced_command != NULL &&
557: *cert_forced_command != NULL) {
1.58 djm 558: free(*cert_forced_command);
1.52 djm 559: *cert_forced_command = NULL;
560: }
561: if (name != NULL)
1.58 djm 562: free(name);
1.65 markus 563: sshbuf_free(data);
564: sshbuf_free(c);
1.52 djm 565: return ret;
566: }
567:
568: /*
569: * Set options from critical certificate options. These supersede user key
570: * options so this must be called after auth_parse_options().
571: */
572: int
1.65 markus 573: auth_cert_options(struct sshkey *k, struct passwd *pw)
1.52 djm 574: {
575: int cert_no_port_forwarding_flag = 1;
576: int cert_no_agent_forwarding_flag = 1;
577: int cert_no_x11_forwarding_flag = 1;
578: int cert_no_pty_flag = 1;
579: int cert_no_user_rc = 1;
580: char *cert_forced_command = NULL;
581: int cert_source_address_done = 0;
582:
1.65 markus 583: if (sshkey_cert_is_legacy(k)) {
1.52 djm 584: /* All options are in the one field for v00 certs */
1.65 markus 585: if (parse_option_list(k->cert->critical, pw,
1.52 djm 586: OPTIONS_CRITICAL|OPTIONS_EXTENSIONS, 1,
587: &cert_no_port_forwarding_flag,
588: &cert_no_agent_forwarding_flag,
589: &cert_no_x11_forwarding_flag,
590: &cert_no_pty_flag,
591: &cert_no_user_rc,
592: &cert_forced_command,
593: &cert_source_address_done) == -1)
594: return -1;
595: } else {
596: /* Separate options and extensions for v01 certs */
1.65 markus 597: if (parse_option_list(k->cert->critical, pw,
1.52 djm 598: OPTIONS_CRITICAL, 1, NULL, NULL, NULL, NULL, NULL,
599: &cert_forced_command,
600: &cert_source_address_done) == -1)
601: return -1;
1.65 markus 602: if (parse_option_list(k->cert->extensions, pw,
1.66 ! djm 603: OPTIONS_EXTENSIONS, 0,
1.52 djm 604: &cert_no_port_forwarding_flag,
605: &cert_no_agent_forwarding_flag,
606: &cert_no_x11_forwarding_flag,
607: &cert_no_pty_flag,
608: &cert_no_user_rc,
609: NULL, NULL) == -1)
610: return -1;
611: }
612:
1.45 djm 613: no_port_forwarding_flag |= cert_no_port_forwarding_flag;
614: no_agent_forwarding_flag |= cert_no_agent_forwarding_flag;
615: no_x11_forwarding_flag |= cert_no_x11_forwarding_flag;
616: no_pty_flag |= cert_no_pty_flag;
617: no_user_rc |= cert_no_user_rc;
618: /* CA-specified forced command supersedes key option */
619: if (cert_forced_command != NULL) {
620: if (forced_command != NULL)
1.58 djm 621: free(forced_command);
1.45 djm 622: forced_command = cert_forced_command;
623: }
1.52 djm 624: return 0;
1.45 djm 625: }
626: