diff options
author | millert@openbsd.org <millert@openbsd.org> | 2017-10-21 23:06:24 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2017-10-23 16:10:08 +1100 |
commit | 887669ef032d63cf07f53cada216fa8a0c9a7d72 (patch) | |
tree | 089b20255da21a489d7bc796a8ee86bd0b8f028f /misc.c | |
parent | d27bff293cfeb2252f4c7a58babe5ad3262c6c98 (diff) |
upstream commit
Add URI support to ssh, sftp and scp. For example
ssh://user@host or sftp://user@host/path. The connection parameters
described in draft-ietf-secsh-scp-sftp-ssh-uri-04 are not implemented since
the ssh fingerprint format in the draft uses md5 with no way to specify the
hash function type. OK djm@
Upstream-ID: 4ba3768b662d6722de59e6ecb00abf2d4bf9cacc
Diffstat (limited to 'misc.c')
-rw-r--r-- | misc.c | 297 |
1 files changed, 292 insertions, 5 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 | } | ||