diff options
author | djm@openbsd.org <djm@openbsd.org> | 2018-11-23 05:08:07 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2018-11-23 16:09:12 +1100 |
commit | 9e34e0c59ab04514f9de9934a772283f7f372afe (patch) | |
tree | 4306e1438b1efe0283b635d4d4ed1256cff0fe59 | |
parent | 4da58d58736b065b1182b563d10ad6765d811c6d (diff) |
upstream: add a ssh_config "Match final" predicate
Matches in same pass as "Match canonical" but doesn't require
hostname canonicalisation be enabled. bz#2906 ok markus
OpenBSD-Commit-ID: fba1dfe9f6e0cabcd0e2b3be13f7a434199beffa
-rw-r--r-- | readconf.c | 44 | ||||
-rw-r--r-- | readconf.h | 6 | ||||
-rw-r--r-- | ssh-keysign.c | 5 | ||||
-rw-r--r-- | ssh.c | 31 | ||||
-rw-r--r-- | ssh_config.5 | 26 |
5 files changed, 76 insertions, 36 deletions
diff --git a/readconf.c b/readconf.c index 7850f2f59..7331ef5ad 100644 --- a/readconf.c +++ b/readconf.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: readconf.c,v 1.301 2018/11/16 03:26:01 djm Exp $ */ | 1 | /* $OpenBSD: readconf.c,v 1.302 2018/11/23 05:08:07 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -133,10 +133,11 @@ | |||
133 | 133 | ||
134 | static int read_config_file_depth(const char *filename, struct passwd *pw, | 134 | static int read_config_file_depth(const char *filename, struct passwd *pw, |
135 | const char *host, const char *original_host, Options *options, | 135 | const char *host, const char *original_host, Options *options, |
136 | int flags, int *activep, int depth); | 136 | int flags, int *activep, int *want_final_pass, int depth); |
137 | static int process_config_line_depth(Options *options, struct passwd *pw, | 137 | static int process_config_line_depth(Options *options, struct passwd *pw, |
138 | const char *host, const char *original_host, char *line, | 138 | const char *host, const char *original_host, char *line, |
139 | const char *filename, int linenum, int *activep, int flags, int depth); | 139 | const char *filename, int linenum, int *activep, int flags, |
140 | int *want_final_pass, int depth); | ||
140 | 141 | ||
141 | /* Keyword tokens. */ | 142 | /* Keyword tokens. */ |
142 | 143 | ||
@@ -539,8 +540,8 @@ execute_in_shell(const char *cmd) | |||
539 | */ | 540 | */ |
540 | static int | 541 | static int |
541 | match_cfg_line(Options *options, char **condition, struct passwd *pw, | 542 | match_cfg_line(Options *options, char **condition, struct passwd *pw, |
542 | const char *host_arg, const char *original_host, int post_canon, | 543 | const char *host_arg, const char *original_host, int final_pass, |
543 | const char *filename, int linenum) | 544 | int *want_final_pass, const char *filename, int linenum) |
544 | { | 545 | { |
545 | char *arg, *oattrib, *attrib, *cmd, *cp = *condition, *host, *criteria; | 546 | char *arg, *oattrib, *attrib, *cmd, *cp = *condition, *host, *criteria; |
546 | const char *ruser; | 547 | const char *ruser; |
@@ -554,7 +555,7 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw, | |||
554 | */ | 555 | */ |
555 | port = options->port <= 0 ? default_ssh_port() : options->port; | 556 | port = options->port <= 0 ? default_ssh_port() : options->port; |
556 | ruser = options->user == NULL ? pw->pw_name : options->user; | 557 | ruser = options->user == NULL ? pw->pw_name : options->user; |
557 | if (post_canon) { | 558 | if (final_pass) { |
558 | host = xstrdup(options->hostname); | 559 | host = xstrdup(options->hostname); |
559 | } else if (options->hostname != NULL) { | 560 | } else if (options->hostname != NULL) { |
560 | /* NB. Please keep in sync with ssh.c:main() */ | 561 | /* NB. Please keep in sync with ssh.c:main() */ |
@@ -586,8 +587,16 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw, | |||
586 | goto out; | 587 | goto out; |
587 | } | 588 | } |
588 | attributes++; | 589 | attributes++; |
589 | if (strcasecmp(attrib, "canonical") == 0) { | 590 | if (strcasecmp(attrib, "canonical") == 0 || |
590 | r = !!post_canon; /* force bitmask member to boolean */ | 591 | strcasecmp(attrib, "final") == 0) { |
592 | /* | ||
593 | * If the config requests "Match final" then remember | ||
594 | * this so we can perform a second pass later. | ||
595 | */ | ||
596 | if (strcasecmp(attrib, "final") == 0 && | ||
597 | want_final_pass != NULL) | ||
598 | *want_final_pass = 1; | ||
599 | r = !!final_pass; /* force bitmask member to boolean */ | ||
591 | if (r == (negate ? 1 : 0)) | 600 | if (r == (negate ? 1 : 0)) |
592 | this_result = result = 0; | 601 | this_result = result = 0; |
593 | debug3("%.200s line %d: %smatched '%s'", | 602 | debug3("%.200s line %d: %smatched '%s'", |
@@ -824,14 +833,14 @@ process_config_line(Options *options, struct passwd *pw, const char *host, | |||
824 | int linenum, int *activep, int flags) | 833 | int linenum, int *activep, int flags) |
825 | { | 834 | { |
826 | return process_config_line_depth(options, pw, host, original_host, | 835 | return process_config_line_depth(options, pw, host, original_host, |
827 | line, filename, linenum, activep, flags, 0); | 836 | line, filename, linenum, activep, flags, NULL, 0); |
828 | } | 837 | } |
829 | 838 | ||
830 | #define WHITESPACE " \t\r\n" | 839 | #define WHITESPACE " \t\r\n" |
831 | static int | 840 | static int |
832 | process_config_line_depth(Options *options, struct passwd *pw, const char *host, | 841 | process_config_line_depth(Options *options, struct passwd *pw, const char *host, |
833 | const char *original_host, char *line, const char *filename, | 842 | const char *original_host, char *line, const char *filename, |
834 | int linenum, int *activep, int flags, int depth) | 843 | int linenum, int *activep, int flags, int *want_final_pass, int depth) |
835 | { | 844 | { |
836 | char *s, **charptr, *endofnumber, *keyword, *arg, *arg2; | 845 | char *s, **charptr, *endofnumber, *keyword, *arg, *arg2; |
837 | char **cpptr, fwdarg[256]; | 846 | char **cpptr, fwdarg[256]; |
@@ -1339,7 +1348,8 @@ parse_keytypes: | |||
1339 | fatal("Host directive not supported as a command-line " | 1348 | fatal("Host directive not supported as a command-line " |
1340 | "option"); | 1349 | "option"); |
1341 | value = match_cfg_line(options, &s, pw, host, original_host, | 1350 | value = match_cfg_line(options, &s, pw, host, original_host, |
1342 | flags & SSHCONF_POSTCANON, filename, linenum); | 1351 | flags & SSHCONF_FINAL, want_final_pass, |
1352 | filename, linenum); | ||
1343 | if (value < 0) | 1353 | if (value < 0) |
1344 | fatal("%.200s line %d: Bad Match condition", filename, | 1354 | fatal("%.200s line %d: Bad Match condition", filename, |
1345 | linenum); | 1355 | linenum); |
@@ -1548,7 +1558,7 @@ parse_keytypes: | |||
1548 | pw, host, original_host, options, | 1558 | pw, host, original_host, options, |
1549 | flags | SSHCONF_CHECKPERM | | 1559 | flags | SSHCONF_CHECKPERM | |
1550 | (oactive ? 0 : SSHCONF_NEVERMATCH), | 1560 | (oactive ? 0 : SSHCONF_NEVERMATCH), |
1551 | activep, depth + 1); | 1561 | activep, want_final_pass, depth + 1); |
1552 | if (r != 1 && errno != ENOENT) { | 1562 | if (r != 1 && errno != ENOENT) { |
1553 | fatal("Can't open user config file " | 1563 | fatal("Can't open user config file " |
1554 | "%.100s: %.100s", gl.gl_pathv[i], | 1564 | "%.100s: %.100s", gl.gl_pathv[i], |
@@ -1751,19 +1761,20 @@ parse_keytypes: | |||
1751 | */ | 1761 | */ |
1752 | int | 1762 | int |
1753 | read_config_file(const char *filename, struct passwd *pw, const char *host, | 1763 | read_config_file(const char *filename, struct passwd *pw, const char *host, |
1754 | const char *original_host, Options *options, int flags) | 1764 | const char *original_host, Options *options, int flags, |
1765 | int *want_final_pass) | ||
1755 | { | 1766 | { |
1756 | int active = 1; | 1767 | int active = 1; |
1757 | 1768 | ||
1758 | return read_config_file_depth(filename, pw, host, original_host, | 1769 | return read_config_file_depth(filename, pw, host, original_host, |
1759 | options, flags, &active, 0); | 1770 | options, flags, &active, want_final_pass, 0); |
1760 | } | 1771 | } |
1761 | 1772 | ||
1762 | #define READCONF_MAX_DEPTH 16 | 1773 | #define READCONF_MAX_DEPTH 16 |
1763 | static int | 1774 | static int |
1764 | read_config_file_depth(const char *filename, struct passwd *pw, | 1775 | read_config_file_depth(const char *filename, struct passwd *pw, |
1765 | const char *host, const char *original_host, Options *options, | 1776 | const char *host, const char *original_host, Options *options, |
1766 | int flags, int *activep, int depth) | 1777 | int flags, int *activep, int *want_final_pass, int depth) |
1767 | { | 1778 | { |
1768 | FILE *f; | 1779 | FILE *f; |
1769 | char *line = NULL; | 1780 | char *line = NULL; |
@@ -1798,7 +1809,8 @@ read_config_file_depth(const char *filename, struct passwd *pw, | |||
1798 | /* Update line number counter. */ | 1809 | /* Update line number counter. */ |
1799 | linenum++; | 1810 | linenum++; |
1800 | if (process_config_line_depth(options, pw, host, original_host, | 1811 | if (process_config_line_depth(options, pw, host, original_host, |
1801 | line, filename, linenum, activep, flags, depth) != 0) | 1812 | line, filename, linenum, activep, flags, want_final_pass, |
1813 | depth) != 0) | ||
1802 | bad_options++; | 1814 | bad_options++; |
1803 | } | 1815 | } |
1804 | free(line); | 1816 | free(line); |
diff --git a/readconf.h b/readconf.h index fc7e38251..8e36bf32a 100644 --- a/readconf.h +++ b/readconf.h | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: readconf.h,v 1.128 2018/09/20 03:30:44 djm Exp $ */ | 1 | /* $OpenBSD: readconf.h,v 1.129 2018/11/23 05:08:07 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
@@ -185,7 +185,7 @@ typedef struct { | |||
185 | 185 | ||
186 | #define SSHCONF_CHECKPERM 1 /* check permissions on config file */ | 186 | #define SSHCONF_CHECKPERM 1 /* check permissions on config file */ |
187 | #define SSHCONF_USERCONF 2 /* user provided config file not system */ | 187 | #define SSHCONF_USERCONF 2 /* user provided config file not system */ |
188 | #define SSHCONF_POSTCANON 4 /* After hostname canonicalisation */ | 188 | #define SSHCONF_FINAL 4 /* Final pass over config, after canon. */ |
189 | #define SSHCONF_NEVERMATCH 8 /* Match/Host never matches; internal only */ | 189 | #define SSHCONF_NEVERMATCH 8 /* Match/Host never matches; internal only */ |
190 | 190 | ||
191 | #define SSH_UPDATE_HOSTKEYS_NO 0 | 191 | #define SSH_UPDATE_HOSTKEYS_NO 0 |
@@ -203,7 +203,7 @@ void fill_default_options_for_canonicalization(Options *); | |||
203 | int process_config_line(Options *, struct passwd *, const char *, | 203 | int process_config_line(Options *, struct passwd *, const char *, |
204 | const char *, char *, const char *, int, int *, int); | 204 | const char *, char *, const char *, int, int *, int); |
205 | int read_config_file(const char *, struct passwd *, const char *, | 205 | int read_config_file(const char *, struct passwd *, const char *, |
206 | const char *, Options *, int); | 206 | const char *, Options *, int, int *); |
207 | int parse_forward(struct Forward *, const char *, int, int); | 207 | int parse_forward(struct Forward *, const char *, int, int); |
208 | int parse_jump(const char *, Options *, int); | 208 | int parse_jump(const char *, Options *, int); |
209 | int parse_ssh_uri(const char *, char **, char **, int *); | 209 | int parse_ssh_uri(const char *, char **, char **, int *); |
diff --git a/ssh-keysign.c b/ssh-keysign.c index 8f487b8c5..7ea5ad0e9 100644 --- a/ssh-keysign.c +++ b/ssh-keysign.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: ssh-keysign.c,v 1.55 2018/07/27 05:34:42 dtucker Exp $ */ | 1 | /* $OpenBSD: ssh-keysign.c,v 1.56 2018/11/23 05:08:07 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2002 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 2002 Markus Friedl. All rights reserved. |
4 | * | 4 | * |
@@ -208,7 +208,8 @@ main(int argc, char **argv) | |||
208 | 208 | ||
209 | /* verify that ssh-keysign is enabled by the admin */ | 209 | /* verify that ssh-keysign is enabled by the admin */ |
210 | initialize_options(&options); | 210 | initialize_options(&options); |
211 | (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, "", "", &options, 0); | 211 | (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, "", "", |
212 | &options, 0, NULL); | ||
212 | fill_default_options(&options); | 213 | fill_default_options(&options); |
213 | if (options.enable_ssh_keysign != 1) | 214 | if (options.enable_ssh_keysign != 1) |
214 | fatal("ssh-keysign not enabled in %s", | 215 | fatal("ssh-keysign not enabled in %s", |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: ssh.c,v 1.495 2018/10/23 05:56:35 djm Exp $ */ | 1 | /* $OpenBSD: ssh.c,v 1.496 2018/11/23 05:08:07 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -527,7 +527,8 @@ check_load(int r, const char *path, const char *message) | |||
527 | * file if the user specifies a config file on the command line. | 527 | * file if the user specifies a config file on the command line. |
528 | */ | 528 | */ |
529 | static void | 529 | static void |
530 | process_config_files(const char *host_name, struct passwd *pw, int post_canon) | 530 | process_config_files(const char *host_name, struct passwd *pw, int final_pass, |
531 | int *want_final_pass) | ||
531 | { | 532 | { |
532 | char buf[PATH_MAX]; | 533 | char buf[PATH_MAX]; |
533 | int r; | 534 | int r; |
@@ -535,7 +536,8 @@ process_config_files(const char *host_name, struct passwd *pw, int post_canon) | |||
535 | if (config != NULL) { | 536 | if (config != NULL) { |
536 | if (strcasecmp(config, "none") != 0 && | 537 | if (strcasecmp(config, "none") != 0 && |
537 | !read_config_file(config, pw, host, host_name, &options, | 538 | !read_config_file(config, pw, host, host_name, &options, |
538 | SSHCONF_USERCONF | (post_canon ? SSHCONF_POSTCANON : 0))) | 539 | SSHCONF_USERCONF | (final_pass ? SSHCONF_FINAL : 0), |
540 | want_final_pass)) | ||
539 | fatal("Can't open user config file %.100s: " | 541 | fatal("Can't open user config file %.100s: " |
540 | "%.100s", config, strerror(errno)); | 542 | "%.100s", config, strerror(errno)); |
541 | } else { | 543 | } else { |
@@ -544,12 +546,12 @@ process_config_files(const char *host_name, struct passwd *pw, int post_canon) | |||
544 | if (r > 0 && (size_t)r < sizeof(buf)) | 546 | if (r > 0 && (size_t)r < sizeof(buf)) |
545 | (void)read_config_file(buf, pw, host, host_name, | 547 | (void)read_config_file(buf, pw, host, host_name, |
546 | &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF | | 548 | &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF | |
547 | (post_canon ? SSHCONF_POSTCANON : 0)); | 549 | (final_pass ? SSHCONF_FINAL : 0), want_final_pass); |
548 | 550 | ||
549 | /* Read systemwide configuration file after user config. */ | 551 | /* Read systemwide configuration file after user config. */ |
550 | (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, | 552 | (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, |
551 | host, host_name, &options, | 553 | host, host_name, &options, |
552 | post_canon ? SSHCONF_POSTCANON : 0); | 554 | final_pass ? SSHCONF_FINAL : 0, want_final_pass); |
553 | } | 555 | } |
554 | } | 556 | } |
555 | 557 | ||
@@ -581,7 +583,7 @@ main(int ac, char **av) | |||
581 | { | 583 | { |
582 | struct ssh *ssh = NULL; | 584 | struct ssh *ssh = NULL; |
583 | int i, r, opt, exit_status, use_syslog, direct, timeout_ms; | 585 | int i, r, opt, exit_status, use_syslog, direct, timeout_ms; |
584 | int was_addr, config_test = 0, opt_terminated = 0; | 586 | int was_addr, config_test = 0, opt_terminated = 0, want_final_pass = 0; |
585 | char *p, *cp, *line, *argv0, buf[PATH_MAX], *logfile; | 587 | char *p, *cp, *line, *argv0, buf[PATH_MAX], *logfile; |
586 | char cname[NI_MAXHOST]; | 588 | char cname[NI_MAXHOST]; |
587 | struct stat st; | 589 | struct stat st; |
@@ -1089,7 +1091,9 @@ main(int ac, char **av) | |||
1089 | ); | 1091 | ); |
1090 | 1092 | ||
1091 | /* Parse the configuration files */ | 1093 | /* Parse the configuration files */ |
1092 | process_config_files(host_arg, pw, 0); | 1094 | process_config_files(host_arg, pw, 0, &want_final_pass); |
1095 | if (want_final_pass) | ||
1096 | debug("configuration requests final Match pass"); | ||
1093 | 1097 | ||
1094 | /* Hostname canonicalisation needs a few options filled. */ | 1098 | /* Hostname canonicalisation needs a few options filled. */ |
1095 | fill_default_options_for_canonicalization(&options); | 1099 | fill_default_options_for_canonicalization(&options); |
@@ -1146,12 +1150,17 @@ main(int ac, char **av) | |||
1146 | * If canonicalisation is enabled then re-parse the configuration | 1150 | * If canonicalisation is enabled then re-parse the configuration |
1147 | * files as new stanzas may match. | 1151 | * files as new stanzas may match. |
1148 | */ | 1152 | */ |
1149 | if (options.canonicalize_hostname != 0) { | 1153 | if (options.canonicalize_hostname != 0 && !want_final_pass) { |
1150 | debug("Re-reading configuration after hostname " | 1154 | debug("hostname canonicalisation enabled, " |
1151 | "canonicalisation"); | 1155 | "will re-parse configuration"); |
1156 | want_final_pass = 1; | ||
1157 | } | ||
1158 | |||
1159 | if (want_final_pass) { | ||
1160 | debug("re-parsing configuration"); | ||
1152 | free(options.hostname); | 1161 | free(options.hostname); |
1153 | options.hostname = xstrdup(host); | 1162 | options.hostname = xstrdup(host); |
1154 | process_config_files(host_arg, pw, 1); | 1163 | process_config_files(host_arg, pw, 1, NULL); |
1155 | /* | 1164 | /* |
1156 | * Address resolution happens early with canonicalisation | 1165 | * Address resolution happens early with canonicalisation |
1157 | * enabled and the port number may have changed since, so | 1166 | * enabled and the port number may have changed since, so |
diff --git a/ssh_config.5 b/ssh_config.5 index 4d5b01d3e..58a5fa1c8 100644 --- a/ssh_config.5 +++ b/ssh_config.5 | |||
@@ -33,8 +33,8 @@ | |||
33 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 33 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
34 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 34 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
35 | .\" | 35 | .\" |
36 | .\" $OpenBSD: ssh_config.5,v 1.286 2018/10/03 06:38:35 djm Exp $ | 36 | .\" $OpenBSD: ssh_config.5,v 1.287 2018/11/23 05:08:07 djm Exp $ |
37 | .Dd $Mdocdate: October 3 2018 $ | 37 | .Dd $Mdocdate: November 23 2018 $ |
38 | .Dt SSH_CONFIG 5 | 38 | .Dt SSH_CONFIG 5 |
39 | .Os | 39 | .Os |
40 | .Sh NAME | 40 | .Sh NAME |
@@ -139,6 +139,7 @@ or the single token | |||
139 | which always matches. | 139 | which always matches. |
140 | The available criteria keywords are: | 140 | The available criteria keywords are: |
141 | .Cm canonical , | 141 | .Cm canonical , |
142 | .Cm final , | ||
142 | .Cm exec , | 143 | .Cm exec , |
143 | .Cm host , | 144 | .Cm host , |
144 | .Cm originalhost , | 145 | .Cm originalhost , |
@@ -148,12 +149,15 @@ and | |||
148 | The | 149 | The |
149 | .Cm all | 150 | .Cm all |
150 | criteria must appear alone or immediately after | 151 | criteria must appear alone or immediately after |
151 | .Cm canonical . | 152 | .Cm canonical |
153 | or | ||
154 | .Cm final . | ||
152 | Other criteria may be combined arbitrarily. | 155 | Other criteria may be combined arbitrarily. |
153 | All criteria but | 156 | All criteria but |
154 | .Cm all | 157 | .Cm all |
155 | and | ||
156 | .Cm canonical | 158 | .Cm canonical |
159 | and | ||
160 | .Cm final | ||
157 | require an argument. | 161 | require an argument. |
158 | Criteria may be negated by prepending an exclamation mark | 162 | Criteria may be negated by prepending an exclamation mark |
159 | .Pq Sq !\& . | 163 | .Pq Sq !\& . |
@@ -166,6 +170,20 @@ after hostname canonicalization (see the | |||
166 | option.) | 170 | option.) |
167 | This may be useful to specify conditions that work with canonical host | 171 | This may be useful to specify conditions that work with canonical host |
168 | names only. | 172 | names only. |
173 | .Pp | ||
174 | The | ||
175 | .Cm final | ||
176 | keyword requests that the configuration be re-parsed (regardless of whether | ||
177 | .Cm CanonicalizeHostname | ||
178 | is enabled), and matches only during this final pass. | ||
179 | If | ||
180 | .Cm CanonicalizeHostname | ||
181 | is enabled, then | ||
182 | .Cm canonical | ||
183 | and | ||
184 | .Cm final | ||
185 | match during the same pass. | ||
186 | .Pp | ||
169 | The | 187 | The |
170 | .Cm exec | 188 | .Cm exec |
171 | keyword executes the specified command under the user's shell. | 189 | keyword executes the specified command under the user's shell. |