diff options
-rw-r--r-- | misc.c | 297 | ||||
-rw-r--r-- | misc.h | 5 | ||||
-rw-r--r-- | readconf.c | 54 | ||||
-rw-r--r-- | readconf.h | 3 | ||||
-rw-r--r-- | scp.1 | 41 | ||||
-rw-r--r-- | scp.c | 199 | ||||
-rw-r--r-- | sftp.1 | 77 | ||||
-rw-r--r-- | sftp.c | 58 | ||||
-rw-r--r-- | ssh.1 | 36 | ||||
-rw-r--r-- | ssh.c | 56 | ||||
-rw-r--r-- | ssh_config.5 | 7 |
11 files changed, 582 insertions, 251 deletions
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: misc.c,v 1.113 2017/08/18 05:48:04 djm Exp $ */ | 1 | /* $OpenBSD: misc.c,v 1.114 2017/10/21 23:06:24 millert Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
4 | * Copyright (c) 2005,2006 Damien Miller. All rights reserved. | 4 | * Copyright (c) 2005,2006 Damien Miller. All rights reserved. |
@@ -395,11 +395,12 @@ put_host_port(const char *host, u_short port) | |||
395 | * Search for next delimiter between hostnames/addresses and ports. | 395 | * Search for next delimiter between hostnames/addresses and ports. |
396 | * Argument may be modified (for termination). | 396 | * Argument may be modified (for termination). |
397 | * Returns *cp if parsing succeeds. | 397 | * Returns *cp if parsing succeeds. |
398 | * *cp is set to the start of the next delimiter, if one was found. | 398 | * *cp is set to the start of the next field, if one was found. |
399 | * The delimiter char, if present, is stored in delim. | ||
399 | * If this is the last field, *cp is set to NULL. | 400 | * If this is the last field, *cp is set to NULL. |
400 | */ | 401 | */ |
401 | char * | 402 | static char * |
402 | hpdelim(char **cp) | 403 | hpdelim2(char **cp, char *delim) |
403 | { | 404 | { |
404 | char *s, *old; | 405 | char *s, *old; |
405 | 406 | ||
@@ -422,6 +423,8 @@ hpdelim(char **cp) | |||
422 | 423 | ||
423 | case ':': | 424 | case ':': |
424 | case '/': | 425 | case '/': |
426 | if (delim != NULL) | ||
427 | *delim = *s; | ||
425 | *s = '\0'; /* terminate */ | 428 | *s = '\0'; /* terminate */ |
426 | *cp = s + 1; | 429 | *cp = s + 1; |
427 | break; | 430 | break; |
@@ -434,6 +437,12 @@ hpdelim(char **cp) | |||
434 | } | 437 | } |
435 | 438 | ||
436 | char * | 439 | char * |
440 | hpdelim(char **cp) | ||
441 | { | ||
442 | return hpdelim2(cp, NULL); | ||
443 | } | ||
444 | |||
445 | char * | ||
437 | cleanhostname(char *host) | 446 | cleanhostname(char *host) |
438 | { | 447 | { |
439 | if (*host == '[' && host[strlen(host) - 1] == ']') { | 448 | if (*host == '[' && host[strlen(host) - 1] == ']') { |
@@ -467,6 +476,75 @@ colon(char *cp) | |||
467 | } | 476 | } |
468 | 477 | ||
469 | /* | 478 | /* |
479 | * Parse a [user@]host:[path] string. | ||
480 | * Caller must free returned user, host and path. | ||
481 | * Any of the pointer return arguments may be NULL (useful for syntax checking). | ||
482 | * If user was not specified then *userp will be set to NULL. | ||
483 | * If host was not specified then *hostp will be set to NULL. | ||
484 | * If path was not specified then *pathp will be set to ".". | ||
485 | * Returns 0 on success, -1 on failure. | ||
486 | */ | ||
487 | int | ||
488 | parse_user_host_path(const char *s, char **userp, char **hostp, char **pathp) | ||
489 | { | ||
490 | char *user = NULL, *host = NULL, *path = NULL; | ||
491 | char *sdup, *tmp; | ||
492 | int ret = -1; | ||
493 | |||
494 | if (userp != NULL) | ||
495 | *userp = NULL; | ||
496 | if (hostp != NULL) | ||
497 | *hostp = NULL; | ||
498 | if (pathp != NULL) | ||
499 | *pathp = NULL; | ||
500 | |||
501 | sdup = tmp = xstrdup(s); | ||
502 | |||
503 | /* Check for remote syntax: [user@]host:[path] */ | ||
504 | if ((tmp = colon(sdup)) == NULL) | ||
505 | goto out; | ||
506 | |||
507 | /* Extract optional path */ | ||
508 | *tmp++ = '\0'; | ||
509 | if (*tmp == '\0') | ||
510 | tmp = "."; | ||
511 | path = xstrdup(tmp); | ||
512 | |||
513 | /* Extract optional user and mandatory host */ | ||
514 | tmp = strrchr(sdup, '@'); | ||
515 | if (tmp != NULL) { | ||
516 | *tmp++ = '\0'; | ||
517 | host = xstrdup(cleanhostname(tmp)); | ||
518 | if (*sdup != '\0') | ||
519 | user = xstrdup(sdup); | ||
520 | } else { | ||
521 | host = xstrdup(cleanhostname(sdup)); | ||
522 | user = NULL; | ||
523 | } | ||
524 | |||
525 | /* Success */ | ||
526 | if (userp != NULL) { | ||
527 | *userp = user; | ||
528 | user = NULL; | ||
529 | } | ||
530 | if (hostp != NULL) { | ||
531 | *hostp = host; | ||
532 | host = NULL; | ||
533 | } | ||
534 | if (pathp != NULL) { | ||
535 | *pathp = path; | ||
536 | path = NULL; | ||
537 | } | ||
538 | ret = 0; | ||
539 | out: | ||
540 | free(sdup); | ||
541 | free(user); | ||
542 | free(host); | ||
543 | free(path); | ||
544 | return ret; | ||
545 | } | ||
546 | |||
547 | /* | ||
470 | * Parse a [user@]host[:port] string. | 548 | * Parse a [user@]host[:port] string. |
471 | * Caller must free returned user and host. | 549 | * Caller must free returned user and host. |
472 | * Any of the pointer return arguments may be NULL (useful for syntax checking). | 550 | * Any of the pointer return arguments may be NULL (useful for syntax checking). |
@@ -491,7 +569,7 @@ parse_user_host_port(const char *s, char **userp, char **hostp, int *portp) | |||
491 | if ((sdup = tmp = strdup(s)) == NULL) | 569 | if ((sdup = tmp = strdup(s)) == NULL) |
492 | return -1; | 570 | return -1; |
493 | /* Extract optional username */ | 571 | /* Extract optional username */ |
494 | if ((cp = strchr(tmp, '@')) != NULL) { | 572 | if ((cp = strrchr(tmp, '@')) != NULL) { |
495 | *cp = '\0'; | 573 | *cp = '\0'; |
496 | if (*tmp == '\0') | 574 | if (*tmp == '\0') |
497 | goto out; | 575 | goto out; |
@@ -527,6 +605,168 @@ parse_user_host_port(const char *s, char **userp, char **hostp, int *portp) | |||
527 | return ret; | 605 | return ret; |
528 | } | 606 | } |
529 | 607 | ||
608 | /* | ||
609 | * Converts a two-byte hex string to decimal. | ||
610 | * Returns the decimal value or -1 for invalid input. | ||
611 | */ | ||
612 | static int | ||
613 | hexchar(const char *s) | ||
614 | { | ||
615 | unsigned char result[2]; | ||
616 | int i; | ||
617 | |||
618 | for (i = 0; i < 2; i++) { | ||
619 | if (s[i] >= '0' && s[i] <= '9') | ||
620 | result[i] = (unsigned char)(s[i] - '0'); | ||
621 | else if (s[i] >= 'a' && s[i] <= 'f') | ||
622 | result[i] = (unsigned char)(s[i] - 'a') + 10; | ||
623 | else if (s[i] >= 'A' && s[i] <= 'F') | ||
624 | result[i] = (unsigned char)(s[i] - 'A') + 10; | ||
625 | else | ||
626 | return -1; | ||
627 | } | ||
628 | return (result[0] << 4) | result[1]; | ||
629 | } | ||
630 | |||
631 | /* | ||
632 | * Decode an url-encoded string. | ||
633 | * Returns a newly allocated string on success or NULL on failure. | ||
634 | */ | ||
635 | static char * | ||
636 | urldecode(const char *src) | ||
637 | { | ||
638 | char *ret, *dst; | ||
639 | int ch; | ||
640 | |||
641 | ret = xmalloc(strlen(src) + 1); | ||
642 | for (dst = ret; *src != '\0'; src++) { | ||
643 | switch (*src) { | ||
644 | case '+': | ||
645 | *dst++ = ' '; | ||
646 | break; | ||
647 | case '%': | ||
648 | if (!isxdigit((unsigned char)src[1]) || | ||
649 | !isxdigit((unsigned char)src[2]) || | ||
650 | (ch = hexchar(src + 1)) == -1) { | ||
651 | free(ret); | ||
652 | return NULL; | ||
653 | } | ||
654 | *dst++ = ch; | ||
655 | src += 2; | ||
656 | break; | ||
657 | default: | ||
658 | *dst++ = *src; | ||
659 | break; | ||
660 | } | ||
661 | } | ||
662 | *dst = '\0'; | ||
663 | |||
664 | return ret; | ||
665 | } | ||
666 | |||
667 | /* | ||
668 | * Parse an (scp|ssh|sftp)://[user@]host[:port][/path] URI. | ||
669 | * See https://tools.ietf.org/html/draft-ietf-secsh-scp-sftp-ssh-uri-04 | ||
670 | * Either user or path may be url-encoded (but not host or port). | ||
671 | * Caller must free returned user, host and path. | ||
672 | * Any of the pointer return arguments may be NULL (useful for syntax checking) | ||
673 | * but the scheme must always be specified. | ||
674 | * If user was not specified then *userp will be set to NULL. | ||
675 | * If port was not specified then *portp will be -1. | ||
676 | * If path was not specified then *pathp will be set to NULL. | ||
677 | * Returns 0 on success, 1 if non-uri/wrong scheme, -1 on error/invalid uri. | ||
678 | */ | ||
679 | int | ||
680 | parse_uri(const char *scheme, const char *uri, char **userp, char **hostp, | ||
681 | int *portp, char **pathp) | ||
682 | { | ||
683 | char *uridup, *cp, *tmp, ch; | ||
684 | char *user = NULL, *host = NULL, *path = NULL; | ||
685 | int port = -1, ret = -1; | ||
686 | size_t len; | ||
687 | |||
688 | len = strlen(scheme); | ||
689 | if (strncmp(uri, scheme, len) != 0 || strncmp(uri + len, "://", 3) != 0) | ||
690 | return 1; | ||
691 | uri += len + 3; | ||
692 | |||
693 | if (userp != NULL) | ||
694 | *userp = NULL; | ||
695 | if (hostp != NULL) | ||
696 | *hostp = NULL; | ||
697 | if (portp != NULL) | ||
698 | *portp = -1; | ||
699 | if (pathp != NULL) | ||
700 | *pathp = NULL; | ||
701 | |||
702 | uridup = tmp = xstrdup(uri); | ||
703 | |||
704 | /* Extract optional ssh-info (username + connection params) */ | ||
705 | if ((cp = strchr(tmp, '@')) != NULL) { | ||
706 | char *delim; | ||
707 | |||
708 | *cp = '\0'; | ||
709 | /* Extract username and connection params */ | ||
710 | if ((delim = strchr(tmp, ';')) != NULL) { | ||
711 | /* Just ignore connection params for now */ | ||
712 | *delim = '\0'; | ||
713 | } | ||
714 | if (*tmp == '\0') { | ||
715 | /* Empty username */ | ||
716 | goto out; | ||
717 | } | ||
718 | if ((user = urldecode(tmp)) == NULL) | ||
719 | goto out; | ||
720 | tmp = cp + 1; | ||
721 | } | ||
722 | |||
723 | /* Extract mandatory hostname */ | ||
724 | if ((cp = hpdelim2(&tmp, &ch)) == NULL || *cp == '\0') | ||
725 | goto out; | ||
726 | host = xstrdup(cleanhostname(cp)); | ||
727 | if (!valid_domain(host, 0, NULL)) | ||
728 | goto out; | ||
729 | |||
730 | if (tmp != NULL && *tmp != '\0') { | ||
731 | if (ch == ':') { | ||
732 | /* Convert and verify port. */ | ||
733 | if ((cp = strchr(tmp, '/')) != NULL) | ||
734 | *cp = '\0'; | ||
735 | if ((port = a2port(tmp)) <= 0) | ||
736 | goto out; | ||
737 | tmp = cp ? cp + 1 : NULL; | ||
738 | } | ||
739 | if (tmp != NULL && *tmp != '\0') { | ||
740 | /* Extract optional path */ | ||
741 | if ((path = urldecode(tmp)) == NULL) | ||
742 | goto out; | ||
743 | } | ||
744 | } | ||
745 | |||
746 | /* Success */ | ||
747 | if (userp != NULL) { | ||
748 | *userp = user; | ||
749 | user = NULL; | ||
750 | } | ||
751 | if (hostp != NULL) { | ||
752 | *hostp = host; | ||
753 | host = NULL; | ||
754 | } | ||
755 | if (portp != NULL) | ||
756 | *portp = port; | ||
757 | if (pathp != NULL) { | ||
758 | *pathp = path; | ||
759 | path = NULL; | ||
760 | } | ||
761 | ret = 0; | ||
762 | out: | ||
763 | free(uridup); | ||
764 | free(user); | ||
765 | free(host); | ||
766 | free(path); | ||
767 | return ret; | ||
768 | } | ||
769 | |||
530 | /* function to assist building execv() arguments */ | 770 | /* function to assist building execv() arguments */ |
531 | void | 771 | void |
532 | addargs(arglist *args, char *fmt, ...) | 772 | addargs(arglist *args, char *fmt, ...) |
@@ -1743,3 +1983,50 @@ child_set_env(char ***envp, u_int *envsizep, const char *name, | |||
1743 | snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); | 1983 | snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); |
1744 | } | 1984 | } |
1745 | 1985 | ||
1986 | /* | ||
1987 | * Check and optionally lowercase a domain name, also removes trailing '.' | ||
1988 | * Returns 1 on success and 0 on failure, storing an error message in errstr. | ||
1989 | */ | ||
1990 | int | ||
1991 | valid_domain(char *name, int makelower, const char **errstr) | ||
1992 | { | ||
1993 | size_t i, l = strlen(name); | ||
1994 | u_char c, last = '\0'; | ||
1995 | static char errbuf[256]; | ||
1996 | |||
1997 | if (l == 0) { | ||
1998 | strlcpy(errbuf, "empty domain name", sizeof(errbuf)); | ||
1999 | goto bad; | ||
2000 | } | ||
2001 | if (!isalpha((u_char)name[0]) && !isdigit((u_char)name[0])) { | ||
2002 | snprintf(errbuf, sizeof(errbuf), "domain name \"%.100s\" " | ||
2003 | "starts with invalid character", name); | ||
2004 | goto bad; | ||
2005 | } | ||
2006 | for (i = 0; i < l; i++) { | ||
2007 | c = tolower((u_char)name[i]); | ||
2008 | if (makelower) | ||
2009 | name[i] = (char)c; | ||
2010 | if (last == '.' && c == '.') { | ||
2011 | snprintf(errbuf, sizeof(errbuf), "domain name " | ||
2012 | "\"%.100s\" contains consecutive separators", name); | ||
2013 | goto bad; | ||
2014 | } | ||
2015 | if (c != '.' && c != '-' && !isalnum(c) && | ||
2016 | c != '_') /* technically invalid, but common */ { | ||
2017 | snprintf(errbuf, sizeof(errbuf), "domain name " | ||
2018 | "\"%.100s\" contains invalid characters", name); | ||
2019 | goto bad; | ||
2020 | } | ||
2021 | last = c; | ||
2022 | } | ||
2023 | if (name[l - 1] == '.') | ||
2024 | name[l - 1] = '\0'; | ||
2025 | if (errstr != NULL) | ||
2026 | *errstr = NULL; | ||
2027 | return 1; | ||
2028 | bad: | ||
2029 | if (errstr != NULL) | ||
2030 | *errstr = errbuf; | ||
2031 | return 0; | ||
2032 | } | ||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: misc.h,v 1.63 2017/08/18 05:48:04 djm Exp $ */ | 1 | /* $OpenBSD: misc.h,v 1.64 2017/10/21 23:06:24 millert Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
@@ -54,7 +54,9 @@ char *put_host_port(const char *, u_short); | |||
54 | char *hpdelim(char **); | 54 | char *hpdelim(char **); |
55 | char *cleanhostname(char *); | 55 | char *cleanhostname(char *); |
56 | char *colon(char *); | 56 | char *colon(char *); |
57 | int parse_user_host_path(const char *, char **, char **, char **); | ||
57 | int parse_user_host_port(const char *, char **, char **, int *); | 58 | int parse_user_host_port(const char *, char **, char **, int *); |
59 | int parse_uri(const char *, const char *, char **, char **, int *, char **); | ||
58 | long convtime(const char *); | 60 | long convtime(const char *); |
59 | char *tilde_expand_filename(const char *, uid_t); | 61 | char *tilde_expand_filename(const char *, uid_t); |
60 | char *percent_expand(const char *, ...) __attribute__((__sentinel__)); | 62 | char *percent_expand(const char *, ...) __attribute__((__sentinel__)); |
@@ -66,6 +68,7 @@ time_t monotime(void); | |||
66 | double monotime_double(void); | 68 | double monotime_double(void); |
67 | void lowercase(char *s); | 69 | void lowercase(char *s); |
68 | int unix_listener(const char *, int, int); | 70 | int unix_listener(const char *, int, int); |
71 | int valid_domain(char *, int, const char **); | ||
69 | 72 | ||
70 | void sock_set_v6only(int); | 73 | void sock_set_v6only(int); |
71 | 74 | ||
diff --git a/readconf.c b/readconf.c index f63894f9c..63baa7d78 100644 --- a/readconf.c +++ b/readconf.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: readconf.c,v 1.279 2017/09/21 19:16:53 markus Exp $ */ | 1 | /* $OpenBSD: readconf.c,v 1.280 2017/10/21 23:06:24 millert 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 |
@@ -683,34 +683,6 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw, | |||
683 | return result; | 683 | return result; |
684 | } | 684 | } |
685 | 685 | ||
686 | /* Check and prepare a domain name: removes trailing '.' and lowercases */ | ||
687 | static void | ||
688 | valid_domain(char *name, const char *filename, int linenum) | ||
689 | { | ||
690 | size_t i, l = strlen(name); | ||
691 | u_char c, last = '\0'; | ||
692 | |||
693 | if (l == 0) | ||
694 | fatal("%s line %d: empty hostname suffix", filename, linenum); | ||
695 | if (!isalpha((u_char)name[0]) && !isdigit((u_char)name[0])) | ||
696 | fatal("%s line %d: hostname suffix \"%.100s\" " | ||
697 | "starts with invalid character", filename, linenum, name); | ||
698 | for (i = 0; i < l; i++) { | ||
699 | c = tolower((u_char)name[i]); | ||
700 | name[i] = (char)c; | ||
701 | if (last == '.' && c == '.') | ||
702 | fatal("%s line %d: hostname suffix \"%.100s\" contains " | ||
703 | "consecutive separators", filename, linenum, name); | ||
704 | if (c != '.' && c != '-' && !isalnum(c) && | ||
705 | c != '_') /* technically invalid, but common */ | ||
706 | fatal("%s line %d: hostname suffix \"%.100s\" contains " | ||
707 | "invalid characters", filename, linenum, name); | ||
708 | last = c; | ||
709 | } | ||
710 | if (name[l - 1] == '.') | ||
711 | name[l - 1] = '\0'; | ||
712 | } | ||
713 | |||
714 | /* | 686 | /* |
715 | * Returns the number of the token pointed to by cp or oBadOption. | 687 | * Returns the number of the token pointed to by cp or oBadOption. |
716 | */ | 688 | */ |
@@ -1562,7 +1534,11 @@ parse_keytypes: | |||
1562 | case oCanonicalDomains: | 1534 | case oCanonicalDomains: |
1563 | value = options->num_canonical_domains != 0; | 1535 | value = options->num_canonical_domains != 0; |
1564 | while ((arg = strdelim(&s)) != NULL && *arg != '\0') { | 1536 | while ((arg = strdelim(&s)) != NULL && *arg != '\0') { |
1565 | valid_domain(arg, filename, linenum); | 1537 | const char *errstr; |
1538 | if (!valid_domain(arg, 1, &errstr)) { | ||
1539 | fatal("%s line %d: %s", filename, linenum, | ||
1540 | errstr); | ||
1541 | } | ||
1566 | if (!*activep || value) | 1542 | if (!*activep || value) |
1567 | continue; | 1543 | continue; |
1568 | if (options->num_canonical_domains >= MAX_CANON_DOMAINS) | 1544 | if (options->num_canonical_domains >= MAX_CANON_DOMAINS) |
@@ -2294,11 +2270,13 @@ parse_jump(const char *s, Options *o, int active) | |||
2294 | 2270 | ||
2295 | if (first) { | 2271 | if (first) { |
2296 | /* First argument and configuration is active */ | 2272 | /* First argument and configuration is active */ |
2297 | if (parse_user_host_port(cp, &user, &host, &port) != 0) | 2273 | if (parse_ssh_uri(cp, &user, &host, &port) == -1 || |
2274 | parse_user_host_port(cp, &user, &host, &port) != 0) | ||
2298 | goto out; | 2275 | goto out; |
2299 | } else { | 2276 | } else { |
2300 | /* Subsequent argument or inactive configuration */ | 2277 | /* Subsequent argument or inactive configuration */ |
2301 | if (parse_user_host_port(cp, NULL, NULL, NULL) != 0) | 2278 | if (parse_ssh_uri(cp, NULL, NULL, NULL) == -1 || |
2279 | parse_user_host_port(cp, NULL, NULL, NULL) != 0) | ||
2302 | goto out; | 2280 | goto out; |
2303 | } | 2281 | } |
2304 | first = 0; /* only check syntax for subsequent hosts */ | 2282 | first = 0; /* only check syntax for subsequent hosts */ |
@@ -2323,6 +2301,18 @@ parse_jump(const char *s, Options *o, int active) | |||
2323 | return ret; | 2301 | return ret; |
2324 | } | 2302 | } |
2325 | 2303 | ||
2304 | int | ||
2305 | parse_ssh_uri(const char *uri, char **userp, char **hostp, int *portp) | ||
2306 | { | ||
2307 | char *path; | ||
2308 | int r; | ||
2309 | |||
2310 | r = parse_uri("ssh", uri, userp, hostp, portp, &path); | ||
2311 | if (r == 0 && path != NULL) | ||
2312 | r = -1; /* path not allowed */ | ||
2313 | return r; | ||
2314 | } | ||
2315 | |||
2326 | /* XXX the following is a near-vebatim copy from servconf.c; refactor */ | 2316 | /* XXX the following is a near-vebatim copy from servconf.c; refactor */ |
2327 | static const char * | 2317 | static const char * |
2328 | fmt_multistate_int(int val, const struct multistate *m) | 2318 | fmt_multistate_int(int val, const struct multistate *m) |
diff --git a/readconf.h b/readconf.h index 22fe5c187..34aad83cf 100644 --- a/readconf.h +++ b/readconf.h | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: readconf.h,v 1.123 2017/09/03 23:33:13 djm Exp $ */ | 1 | /* $OpenBSD: readconf.h,v 1.124 2017/10/21 23:06:24 millert Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
@@ -204,6 +204,7 @@ int read_config_file(const char *, struct passwd *, const char *, | |||
204 | const char *, Options *, int); | 204 | const char *, Options *, int); |
205 | int parse_forward(struct Forward *, const char *, int, int); | 205 | int parse_forward(struct Forward *, const char *, int, int); |
206 | int parse_jump(const char *, Options *, int); | 206 | int parse_jump(const char *, Options *, int); |
207 | int parse_ssh_uri(const char *, char **, char **, int *); | ||
207 | int default_ssh_port(void); | 208 | int default_ssh_port(void); |
208 | int option_clear_or_none(const char *); | 209 | int option_clear_or_none(const char *); |
209 | void dump_client_config(Options *o, const char *host); | 210 | void dump_client_config(Options *o, const char *host); |
@@ -8,9 +8,9 @@ | |||
8 | .\" | 8 | .\" |
9 | .\" Created: Sun May 7 00:14:37 1995 ylo | 9 | .\" Created: Sun May 7 00:14:37 1995 ylo |
10 | .\" | 10 | .\" |
11 | .\" $OpenBSD: scp.1,v 1.74 2017/05/03 21:49:18 naddy Exp $ | 11 | .\" $OpenBSD: scp.1,v 1.75 2017/10/21 23:06:24 millert Exp $ |
12 | .\" | 12 | .\" |
13 | .Dd $Mdocdate: May 3 2017 $ | 13 | .Dd $Mdocdate: October 21 2017 $ |
14 | .Dt SCP 1 | 14 | .Dt SCP 1 |
15 | .Os | 15 | .Os |
16 | .Sh NAME | 16 | .Sh NAME |
@@ -27,20 +27,8 @@ | |||
27 | .Op Fl o Ar ssh_option | 27 | .Op Fl o Ar ssh_option |
28 | .Op Fl P Ar port | 28 | .Op Fl P Ar port |
29 | .Op Fl S Ar program | 29 | .Op Fl S Ar program |
30 | .Sm off | 30 | .Ar source ... |
31 | .Oo | 31 | .Ar target |
32 | .Op Ar user No @ | ||
33 | .Ar host1 : | ||
34 | .Oc Ar file1 | ||
35 | .Sm on | ||
36 | .Ar ... | ||
37 | .Sm off | ||
38 | .Oo | ||
39 | .Op Ar user No @ | ||
40 | .Ar host2 : | ||
41 | .Oc Ar file2 | ||
42 | .Sm on | ||
43 | .Ek | ||
44 | .Sh DESCRIPTION | 32 | .Sh DESCRIPTION |
45 | .Nm | 33 | .Nm |
46 | copies files between hosts on a network. | 34 | copies files between hosts on a network. |
@@ -53,15 +41,30 @@ same security as | |||
53 | will ask for passwords or passphrases if they are needed for | 41 | will ask for passwords or passphrases if they are needed for |
54 | authentication. | 42 | authentication. |
55 | .Pp | 43 | .Pp |
56 | File names may contain a user and host specification to indicate | 44 | The |
57 | that the file is to be copied to/from that host. | 45 | .Ar target |
46 | and | ||
47 | .Ar destination | ||
48 | may be specified as a local pathname, a remote host with optional path | ||
49 | in the form | ||
50 | .Oo Ar user Ns @ Oc Ns Ar host Ns : Ns Oo Ar path Oc , | ||
51 | or an scp URI in the form | ||
52 | .No scp:// Ns Oo Ar user Ns @ Oc Ns Ar host Ns | ||
53 | .Oo : Ns Ar port Oc Ns Oo / Ns Ar path Oc . | ||
58 | Local file names can be made explicit using absolute or relative pathnames | 54 | Local file names can be made explicit using absolute or relative pathnames |
59 | to avoid | 55 | to avoid |
60 | .Nm | 56 | .Nm |
61 | treating file names containing | 57 | treating file names containing |
62 | .Sq :\& | 58 | .Sq :\& |
63 | as host specifiers. | 59 | as host specifiers. |
64 | Copies between two remote hosts are also permitted. | 60 | .Pp |
61 | When copying between two remote hosts, if the URI format is used, a | ||
62 | .Ar port | ||
63 | may only be specified on the | ||
64 | .Ar target | ||
65 | if the | ||
66 | .Fl 3 | ||
67 | option is used. | ||
65 | .Pp | 68 | .Pp |
66 | The options are as follows: | 69 | The options are as follows: |
67 | .Bl -tag -width Ds | 70 | .Bl -tag -width Ds |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: scp.c,v 1.192 2017/05/31 09:15:42 deraadt Exp $ */ | 1 | /* $OpenBSD: scp.c,v 1.193 2017/10/21 23:06:24 millert Exp $ */ |
2 | /* | 2 | /* |
3 | * scp - secure remote copy. This is basically patched BSD rcp which | 3 | * scp - secure remote copy. This is basically patched BSD rcp which |
4 | * uses ssh to do the data transfer (instead of using rcmd). | 4 | * uses ssh to do the data transfer (instead of using rcmd). |
@@ -112,6 +112,7 @@ | |||
112 | #endif | 112 | #endif |
113 | 113 | ||
114 | #include "xmalloc.h" | 114 | #include "xmalloc.h" |
115 | #include "ssh.h" | ||
115 | #include "atomicio.h" | 116 | #include "atomicio.h" |
116 | #include "pathnames.h" | 117 | #include "pathnames.h" |
117 | #include "log.h" | 118 | #include "log.h" |
@@ -123,8 +124,8 @@ extern char *__progname; | |||
123 | 124 | ||
124 | #define COPY_BUFLEN 16384 | 125 | #define COPY_BUFLEN 16384 |
125 | 126 | ||
126 | int do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout); | 127 | int do_cmd(char *host, char *remuser, int port, char *cmd, int *fdin, int *fdout); |
127 | int do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout); | 128 | int do_cmd2(char *host, char *remuser, int port, char *cmd, int fdin, int fdout); |
128 | 129 | ||
129 | /* Struct for addargs */ | 130 | /* Struct for addargs */ |
130 | arglist args; | 131 | arglist args; |
@@ -149,6 +150,9 @@ int showprogress = 1; | |||
149 | */ | 150 | */ |
150 | int throughlocal = 0; | 151 | int throughlocal = 0; |
151 | 152 | ||
153 | /* Non-standard port to use for the ssh connection or -1. */ | ||
154 | int sshport = -1; | ||
155 | |||
152 | /* This is the program to execute for the secured connection. ("ssh" or -S) */ | 156 | /* This is the program to execute for the secured connection. ("ssh" or -S) */ |
153 | char *ssh_program = _PATH_SSH_PROGRAM; | 157 | char *ssh_program = _PATH_SSH_PROGRAM; |
154 | 158 | ||
@@ -231,7 +235,7 @@ do_local_cmd(arglist *a) | |||
231 | */ | 235 | */ |
232 | 236 | ||
233 | int | 237 | int |
234 | do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) | 238 | do_cmd(char *host, char *remuser, int port, char *cmd, int *fdin, int *fdout) |
235 | { | 239 | { |
236 | int pin[2], pout[2], reserved[2]; | 240 | int pin[2], pout[2], reserved[2]; |
237 | 241 | ||
@@ -241,6 +245,9 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) | |||
241 | ssh_program, host, | 245 | ssh_program, host, |
242 | remuser ? remuser : "(unspecified)", cmd); | 246 | remuser ? remuser : "(unspecified)", cmd); |
243 | 247 | ||
248 | if (port == -1) | ||
249 | port = sshport; | ||
250 | |||
244 | /* | 251 | /* |
245 | * Reserve two descriptors so that the real pipes won't get | 252 | * Reserve two descriptors so that the real pipes won't get |
246 | * descriptors 0 and 1 because that will screw up dup2 below. | 253 | * descriptors 0 and 1 because that will screw up dup2 below. |
@@ -274,6 +281,10 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) | |||
274 | close(pout[1]); | 281 | close(pout[1]); |
275 | 282 | ||
276 | replacearg(&args, 0, "%s", ssh_program); | 283 | replacearg(&args, 0, "%s", ssh_program); |
284 | if (port != -1) { | ||
285 | addargs(&args, "-p"); | ||
286 | addargs(&args, "%d", port); | ||
287 | } | ||
277 | if (remuser != NULL) { | 288 | if (remuser != NULL) { |
278 | addargs(&args, "-l"); | 289 | addargs(&args, "-l"); |
279 | addargs(&args, "%s", remuser); | 290 | addargs(&args, "%s", remuser); |
@@ -305,7 +316,7 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) | |||
305 | * This way the input and output of two commands can be connected. | 316 | * This way the input and output of two commands can be connected. |
306 | */ | 317 | */ |
307 | int | 318 | int |
308 | do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout) | 319 | do_cmd2(char *host, char *remuser, int port, char *cmd, int fdin, int fdout) |
309 | { | 320 | { |
310 | pid_t pid; | 321 | pid_t pid; |
311 | int status; | 322 | int status; |
@@ -316,6 +327,9 @@ do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout) | |||
316 | ssh_program, host, | 327 | ssh_program, host, |
317 | remuser ? remuser : "(unspecified)", cmd); | 328 | remuser ? remuser : "(unspecified)", cmd); |
318 | 329 | ||
330 | if (port == -1) | ||
331 | port = sshport; | ||
332 | |||
319 | /* Fork a child to execute the command on the remote host using ssh. */ | 333 | /* Fork a child to execute the command on the remote host using ssh. */ |
320 | pid = fork(); | 334 | pid = fork(); |
321 | if (pid == 0) { | 335 | if (pid == 0) { |
@@ -323,6 +337,10 @@ do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout) | |||
323 | dup2(fdout, 1); | 337 | dup2(fdout, 1); |
324 | 338 | ||
325 | replacearg(&args, 0, "%s", ssh_program); | 339 | replacearg(&args, 0, "%s", ssh_program); |
340 | if (port != -1) { | ||
341 | addargs(&args, "-p"); | ||
342 | addargs(&args, "%d", port); | ||
343 | } | ||
326 | if (remuser != NULL) { | 344 | if (remuser != NULL) { |
327 | addargs(&args, "-l"); | 345 | addargs(&args, "-l"); |
328 | addargs(&args, "%s", remuser); | 346 | addargs(&args, "%s", remuser); |
@@ -367,14 +385,14 @@ void rsource(char *, struct stat *); | |||
367 | void sink(int, char *[]); | 385 | void sink(int, char *[]); |
368 | void source(int, char *[]); | 386 | void source(int, char *[]); |
369 | void tolocal(int, char *[]); | 387 | void tolocal(int, char *[]); |
370 | void toremote(char *, int, char *[]); | 388 | void toremote(int, char *[]); |
371 | void usage(void); | 389 | void usage(void); |
372 | 390 | ||
373 | int | 391 | int |
374 | main(int argc, char **argv) | 392 | main(int argc, char **argv) |
375 | { | 393 | { |
376 | int ch, fflag, tflag, status, n; | 394 | int ch, fflag, tflag, status, n; |
377 | char *targ, **newargv; | 395 | char **newargv; |
378 | const char *errstr; | 396 | const char *errstr; |
379 | extern char *optarg; | 397 | extern char *optarg; |
380 | extern int optind; | 398 | extern int optind; |
@@ -430,10 +448,9 @@ main(int argc, char **argv) | |||
430 | addargs(&args, "%s", optarg); | 448 | addargs(&args, "%s", optarg); |
431 | break; | 449 | break; |
432 | case 'P': | 450 | case 'P': |
433 | addargs(&remote_remote_args, "-p"); | 451 | sshport = a2port(optarg); |
434 | addargs(&remote_remote_args, "%s", optarg); | 452 | if (sshport <= 0) |
435 | addargs(&args, "-p"); | 453 | fatal("bad port \"%s\"\n", optarg); |
436 | addargs(&args, "%s", optarg); | ||
437 | break; | 454 | break; |
438 | case 'B': | 455 | case 'B': |
439 | addargs(&remote_remote_args, "-oBatchmode=yes"); | 456 | addargs(&remote_remote_args, "-oBatchmode=yes"); |
@@ -533,8 +550,8 @@ main(int argc, char **argv) | |||
533 | 550 | ||
534 | (void) signal(SIGPIPE, lostconn); | 551 | (void) signal(SIGPIPE, lostconn); |
535 | 552 | ||
536 | if ((targ = colon(argv[argc - 1]))) /* Dest is remote host. */ | 553 | if (colon(argv[argc - 1])) /* Dest is remote host. */ |
537 | toremote(targ, argc, argv); | 554 | toremote(argc, argv); |
538 | else { | 555 | else { |
539 | if (targetshouldbedirectory) | 556 | if (targetshouldbedirectory) |
540 | verifydir(argv[argc - 1]); | 557 | verifydir(argv[argc - 1]); |
@@ -590,71 +607,65 @@ do_times(int fd, int verb, const struct stat *sb) | |||
590 | } | 607 | } |
591 | 608 | ||
592 | void | 609 | void |
593 | toremote(char *targ, int argc, char **argv) | 610 | toremote(int argc, char **argv) |
594 | { | 611 | { |
595 | char *bp, *host, *src, *suser, *thost, *tuser, *arg; | 612 | char *suser = NULL, *host = NULL, *src = NULL; |
613 | char *bp, *tuser, *thost, *targ; | ||
614 | int sport = -1, tport = -1; | ||
596 | arglist alist; | 615 | arglist alist; |
597 | int i; | 616 | int i, r; |
598 | u_int j; | 617 | u_int j; |
599 | 618 | ||
600 | memset(&alist, '\0', sizeof(alist)); | 619 | memset(&alist, '\0', sizeof(alist)); |
601 | alist.list = NULL; | 620 | alist.list = NULL; |
602 | 621 | ||
603 | *targ++ = 0; | 622 | /* Parse target */ |
604 | if (*targ == 0) | 623 | r = parse_uri("scp", argv[argc - 1], &tuser, &thost, &tport, &targ); |
605 | targ = "."; | 624 | if (r == -1) |
606 | 625 | goto out; /* invalid URI */ | |
607 | arg = xstrdup(argv[argc - 1]); | 626 | if (r != 0) { |
608 | if ((thost = strrchr(arg, '@'))) { | 627 | if (parse_user_host_path(argv[argc - 1], &tuser, &thost, |
609 | /* user@host */ | 628 | &targ) == -1) |
610 | *thost++ = 0; | 629 | goto out; |
611 | tuser = arg; | ||
612 | if (*tuser == '\0') | ||
613 | tuser = NULL; | ||
614 | } else { | ||
615 | thost = arg; | ||
616 | tuser = NULL; | ||
617 | } | ||
618 | |||
619 | if (tuser != NULL && !okname(tuser)) { | ||
620 | free(arg); | ||
621 | return; | ||
622 | } | 630 | } |
631 | if (tuser != NULL && !okname(tuser)) | ||
632 | goto out; | ||
623 | 633 | ||
634 | /* Parse source files */ | ||
624 | for (i = 0; i < argc - 1; i++) { | 635 | for (i = 0; i < argc - 1; i++) { |
625 | src = colon(argv[i]); | 636 | free(suser); |
626 | if (src && throughlocal) { /* extended remote to remote */ | 637 | free(host); |
627 | *src++ = 0; | 638 | free(src); |
628 | if (*src == 0) | 639 | r = parse_uri("scp", argv[i], &suser, &host, &sport, &src); |
629 | src = "."; | 640 | if (r == -1) |
630 | host = strrchr(argv[i], '@'); | 641 | continue; /* invalid URI */ |
631 | if (host) { | 642 | if (r != 0) |
632 | *host++ = 0; | 643 | parse_user_host_path(argv[i], &suser, &host, &src); |
633 | host = cleanhostname(host); | 644 | if (suser != NULL && !okname(suser)) { |
634 | suser = argv[i]; | 645 | ++errs; |
635 | if (*suser == '\0') | 646 | continue; |
636 | suser = pwd->pw_name; | 647 | } |
637 | else if (!okname(suser)) | 648 | if (host && throughlocal) { /* extended remote to remote */ |
638 | continue; | ||
639 | } else { | ||
640 | host = cleanhostname(argv[i]); | ||
641 | suser = NULL; | ||
642 | } | ||
643 | xasprintf(&bp, "%s -f %s%s", cmd, | 649 | xasprintf(&bp, "%s -f %s%s", cmd, |
644 | *src == '-' ? "-- " : "", src); | 650 | *src == '-' ? "-- " : "", src); |
645 | if (do_cmd(host, suser, bp, &remin, &remout) < 0) | 651 | if (do_cmd(host, suser, sport, bp, &remin, &remout) < 0) |
646 | exit(1); | 652 | exit(1); |
647 | free(bp); | 653 | free(bp); |
648 | host = cleanhostname(thost); | ||
649 | xasprintf(&bp, "%s -t %s%s", cmd, | 654 | xasprintf(&bp, "%s -t %s%s", cmd, |
650 | *targ == '-' ? "-- " : "", targ); | 655 | *targ == '-' ? "-- " : "", targ); |
651 | if (do_cmd2(host, tuser, bp, remin, remout) < 0) | 656 | if (do_cmd2(thost, tuser, tport, bp, remin, remout) < 0) |
652 | exit(1); | 657 | exit(1); |
653 | free(bp); | 658 | free(bp); |
654 | (void) close(remin); | 659 | (void) close(remin); |
655 | (void) close(remout); | 660 | (void) close(remout); |
656 | remin = remout = -1; | 661 | remin = remout = -1; |
657 | } else if (src) { /* standard remote to remote */ | 662 | } else if (host) { /* standard remote to remote */ |
663 | if (tport != -1 && tport != SSH_DEFAULT_PORT) { | ||
664 | /* This would require the remote support URIs */ | ||
665 | fatal("target port not supported with two " | ||
666 | "remote hosts without the -3 option"); | ||
667 | } | ||
668 | |||
658 | freeargs(&alist); | 669 | freeargs(&alist); |
659 | addargs(&alist, "%s", ssh_program); | 670 | addargs(&alist, "%s", ssh_program); |
660 | addargs(&alist, "-x"); | 671 | addargs(&alist, "-x"); |
@@ -664,23 +675,14 @@ toremote(char *targ, int argc, char **argv) | |||
664 | addargs(&alist, "%s", | 675 | addargs(&alist, "%s", |
665 | remote_remote_args.list[j]); | 676 | remote_remote_args.list[j]); |
666 | } | 677 | } |
667 | *src++ = 0; | 678 | |
668 | if (*src == 0) | 679 | if (sport != -1) { |
669 | src = "."; | 680 | addargs(&alist, "-p"); |
670 | host = strrchr(argv[i], '@'); | 681 | addargs(&alist, "%d", sport); |
671 | 682 | } | |
672 | if (host) { | 683 | if (suser) { |
673 | *host++ = 0; | ||
674 | host = cleanhostname(host); | ||
675 | suser = argv[i]; | ||
676 | if (*suser == '\0') | ||
677 | suser = pwd->pw_name; | ||
678 | else if (!okname(suser)) | ||
679 | continue; | ||
680 | addargs(&alist, "-l"); | 684 | addargs(&alist, "-l"); |
681 | addargs(&alist, "%s", suser); | 685 | addargs(&alist, "%s", suser); |
682 | } else { | ||
683 | host = cleanhostname(argv[i]); | ||
684 | } | 686 | } |
685 | addargs(&alist, "--"); | 687 | addargs(&alist, "--"); |
686 | addargs(&alist, "%s", host); | 688 | addargs(&alist, "%s", host); |
@@ -695,8 +697,7 @@ toremote(char *targ, int argc, char **argv) | |||
695 | if (remin == -1) { | 697 | if (remin == -1) { |
696 | xasprintf(&bp, "%s -t %s%s", cmd, | 698 | xasprintf(&bp, "%s -t %s%s", cmd, |
697 | *targ == '-' ? "-- " : "", targ); | 699 | *targ == '-' ? "-- " : "", targ); |
698 | host = cleanhostname(thost); | 700 | if (do_cmd(thost, tuser, tport, bp, &remin, |
699 | if (do_cmd(host, tuser, bp, &remin, | ||
700 | &remout) < 0) | 701 | &remout) < 0) |
701 | exit(1); | 702 | exit(1); |
702 | if (response() < 0) | 703 | if (response() < 0) |
@@ -706,21 +707,41 @@ toremote(char *targ, int argc, char **argv) | |||
706 | source(1, argv + i); | 707 | source(1, argv + i); |
707 | } | 708 | } |
708 | } | 709 | } |
709 | free(arg); | 710 | out: |
711 | free(tuser); | ||
712 | free(thost); | ||
713 | free(targ); | ||
714 | free(suser); | ||
715 | free(host); | ||
716 | free(src); | ||
710 | } | 717 | } |
711 | 718 | ||
712 | void | 719 | void |
713 | tolocal(int argc, char **argv) | 720 | tolocal(int argc, char **argv) |
714 | { | 721 | { |
715 | char *bp, *host, *src, *suser; | 722 | char *bp, *host = NULL, *src = NULL, *suser = NULL; |
716 | arglist alist; | 723 | arglist alist; |
717 | int i; | 724 | int i, r, sport = -1; |
718 | 725 | ||
719 | memset(&alist, '\0', sizeof(alist)); | 726 | memset(&alist, '\0', sizeof(alist)); |
720 | alist.list = NULL; | 727 | alist.list = NULL; |
721 | 728 | ||
722 | for (i = 0; i < argc - 1; i++) { | 729 | for (i = 0; i < argc - 1; i++) { |
723 | if (!(src = colon(argv[i]))) { /* Local to local. */ | 730 | free(suser); |
731 | free(host); | ||
732 | free(src); | ||
733 | r = parse_uri("scp", argv[i], &suser, &host, &sport, &src); | ||
734 | if (r == -1) { | ||
735 | ++errs; | ||
736 | continue; | ||
737 | } | ||
738 | if (r != 0) | ||
739 | parse_user_host_path(argv[i], &suser, &host, &src); | ||
740 | if (suser != NULL && !okname(suser)) { | ||
741 | ++errs; | ||
742 | continue; | ||
743 | } | ||
744 | if (!host) { /* Local to local. */ | ||
724 | freeargs(&alist); | 745 | freeargs(&alist); |
725 | addargs(&alist, "%s", _PATH_CP); | 746 | addargs(&alist, "%s", _PATH_CP); |
726 | if (iamrecursive) | 747 | if (iamrecursive) |
@@ -734,22 +755,10 @@ tolocal(int argc, char **argv) | |||
734 | ++errs; | 755 | ++errs; |
735 | continue; | 756 | continue; |
736 | } | 757 | } |
737 | *src++ = 0; | 758 | /* Remote to local. */ |
738 | if (*src == 0) | ||
739 | src = "."; | ||
740 | if ((host = strrchr(argv[i], '@')) == NULL) { | ||
741 | host = argv[i]; | ||
742 | suser = NULL; | ||
743 | } else { | ||
744 | *host++ = 0; | ||
745 | suser = argv[i]; | ||
746 | if (*suser == '\0') | ||
747 | suser = pwd->pw_name; | ||
748 | } | ||
749 | host = cleanhostname(host); | ||
750 | xasprintf(&bp, "%s -f %s%s", | 759 | xasprintf(&bp, "%s -f %s%s", |
751 | cmd, *src == '-' ? "-- " : "", src); | 760 | cmd, *src == '-' ? "-- " : "", src); |
752 | if (do_cmd(host, suser, bp, &remin, &remout) < 0) { | 761 | if (do_cmd(host, suser, sport, bp, &remin, &remout) < 0) { |
753 | free(bp); | 762 | free(bp); |
754 | ++errs; | 763 | ++errs; |
755 | continue; | 764 | continue; |
@@ -759,6 +768,9 @@ tolocal(int argc, char **argv) | |||
759 | (void) close(remin); | 768 | (void) close(remin); |
760 | remin = remout = -1; | 769 | remin = remout = -1; |
761 | } | 770 | } |
771 | free(suser); | ||
772 | free(host); | ||
773 | free(src); | ||
762 | } | 774 | } |
763 | 775 | ||
764 | void | 776 | void |
@@ -1275,8 +1287,7 @@ usage(void) | |||
1275 | { | 1287 | { |
1276 | (void) fprintf(stderr, | 1288 | (void) fprintf(stderr, |
1277 | "usage: scp [-346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n" | 1289 | "usage: scp [-346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n" |
1278 | " [-l limit] [-o ssh_option] [-P port] [-S program]\n" | 1290 | " [-l limit] [-o ssh_option] [-P port] [-S program] source ... target\n"); |
1279 | " [[user@]host1:]file1 ... [[user@]host2:]file2\n"); | ||
1280 | exit(1); | 1291 | exit(1); |
1281 | } | 1292 | } |
1282 | 1293 | ||
@@ -1,4 +1,4 @@ | |||
1 | .\" $OpenBSD: sftp.1,v 1.110 2017/05/03 21:49:18 naddy Exp $ | 1 | .\" $OpenBSD: sftp.1,v 1.111 2017/10/21 23:06:24 millert Exp $ |
2 | .\" | 2 | .\" |
3 | .\" Copyright (c) 2001 Damien Miller. All rights reserved. | 3 | .\" Copyright (c) 2001 Damien Miller. All rights reserved. |
4 | .\" | 4 | .\" |
@@ -22,7 +22,7 @@ | |||
22 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 22 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
23 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 23 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | .\" | 24 | .\" |
25 | .Dd $Mdocdate: May 3 2017 $ | 25 | .Dd $Mdocdate: October 21 2017 $ |
26 | .Dt SFTP 1 | 26 | .Dt SFTP 1 |
27 | .Os | 27 | .Os |
28 | .Sh NAME | 28 | .Sh NAME |
@@ -44,54 +44,52 @@ | |||
44 | .Op Fl R Ar num_requests | 44 | .Op Fl R Ar num_requests |
45 | .Op Fl S Ar program | 45 | .Op Fl S Ar program |
46 | .Op Fl s Ar subsystem | sftp_server | 46 | .Op Fl s Ar subsystem | sftp_server |
47 | .Ar host | 47 | .Ar destination |
48 | .Ek | ||
49 | .Nm sftp | ||
50 | .Oo Ar user Ns @ Oc Ns | ||
51 | .Ar host Ns Op : Ns Ar | ||
52 | .Nm sftp | ||
53 | .Oo | ||
54 | .Ar user Ns @ Oc Ns | ||
55 | .Ar host Ns Oo : Ns Ar dir Ns | ||
56 | .Op Ar / | ||
57 | .Oc | ||
58 | .Nm sftp | ||
59 | .Fl b Ar batchfile | ||
60 | .Oo Ar user Ns @ Oc Ns Ar host | ||
61 | .Sh DESCRIPTION | 48 | .Sh DESCRIPTION |
62 | .Nm | 49 | .Nm |
63 | is an interactive file transfer program, similar to | 50 | is a file transfer program, similar to |
64 | .Xr ftp 1 , | 51 | .Xr ftp 1 , |
65 | which performs all operations over an encrypted | 52 | which performs all operations over an encrypted |
66 | .Xr ssh 1 | 53 | .Xr ssh 1 |
67 | transport. | 54 | transport. |
68 | It may also use many features of ssh, such as public key authentication and | 55 | It may also use many features of ssh, such as public key authentication and |
69 | compression. | 56 | compression. |
70 | .Nm | ||
71 | connects and logs into the specified | ||
72 | .Ar host , | ||
73 | then enters an interactive command mode. | ||
74 | .Pp | 57 | .Pp |
75 | The second usage format will retrieve files automatically if a non-interactive | 58 | The |
59 | .Ar destination | ||
60 | may be specified either as | ||
61 | .Oo Ar user Ns @ Oc Ns Ar host Ns Oo : Ns Ar path Oc | ||
62 | or as an sftp URI in the form | ||
63 | .No sftp:// Ns Oo Ar user Ns @ Oc Ns Ar host Ns | ||
64 | .Oo : Ns Ar port Oc Ns Oo / Ns Ar path Oc . | ||
65 | .Pp | ||
66 | If the | ||
67 | .Ar destination | ||
68 | includes a | ||
69 | .Ar path | ||
70 | and it is not a directory, | ||
71 | .Nm | ||
72 | will retrieve files automatically if a non-interactive | ||
76 | authentication method is used; otherwise it will do so after | 73 | authentication method is used; otherwise it will do so after |
77 | successful interactive authentication. | 74 | successful interactive authentication. |
78 | .Pp | 75 | .Pp |
79 | The third usage format allows | 76 | If no |
77 | .Ar path | ||
78 | is specified, or if the | ||
79 | .Ar path | ||
80 | is a directory, | ||
80 | .Nm | 81 | .Nm |
81 | to start in a remote directory. | 82 | will log in to the specified |
82 | .Pp | 83 | .Ar host |
83 | The final usage format allows for automated sessions using the | 84 | and enter interactive command mode, changing to the remote directory |
84 | .Fl b | 85 | if one was specified. |
85 | option. | 86 | An optional trailing slash can be used to force the |
86 | In such cases, it is necessary to configure non-interactive authentication | 87 | .Ar path |
87 | to obviate the need to enter a password at connection time (see | 88 | to be interpreted as a directory. |
88 | .Xr sshd 8 | ||
89 | and | ||
90 | .Xr ssh-keygen 1 | ||
91 | for details). | ||
92 | .Pp | 89 | .Pp |
93 | Since some usage formats use colon characters to delimit host names from path | 90 | Since the destination formats use colon characters to delimit host |
94 | names, IPv6 addresses must be enclosed in square brackets to avoid ambiguity. | 91 | names from path names or port numbers, IPv6 addresses must be |
92 | enclosed in square brackets to avoid ambiguity. | ||
95 | .Pp | 93 | .Pp |
96 | The options are as follows: | 94 | The options are as follows: |
97 | .Bl -tag -width Ds | 95 | .Bl -tag -width Ds |
@@ -121,7 +119,12 @@ Batch mode reads a series of commands from an input | |||
121 | instead of | 119 | instead of |
122 | .Em stdin . | 120 | .Em stdin . |
123 | Since it lacks user interaction it should be used in conjunction with | 121 | Since it lacks user interaction it should be used in conjunction with |
124 | non-interactive authentication. | 122 | non-interactive authentication to obviate the need to enter a password |
123 | at connection time (see | ||
124 | .Xr sshd 8 | ||
125 | and | ||
126 | .Xr ssh-keygen 1 | ||
127 | for details). | ||
125 | A | 128 | A |
126 | .Ar batchfile | 129 | .Ar batchfile |
127 | of | 130 | of |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sftp.c,v 1.180 2017/06/10 06:33:34 djm Exp $ */ | 1 | /* $OpenBSD: sftp.c,v 1.181 2017/10/21 23:06:24 millert Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> | 3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> |
4 | * | 4 | * |
@@ -2301,19 +2301,16 @@ usage(void) | |||
2301 | "[-i identity_file] [-l limit]\n" | 2301 | "[-i identity_file] [-l limit]\n" |
2302 | " [-o ssh_option] [-P port] [-R num_requests] " | 2302 | " [-o ssh_option] [-P port] [-R num_requests] " |
2303 | "[-S program]\n" | 2303 | "[-S program]\n" |
2304 | " [-s subsystem | sftp_server] host\n" | 2304 | " [-s subsystem | sftp_server] destination\n", |
2305 | " %s [user@]host[:file ...]\n" | 2305 | __progname); |
2306 | " %s [user@]host[:dir[/]]\n" | ||
2307 | " %s -b batchfile [user@]host\n", | ||
2308 | __progname, __progname, __progname, __progname); | ||
2309 | exit(1); | 2306 | exit(1); |
2310 | } | 2307 | } |
2311 | 2308 | ||
2312 | int | 2309 | int |
2313 | main(int argc, char **argv) | 2310 | main(int argc, char **argv) |
2314 | { | 2311 | { |
2315 | int in, out, ch, err; | 2312 | int in, out, ch, err, tmp, port = -1; |
2316 | char *host = NULL, *userhost, *cp, *file2 = NULL; | 2313 | char *host = NULL, *user, *cp, *file2 = NULL; |
2317 | int debug_level = 0, sshver = 2; | 2314 | int debug_level = 0, sshver = 2; |
2318 | char *file1 = NULL, *sftp_server = NULL; | 2315 | char *file1 = NULL, *sftp_server = NULL; |
2319 | char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; | 2316 | char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; |
@@ -2368,7 +2365,9 @@ main(int argc, char **argv) | |||
2368 | addargs(&args, "-%c", ch); | 2365 | addargs(&args, "-%c", ch); |
2369 | break; | 2366 | break; |
2370 | case 'P': | 2367 | case 'P': |
2371 | addargs(&args, "-oPort %s", optarg); | 2368 | port = a2port(optarg); |
2369 | if (port <= 0) | ||
2370 | fatal("Bad port \"%s\"\n", optarg); | ||
2372 | break; | 2371 | break; |
2373 | case 'v': | 2372 | case 'v': |
2374 | if (debug_level < 3) { | 2373 | if (debug_level < 3) { |
@@ -2451,33 +2450,38 @@ main(int argc, char **argv) | |||
2451 | if (sftp_direct == NULL) { | 2450 | if (sftp_direct == NULL) { |
2452 | if (optind == argc || argc > (optind + 2)) | 2451 | if (optind == argc || argc > (optind + 2)) |
2453 | usage(); | 2452 | usage(); |
2453 | argv += optind; | ||
2454 | 2454 | ||
2455 | userhost = xstrdup(argv[optind]); | 2455 | switch (parse_uri("sftp", *argv, &user, &host, &tmp, &file1)) { |
2456 | file2 = argv[optind+1]; | 2456 | case -1: |
2457 | 2457 | usage(); | |
2458 | if ((host = strrchr(userhost, '@')) == NULL) | 2458 | break; |
2459 | host = userhost; | 2459 | case 0: |
2460 | else { | 2460 | if (tmp != -1) |
2461 | *host++ = '\0'; | 2461 | port = tmp; |
2462 | if (!userhost[0]) { | 2462 | break; |
2463 | fprintf(stderr, "Missing username\n"); | 2463 | default: |
2464 | usage(); | 2464 | if (parse_user_host_path(*argv, &user, &host, |
2465 | &file1) == -1) { | ||
2466 | /* Treat as a plain hostname. */ | ||
2467 | host = xstrdup(*argv); | ||
2468 | host = cleanhostname(host); | ||
2465 | } | 2469 | } |
2466 | addargs(&args, "-l"); | 2470 | break; |
2467 | addargs(&args, "%s", userhost); | ||
2468 | } | ||
2469 | |||
2470 | if ((cp = colon(host)) != NULL) { | ||
2471 | *cp++ = '\0'; | ||
2472 | file1 = cp; | ||
2473 | } | 2471 | } |
2472 | file2 = *(argv + 1); | ||
2474 | 2473 | ||
2475 | host = cleanhostname(host); | ||
2476 | if (!*host) { | 2474 | if (!*host) { |
2477 | fprintf(stderr, "Missing hostname\n"); | 2475 | fprintf(stderr, "Missing hostname\n"); |
2478 | usage(); | 2476 | usage(); |
2479 | } | 2477 | } |
2480 | 2478 | ||
2479 | if (port != -1) | ||
2480 | addargs(&args, "-oPort %d", port); | ||
2481 | if (user != NULL) { | ||
2482 | addargs(&args, "-l"); | ||
2483 | addargs(&args, "%s", user); | ||
2484 | } | ||
2481 | addargs(&args, "-oProtocol %d", sshver); | 2485 | addargs(&args, "-oProtocol %d", sshver); |
2482 | 2486 | ||
2483 | /* no subsystem if the server-spec contains a '/' */ | 2487 | /* no subsystem if the server-spec contains a '/' */ |
@@ -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.1,v 1.385 2017/10/13 06:45:18 djm Exp $ | 36 | .\" $OpenBSD: ssh.1,v 1.386 2017/10/21 23:06:24 millert Exp $ |
37 | .Dd $Mdocdate: October 13 2017 $ | 37 | .Dd $Mdocdate: October 21 2017 $ |
38 | .Dt SSH 1 | 38 | .Dt SSH 1 |
39 | .Os | 39 | .Os |
40 | .Sh NAME | 40 | .Sh NAME |
@@ -52,7 +52,7 @@ | |||
52 | .Op Fl F Ar configfile | 52 | .Op Fl F Ar configfile |
53 | .Op Fl I Ar pkcs11 | 53 | .Op Fl I Ar pkcs11 |
54 | .Op Fl i Ar identity_file | 54 | .Op Fl i Ar identity_file |
55 | .Op Fl J Oo Ar user Ns @ Oc Ns Ar host Ns Op : Ns Ar port | 55 | .Op Fl J Ar destination |
56 | .Op Fl L Ar address | 56 | .Op Fl L Ar address |
57 | .Op Fl l Ar login_name | 57 | .Op Fl l Ar login_name |
58 | .Op Fl m Ar mac_spec | 58 | .Op Fl m Ar mac_spec |
@@ -64,7 +64,7 @@ | |||
64 | .Op Fl S Ar ctl_path | 64 | .Op Fl S Ar ctl_path |
65 | .Op Fl W Ar host : Ns Ar port | 65 | .Op Fl W Ar host : Ns Ar port |
66 | .Op Fl w Ar local_tun Ns Op : Ns Ar remote_tun | 66 | .Op Fl w Ar local_tun Ns Op : Ns Ar remote_tun |
67 | .Oo Ar user Ns @ Oc Ns Ar hostname | 67 | .Ar destination |
68 | .Op Ar command | 68 | .Op Ar command |
69 | .Ek | 69 | .Ek |
70 | .Sh DESCRIPTION | 70 | .Sh DESCRIPTION |
@@ -79,15 +79,23 @@ sockets can also be forwarded over the secure channel. | |||
79 | .Pp | 79 | .Pp |
80 | .Nm | 80 | .Nm |
81 | connects and logs into the specified | 81 | connects and logs into the specified |
82 | .Ar hostname | 82 | .Ar destination |
83 | (with optional | 83 | which may be specified as either |
84 | .Oo Ar user Ns @ Oc Ns Ar hostname | ||
85 | where the | ||
86 | .Ar user | ||
87 | is optional, or an ssh URI of the form | ||
88 | .No ssh:// Ns Oo Ar user Ns @ Oc Ns Ar hostname Ns Oo : Ns Ar port Oc | ||
89 | where the | ||
84 | .Ar user | 90 | .Ar user |
85 | name). | 91 | and |
92 | .Ar port | ||
93 | are optional. | ||
86 | The user must prove | 94 | The user must prove |
87 | his/her identity to the remote machine using one of several methods | 95 | his/her identity to the remote machine using one of several methods |
88 | (see below). | 96 | (see below). |
89 | .Pp | 97 | .Pp |
90 | If | 98 | If a |
91 | .Ar command | 99 | .Ar command |
92 | is specified, | 100 | is specified, |
93 | it is executed on the remote host instead of a login shell. | 101 | it is executed on the remote host instead of a login shell. |
@@ -287,17 +295,11 @@ by appending | |||
287 | .Pa -cert.pub | 295 | .Pa -cert.pub |
288 | to identity filenames. | 296 | to identity filenames. |
289 | .Pp | 297 | .Pp |
290 | .It Fl J Xo | 298 | .It Fl J Ar destination |
291 | .Sm off | ||
292 | .Op Ar user No @ | ||
293 | .Ar host | ||
294 | .Op : Ar port | ||
295 | .Sm on | ||
296 | .Xc | ||
297 | Connect to the target host by first making a | 299 | Connect to the target host by first making a |
298 | .Nm | 300 | .Nm |
299 | connection to the jump | 301 | connection to the jump host described by |
300 | .Ar host | 302 | .Ar destination |
301 | and then establishing a TCP forwarding to the ultimate destination from | 303 | and then establishing a TCP forwarding to the ultimate destination from |
302 | there. | 304 | there. |
303 | Multiple jump hops may be specified separated by comma characters. | 305 | Multiple jump hops may be specified separated by comma characters. |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: ssh.c,v 1.464 2017/09/21 19:16:53 markus Exp $ */ | 1 | /* $OpenBSD: ssh.c,v 1.465 2017/10/21 23:06:24 millert 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 |
@@ -203,7 +203,7 @@ usage(void) | |||
203 | " [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec]\n" | 203 | " [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec]\n" |
204 | " [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address]\n" | 204 | " [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address]\n" |
205 | " [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]\n" | 205 | " [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]\n" |
206 | " [user@]hostname [command]\n" | 206 | " destination [command]\n" |
207 | ); | 207 | ); |
208 | exit(255); | 208 | exit(255); |
209 | } | 209 | } |
@@ -846,14 +846,18 @@ main(int ac, char **av) | |||
846 | options.control_master = SSHCTL_MASTER_YES; | 846 | options.control_master = SSHCTL_MASTER_YES; |
847 | break; | 847 | break; |
848 | case 'p': | 848 | case 'p': |
849 | options.port = a2port(optarg); | 849 | if (options.port == -1) { |
850 | if (options.port <= 0) { | 850 | options.port = a2port(optarg); |
851 | fprintf(stderr, "Bad port '%s'\n", optarg); | 851 | if (options.port <= 0) { |
852 | exit(255); | 852 | fprintf(stderr, "Bad port '%s'\n", |
853 | optarg); | ||
854 | exit(255); | ||
855 | } | ||
853 | } | 856 | } |
854 | break; | 857 | break; |
855 | case 'l': | 858 | case 'l': |
856 | options.user = optarg; | 859 | if (options.user == NULL) |
860 | options.user = optarg; | ||
857 | break; | 861 | break; |
858 | 862 | ||
859 | case 'L': | 863 | case 'L': |
@@ -933,16 +937,38 @@ main(int ac, char **av) | |||
933 | av += optind; | 937 | av += optind; |
934 | 938 | ||
935 | if (ac > 0 && !host) { | 939 | if (ac > 0 && !host) { |
936 | if (strrchr(*av, '@')) { | 940 | int tport; |
941 | char *tuser; | ||
942 | switch (parse_ssh_uri(*av, &tuser, &host, &tport)) { | ||
943 | case -1: | ||
944 | usage(); | ||
945 | break; | ||
946 | case 0: | ||
947 | if (options.user == NULL) { | ||
948 | options.user = tuser; | ||
949 | tuser = NULL; | ||
950 | } | ||
951 | free(tuser); | ||
952 | if (options.port == -1 && tport != -1) | ||
953 | options.port = tport; | ||
954 | break; | ||
955 | default: | ||
937 | p = xstrdup(*av); | 956 | p = xstrdup(*av); |
938 | cp = strrchr(p, '@'); | 957 | cp = strrchr(p, '@'); |
939 | if (cp == NULL || cp == p) | 958 | if (cp != NULL) { |
940 | usage(); | 959 | if (cp == p) |
941 | options.user = p; | 960 | usage(); |
942 | *cp = '\0'; | 961 | if (options.user == NULL) { |
943 | host = xstrdup(++cp); | 962 | options.user = p; |
944 | } else | 963 | p = NULL; |
945 | host = xstrdup(*av); | 964 | } |
965 | *cp++ = '\0'; | ||
966 | host = xstrdup(cp); | ||
967 | free(p); | ||
968 | } else | ||
969 | host = p; | ||
970 | break; | ||
971 | } | ||
946 | if (ac > 1 && !opt_terminated) { | 972 | if (ac > 1 && !opt_terminated) { |
947 | optind = optreset = 1; | 973 | optind = optreset = 1; |
948 | goto again; | 974 | goto again; |
diff --git a/ssh_config.5 b/ssh_config.5 index 96e6904bc..c04701044 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.259 2017/10/18 05:36:59 jmc Exp $ | 36 | .\" $OpenBSD: ssh_config.5,v 1.260 2017/10/21 23:06:24 millert Exp $ |
37 | .Dd $Mdocdate: October 18 2017 $ | 37 | .Dd $Mdocdate: October 21 2017 $ |
38 | .Dt SSH_CONFIG 5 | 38 | .Dt SSH_CONFIG 5 |
39 | .Os | 39 | .Os |
40 | .Sh NAME | 40 | .Sh NAME |
@@ -1198,13 +1198,14 @@ For example, the following directive would connect via an HTTP proxy at | |||
1198 | ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p | 1198 | ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p |
1199 | .Ed | 1199 | .Ed |
1200 | .It Cm ProxyJump | 1200 | .It Cm ProxyJump |
1201 | Specifies one or more jump proxies as | 1201 | Specifies one or more jump proxies as either |
1202 | .Xo | 1202 | .Xo |
1203 | .Sm off | 1203 | .Sm off |
1204 | .Op Ar user No @ | 1204 | .Op Ar user No @ |
1205 | .Ar host | 1205 | .Ar host |
1206 | .Op : Ns Ar port | 1206 | .Op : Ns Ar port |
1207 | .Sm on | 1207 | .Sm on |
1208 | or an ssh URI | ||
1208 | .Xc . | 1209 | .Xc . |
1209 | Multiple proxies may be separated by comma characters and will be visited | 1210 | Multiple proxies may be separated by comma characters and will be visited |
1210 | sequentially. | 1211 | sequentially. |