diff options
author | Damien Miller <djm@mindrot.org> | 2013-10-17 11:47:23 +1100 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2013-10-17 11:47:23 +1100 |
commit | 0faf747e2f77f0f7083bcd59cbed30c4b5448444 (patch) | |
tree | 1f1b80f60be01d61f284070affc314d1b97b6b69 /ssh.c | |
parent | d77b81f856e078714ec6b0f86f61c20249b7ead4 (diff) |
- djm@cvs.openbsd.org 2013/10/16 02:31:47
[readconf.c readconf.h roaming_client.c ssh.1 ssh.c ssh_config.5]
[sshconnect.c sshconnect.h]
Implement client-side hostname canonicalisation to allow an explicit
search path of domain suffixes to use to convert unqualified host names
to fully-qualified ones for host key matching.
This is particularly useful for host certificates, which would otherwise
need to list unqualified names alongside fully-qualified ones (and this
causes a number of problems).
"looks fine" markus@
Diffstat (limited to 'ssh.c')
-rw-r--r-- | ssh.c | 183 |
1 files changed, 167 insertions, 16 deletions
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: ssh.c,v 1.384 2013/10/14 23:31:01 djm Exp $ */ | 1 | /* $OpenBSD: ssh.c,v 1.385 2013/10/16 02:31:46 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 |
@@ -231,6 +231,134 @@ tilde_expand_paths(char **paths, u_int num_paths) | |||
231 | } | 231 | } |
232 | } | 232 | } |
233 | 233 | ||
234 | static struct addrinfo * | ||
235 | resolve_host(const char *name, u_int port, int logerr, char *cname, size_t clen) | ||
236 | { | ||
237 | char strport[NI_MAXSERV]; | ||
238 | struct addrinfo hints, *res; | ||
239 | int gaierr, loglevel = SYSLOG_LEVEL_DEBUG1; | ||
240 | |||
241 | snprintf(strport, sizeof strport, "%u", port); | ||
242 | bzero(&hints, sizeof(hints)); | ||
243 | hints.ai_family = options.address_family; | ||
244 | hints.ai_socktype = SOCK_STREAM; | ||
245 | if (cname != NULL) | ||
246 | hints.ai_flags = AI_CANONNAME; | ||
247 | if ((gaierr = getaddrinfo(name, strport, &hints, &res)) != 0) { | ||
248 | if (logerr || (gaierr != EAI_NONAME && gaierr != EAI_NODATA)) | ||
249 | loglevel = SYSLOG_LEVEL_ERROR; | ||
250 | do_log2(loglevel, "%s: Could not resolve hostname %.100s: %s", | ||
251 | __progname, name, ssh_gai_strerror(gaierr)); | ||
252 | return NULL; | ||
253 | } | ||
254 | if (cname != NULL && res->ai_canonname != NULL) { | ||
255 | if (strlcpy(cname, res->ai_canonname, clen) >= clen) { | ||
256 | error("%s: host \"%s\" cname \"%s\" too long (max %lu)", | ||
257 | __func__, name, res->ai_canonname, (u_long)clen); | ||
258 | if (clen > 0) | ||
259 | *cname = '\0'; | ||
260 | } | ||
261 | } | ||
262 | return res; | ||
263 | } | ||
264 | |||
265 | /* | ||
266 | * Check whether the cname is a permitted replacement for the hostname | ||
267 | * and perform the replacement if it is. | ||
268 | */ | ||
269 | static int | ||
270 | check_follow_cname(char **namep, const char *cname) | ||
271 | { | ||
272 | int i; | ||
273 | struct allowed_cname *rule; | ||
274 | |||
275 | if (*cname == '\0' || options.num_permitted_cnames == 0 || | ||
276 | strcmp(*namep, cname) == 0) | ||
277 | return 0; | ||
278 | if (options.canonicalise_hostname == SSH_CANONICALISE_NO) | ||
279 | return 0; | ||
280 | /* | ||
281 | * Don't attempt to canonicalise names that will be interpreted by | ||
282 | * a proxy unless the user specifically requests so. | ||
283 | */ | ||
284 | if (options.proxy_command != NULL && | ||
285 | options.canonicalise_hostname != SSH_CANONICALISE_ALWAYS) | ||
286 | return 0; | ||
287 | debug3("%s: check \"%s\" CNAME \"%s\"", __func__, *namep, cname); | ||
288 | for (i = 0; i < options.num_permitted_cnames; i++) { | ||
289 | rule = options.permitted_cnames + i; | ||
290 | if (match_pattern_list(*namep, rule->source_list, | ||
291 | strlen(rule->source_list), 1) != 1 || | ||
292 | match_pattern_list(cname, rule->target_list, | ||
293 | strlen(rule->target_list), 1) != 1) | ||
294 | continue; | ||
295 | verbose("Canonicalised DNS aliased hostname " | ||
296 | "\"%s\" => \"%s\"", *namep, cname); | ||
297 | free(*namep); | ||
298 | *namep = xstrdup(cname); | ||
299 | return 1; | ||
300 | } | ||
301 | return 0; | ||
302 | } | ||
303 | |||
304 | /* | ||
305 | * Attempt to resolve the supplied hostname after applying the user's | ||
306 | * canonicalisation rules. Returns the address list for the host or NULL | ||
307 | * if no name was found after canonicalisation. | ||
308 | */ | ||
309 | static struct addrinfo * | ||
310 | resolve_canonicalise(char **hostp, u_int port) | ||
311 | { | ||
312 | int i, ndots; | ||
313 | char *cp, *fullhost, cname_target[NI_MAXHOST]; | ||
314 | struct addrinfo *addrs; | ||
315 | |||
316 | if (options.canonicalise_hostname == SSH_CANONICALISE_NO) | ||
317 | return NULL; | ||
318 | /* | ||
319 | * Don't attempt to canonicalise names that will be interpreted by | ||
320 | * a proxy unless the user specifically requests so. | ||
321 | */ | ||
322 | if (options.proxy_command != NULL && | ||
323 | options.canonicalise_hostname != SSH_CANONICALISE_ALWAYS) | ||
324 | return NULL; | ||
325 | /* Don't apply canonicalisation to sufficiently-qualified hostnames */ | ||
326 | ndots = 0; | ||
327 | for (cp = *hostp; *cp != '\0'; cp++) { | ||
328 | if (*cp == '.') | ||
329 | ndots++; | ||
330 | } | ||
331 | if (ndots > options.canonicalise_max_dots) { | ||
332 | debug3("%s: not canonicalising hostname \"%s\" (max dots %d)", | ||
333 | __func__, *hostp, options.canonicalise_max_dots); | ||
334 | return NULL; | ||
335 | } | ||
336 | /* Attempt each supplied suffix */ | ||
337 | for (i = 0; i < options.num_canonical_domains; i++) { | ||
338 | *cname_target = '\0'; | ||
339 | xasprintf(&fullhost, "%s.%s.", *hostp, | ||
340 | options.canonical_domains[i]); | ||
341 | if ((addrs = resolve_host(fullhost, options.port, 0, | ||
342 | cname_target, sizeof(cname_target))) == NULL) { | ||
343 | free(fullhost); | ||
344 | continue; | ||
345 | } | ||
346 | /* Remove trailing '.' */ | ||
347 | fullhost[strlen(fullhost) - 1] = '\0'; | ||
348 | /* Follow CNAME if requested */ | ||
349 | if (!check_follow_cname(&fullhost, cname_target)) { | ||
350 | debug("Canonicalised hostname \"%s\" => \"%s\"", | ||
351 | *hostp, fullhost); | ||
352 | } | ||
353 | free(*hostp); | ||
354 | *hostp = fullhost; | ||
355 | return addrs; | ||
356 | } | ||
357 | if (!options.canonicalise_fallback_local) | ||
358 | fatal("%s: Could not resolve host \"%s\"", __progname, host); | ||
359 | return NULL; | ||
360 | } | ||
361 | |||
234 | /* | 362 | /* |
235 | * Main program for the ssh client. | 363 | * Main program for the ssh client. |
236 | */ | 364 | */ |
@@ -240,12 +368,14 @@ main(int ac, char **av) | |||
240 | int i, r, opt, exit_status, use_syslog; | 368 | int i, r, opt, exit_status, use_syslog; |
241 | char *p, *cp, *line, *argv0, buf[MAXPATHLEN], *host_arg, *logfile; | 369 | char *p, *cp, *line, *argv0, buf[MAXPATHLEN], *host_arg, *logfile; |
242 | char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; | 370 | char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; |
371 | char cname[NI_MAXHOST]; | ||
243 | struct stat st; | 372 | struct stat st; |
244 | struct passwd *pw; | 373 | struct passwd *pw; |
245 | int timeout_ms; | 374 | int timeout_ms; |
246 | extern int optind, optreset; | 375 | extern int optind, optreset; |
247 | extern char *optarg; | 376 | extern char *optarg; |
248 | Forward fwd; | 377 | Forward fwd; |
378 | struct addrinfo *addrs = NULL; | ||
249 | 379 | ||
250 | /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ | 380 | /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ |
251 | sanitise_stdfd(); | 381 | sanitise_stdfd(); |
@@ -630,9 +760,9 @@ main(int ac, char **av) | |||
630 | usage(); | 760 | usage(); |
631 | options.user = p; | 761 | options.user = p; |
632 | *cp = '\0'; | 762 | *cp = '\0'; |
633 | host = ++cp; | 763 | host = xstrdup(++cp); |
634 | } else | 764 | } else |
635 | host = *av; | 765 | host = xstrdup(*av); |
636 | if (ac > 1) { | 766 | if (ac > 1) { |
637 | optind = optreset = 1; | 767 | optind = optreset = 1; |
638 | goto again; | 768 | goto again; |
@@ -644,6 +774,9 @@ main(int ac, char **av) | |||
644 | if (!host) | 774 | if (!host) |
645 | usage(); | 775 | usage(); |
646 | 776 | ||
777 | lowercase(host); | ||
778 | host_arg = xstrdup(host); | ||
779 | |||
647 | OpenSSL_add_all_algorithms(); | 780 | OpenSSL_add_all_algorithms(); |
648 | ERR_load_crypto_strings(); | 781 | ERR_load_crypto_strings(); |
649 | 782 | ||
@@ -728,6 +861,10 @@ main(int ac, char **av) | |||
728 | strcmp(options.proxy_command, "-") == 0 && | 861 | strcmp(options.proxy_command, "-") == 0 && |
729 | options.proxy_use_fdpass) | 862 | options.proxy_use_fdpass) |
730 | fatal("ProxyCommand=- and ProxyUseFDPass are incompatible"); | 863 | fatal("ProxyCommand=- and ProxyUseFDPass are incompatible"); |
864 | #ifndef HAVE_CYGWIN | ||
865 | if (original_effective_uid != 0) | ||
866 | options.use_privileged_port = 0; | ||
867 | #endif | ||
731 | 868 | ||
732 | /* reinit */ | 869 | /* reinit */ |
733 | log_init(argv0, options.log_level, SYSLOG_FACILITY_USER, !use_syslog); | 870 | log_init(argv0, options.log_level, SYSLOG_FACILITY_USER, !use_syslog); |
@@ -762,10 +899,26 @@ main(int ac, char **av) | |||
762 | options.port = default_ssh_port(); | 899 | options.port = default_ssh_port(); |
763 | 900 | ||
764 | /* preserve host name given on command line for %n expansion */ | 901 | /* preserve host name given on command line for %n expansion */ |
765 | host_arg = host; | ||
766 | if (options.hostname != NULL) { | 902 | if (options.hostname != NULL) { |
767 | host = percent_expand(options.hostname, | 903 | cp = percent_expand(options.hostname, |
768 | "h", host, (char *)NULL); | 904 | "h", host, (char *)NULL); |
905 | free(host); | ||
906 | host = cp; | ||
907 | } | ||
908 | |||
909 | /* If canonicalisation requested then try to apply it */ | ||
910 | if (options.canonicalise_hostname != SSH_CANONICALISE_NO) | ||
911 | addrs = resolve_canonicalise(&host, options.port); | ||
912 | /* | ||
913 | * If canonicalisation not requested, or if it failed then try to | ||
914 | * resolve the bare hostname name using the system resolver's usual | ||
915 | * search rules. | ||
916 | */ | ||
917 | if (addrs == NULL) { | ||
918 | if ((addrs = resolve_host(host, options.port, 1, | ||
919 | cname, sizeof(cname))) == NULL) | ||
920 | cleanup_exit(255); /* resolve_host logs the error */ | ||
921 | check_follow_cname(&host, cname); | ||
769 | } | 922 | } |
770 | 923 | ||
771 | if (gethostname(thishost, sizeof(thishost)) == -1) | 924 | if (gethostname(thishost, sizeof(thishost)) == -1) |
@@ -803,16 +956,15 @@ main(int ac, char **av) | |||
803 | timeout_ms = options.connection_timeout * 1000; | 956 | timeout_ms = options.connection_timeout * 1000; |
804 | 957 | ||
805 | /* Open a connection to the remote host. */ | 958 | /* Open a connection to the remote host. */ |
806 | if (ssh_connect(host, &hostaddr, options.port, | 959 | if (ssh_connect(host, addrs, &hostaddr, options.port, |
807 | options.address_family, options.connection_attempts, &timeout_ms, | 960 | options.address_family, options.connection_attempts, |
808 | options.tcp_keep_alive, | 961 | &timeout_ms, options.tcp_keep_alive, |
809 | #ifdef HAVE_CYGWIN | 962 | options.use_privileged_port) != 0) |
810 | options.use_privileged_port, | 963 | exit(255); |
811 | #else | 964 | |
812 | original_effective_uid == 0 && options.use_privileged_port, | 965 | freeaddrinfo(addrs); |
813 | #endif | 966 | packet_set_timeout(options.server_alive_interval, |
814 | options.proxy_command) != 0) | 967 | options.server_alive_count_max); |
815 | exit(255); | ||
816 | 968 | ||
817 | if (timeout_ms > 0) | 969 | if (timeout_ms > 0) |
818 | debug3("timeout: %d ms remain after connect", timeout_ms); | 970 | debug3("timeout: %d ms remain after connect", timeout_ms); |
@@ -1621,4 +1773,3 @@ main_sigchld_handler(int sig) | |||
1621 | signal(sig, main_sigchld_handler); | 1773 | signal(sig, main_sigchld_handler); |
1622 | errno = save_errno; | 1774 | errno = save_errno; |
1623 | } | 1775 | } |
1624 | |||