diff options
-rw-r--r-- | auth-options.c | 829 | ||||
-rw-r--r-- | auth-options.h | 70 |
2 files changed, 897 insertions, 2 deletions
diff --git a/auth-options.c b/auth-options.c index bed00eef0..8b93b51e5 100644 --- a/auth-options.c +++ b/auth-options.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: auth-options.c,v 1.74 2017/09/12 06:32:07 djm Exp $ */ | 1 | /* $OpenBSD: auth-options.c,v 1.75 2018/03/03 03:06:02 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 |
@@ -9,6 +9,21 @@ | |||
9 | * incompatible with the protocol description in the RFC file, it must be | 9 | * incompatible with the protocol description in the RFC file, it must be |
10 | * called by a name other than "ssh" or "Secure Shell". | 10 | * called by a name other than "ssh" or "Secure Shell". |
11 | */ | 11 | */ |
12 | /* | ||
13 | * Copyright (c) 2018 Damien Miller <djm@mindrot.org> | ||
14 | * | ||
15 | * Permission to use, copy, modify, and distribute this software for any | ||
16 | * purpose with or without fee is hereby granted, provided that the above | ||
17 | * copyright notice and this permission notice appear in all copies. | ||
18 | * | ||
19 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
20 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
21 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
22 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
23 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
24 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
25 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
26 | */ | ||
12 | 27 | ||
13 | #include "includes.h" | 28 | #include "includes.h" |
14 | 29 | ||
@@ -19,6 +34,8 @@ | |||
19 | #include <string.h> | 34 | #include <string.h> |
20 | #include <stdio.h> | 35 | #include <stdio.h> |
21 | #include <stdarg.h> | 36 | #include <stdarg.h> |
37 | #include <ctype.h> | ||
38 | #include <limits.h> | ||
22 | 39 | ||
23 | #include "openbsd-compat/sys-queue.h" | 40 | #include "openbsd-compat/sys-queue.h" |
24 | 41 | ||
@@ -27,6 +44,7 @@ | |||
27 | #include "xmalloc.h" | 44 | #include "xmalloc.h" |
28 | #include "match.h" | 45 | #include "match.h" |
29 | #include "ssherr.h" | 46 | #include "ssherr.h" |
47 | #include "ssh2.h" | ||
30 | #include "log.h" | 48 | #include "log.h" |
31 | #include "canohost.h" | 49 | #include "canohost.h" |
32 | #include "packet.h" | 50 | #include "packet.h" |
@@ -661,3 +679,812 @@ auth_cert_options(struct sshkey *k, struct passwd *pw, const char **reason) | |||
661 | return 0; | 679 | return 0; |
662 | } | 680 | } |
663 | 681 | ||
682 | /* | ||
683 | * authorized_keys options processing. | ||
684 | */ | ||
685 | |||
686 | /* | ||
687 | * Match flag 'opt' in *optsp, and if allow_negate is set then also match | ||
688 | * 'no-opt'. Returns -1 if option not matched, 1 if option matches or 0 | ||
689 | * if negated option matches. | ||
690 | * If the option or negated option matches, then *optsp is updated to | ||
691 | * point to the first character after the option. | ||
692 | */ | ||
693 | static int | ||
694 | opt_flag(const char *opt, int allow_negate, const char **optsp) | ||
695 | { | ||
696 | size_t opt_len = strlen(opt); | ||
697 | const char *opts = *optsp; | ||
698 | int negate = 0; | ||
699 | |||
700 | if (allow_negate && strncasecmp(opts, "no-", 3) == 0) { | ||
701 | opts += 3; | ||
702 | negate = 1; | ||
703 | } | ||
704 | if (strncasecmp(opts, opt, opt_len) == 0) { | ||
705 | *optsp = opts + opt_len; | ||
706 | return negate ? 0 : 1; | ||
707 | } | ||
708 | return -1; | ||
709 | } | ||
710 | |||
711 | static char * | ||
712 | opt_dequote(const char **sp, const char **errstrp) | ||
713 | { | ||
714 | const char *s = *sp; | ||
715 | char *ret; | ||
716 | size_t i; | ||
717 | |||
718 | *errstrp = NULL; | ||
719 | if (*s != '"') { | ||
720 | *errstrp = "missing start quote"; | ||
721 | return NULL; | ||
722 | } | ||
723 | s++; | ||
724 | if ((ret = malloc(strlen((s)) + 1)) == NULL) { | ||
725 | *errstrp = "memory allocation failed"; | ||
726 | return NULL; | ||
727 | } | ||
728 | for (i = 0; *s != '\0' && *s != '"';) { | ||
729 | if (s[0] == '\\' && s[1] == '"') | ||
730 | s++; | ||
731 | ret[i++] = *s++; | ||
732 | } | ||
733 | if (*s == '\0') { | ||
734 | *errstrp = "missing end quote"; | ||
735 | free(ret); | ||
736 | return NULL; | ||
737 | } | ||
738 | ret[i] = '\0'; | ||
739 | s++; | ||
740 | *sp = s; | ||
741 | return ret; | ||
742 | } | ||
743 | |||
744 | static int | ||
745 | opt_match(const char **opts, const char *term) | ||
746 | { | ||
747 | if (strncasecmp((*opts), term, strlen(term)) == 0 && | ||
748 | (*opts)[strlen(term)] == '=') { | ||
749 | *opts += strlen(term) + 1; | ||
750 | return 1; | ||
751 | } | ||
752 | return 0; | ||
753 | } | ||
754 | |||
755 | static int | ||
756 | dup_strings(char ***dstp, size_t *ndstp, char **src, size_t nsrc) | ||
757 | { | ||
758 | char **dst; | ||
759 | size_t i, j; | ||
760 | |||
761 | *dstp = NULL; | ||
762 | *ndstp = 0; | ||
763 | if (nsrc == 0) | ||
764 | return 0; | ||
765 | |||
766 | if ((dst = calloc(nsrc, sizeof(*src))) == NULL) | ||
767 | return -1; | ||
768 | for (i = 0; i < nsrc; i++) { | ||
769 | if ((dst[i] = strdup(src[i])) == NULL) { | ||
770 | for (j = 0; j < i; j++) | ||
771 | free(dst[j]); | ||
772 | free(dst); | ||
773 | return -1; | ||
774 | } | ||
775 | } | ||
776 | /* success */ | ||
777 | *dstp = dst; | ||
778 | *ndstp = nsrc; | ||
779 | return 0; | ||
780 | } | ||
781 | |||
782 | #define OPTIONS_CRITICAL 1 | ||
783 | #define OPTIONS_EXTENSIONS 2 | ||
784 | static int | ||
785 | cert_option_list(struct sshauthopt *opts, struct sshbuf *oblob, | ||
786 | u_int which, int crit) | ||
787 | { | ||
788 | char *command, *allowed; | ||
789 | char *name = NULL; | ||
790 | struct sshbuf *c = NULL, *data = NULL; | ||
791 | int r, ret = -1, found; | ||
792 | |||
793 | if ((c = sshbuf_fromb(oblob)) == NULL) { | ||
794 | error("%s: sshbuf_fromb failed", __func__); | ||
795 | goto out; | ||
796 | } | ||
797 | |||
798 | while (sshbuf_len(c) > 0) { | ||
799 | sshbuf_free(data); | ||
800 | data = NULL; | ||
801 | if ((r = sshbuf_get_cstring(c, &name, NULL)) != 0 || | ||
802 | (r = sshbuf_froms(c, &data)) != 0) { | ||
803 | error("Unable to parse certificate options: %s", | ||
804 | ssh_err(r)); | ||
805 | goto out; | ||
806 | } | ||
807 | debug3("found certificate option \"%.100s\" len %zu", | ||
808 | name, sshbuf_len(data)); | ||
809 | found = 0; | ||
810 | if ((which & OPTIONS_EXTENSIONS) != 0) { | ||
811 | if (strcmp(name, "permit-X11-forwarding") == 0) { | ||
812 | opts->permit_x11_forwarding_flag = 1; | ||
813 | found = 1; | ||
814 | } else if (strcmp(name, | ||
815 | "permit-agent-forwarding") == 0) { | ||
816 | opts->permit_agent_forwarding_flag = 1; | ||
817 | found = 1; | ||
818 | } else if (strcmp(name, | ||
819 | "permit-port-forwarding") == 0) { | ||
820 | opts->permit_port_forwarding_flag = 1; | ||
821 | found = 1; | ||
822 | } else if (strcmp(name, "permit-pty") == 0) { | ||
823 | opts->permit_pty_flag = 1; | ||
824 | found = 1; | ||
825 | } else if (strcmp(name, "permit-user-rc") == 0) { | ||
826 | opts->permit_user_rc = 1; | ||
827 | found = 1; | ||
828 | } | ||
829 | } | ||
830 | if (!found && (which & OPTIONS_CRITICAL) != 0) { | ||
831 | if (strcmp(name, "force-command") == 0) { | ||
832 | if ((r = sshbuf_get_cstring(data, &command, | ||
833 | NULL)) != 0) { | ||
834 | error("Unable to parse \"%s\" " | ||
835 | "section: %s", name, ssh_err(r)); | ||
836 | goto out; | ||
837 | } | ||
838 | if (opts->force_command != NULL) { | ||
839 | error("Certificate has multiple " | ||
840 | "force-command options"); | ||
841 | free(command); | ||
842 | goto out; | ||
843 | } | ||
844 | opts->force_command = command; | ||
845 | found = 1; | ||
846 | } | ||
847 | if (strcmp(name, "source-address") == 0) { | ||
848 | if ((r = sshbuf_get_cstring(data, &allowed, | ||
849 | NULL)) != 0) { | ||
850 | error("Unable to parse \"%s\" " | ||
851 | "section: %s", name, ssh_err(r)); | ||
852 | goto out; | ||
853 | } | ||
854 | if (opts->required_from_host_cert != NULL) { | ||
855 | error("Certificate has multiple " | ||
856 | "source-address options"); | ||
857 | free(allowed); | ||
858 | goto out; | ||
859 | } | ||
860 | /* Check syntax */ | ||
861 | if (addr_match_cidr_list(NULL, allowed) == -1) { | ||
862 | error("Certificate source-address " | ||
863 | "contents invalid"); | ||
864 | goto out; | ||
865 | } | ||
866 | opts->required_from_host_cert = allowed; | ||
867 | found = 1; | ||
868 | } | ||
869 | } | ||
870 | |||
871 | if (!found) { | ||
872 | if (crit) { | ||
873 | error("Certificate critical option \"%s\" " | ||
874 | "is not supported", name); | ||
875 | goto out; | ||
876 | } else { | ||
877 | logit("Certificate extension \"%s\" " | ||
878 | "is not supported", name); | ||
879 | } | ||
880 | } else if (sshbuf_len(data) != 0) { | ||
881 | error("Certificate option \"%s\" corrupt " | ||
882 | "(extra data)", name); | ||
883 | goto out; | ||
884 | } | ||
885 | free(name); | ||
886 | name = NULL; | ||
887 | } | ||
888 | /* successfully parsed all options */ | ||
889 | ret = 0; | ||
890 | |||
891 | out: | ||
892 | free(name); | ||
893 | sshbuf_free(data); | ||
894 | sshbuf_free(c); | ||
895 | return ret; | ||
896 | } | ||
897 | |||
898 | struct sshauthopt * | ||
899 | sshauthopt_new(void) | ||
900 | { | ||
901 | struct sshauthopt *ret; | ||
902 | |||
903 | if ((ret = calloc(1, sizeof(*ret))) == NULL) | ||
904 | return NULL; | ||
905 | ret->force_tun_device = -1; | ||
906 | return ret; | ||
907 | } | ||
908 | |||
909 | void | ||
910 | sshauthopt_free(struct sshauthopt *opts) | ||
911 | { | ||
912 | size_t i; | ||
913 | |||
914 | if (opts == NULL) | ||
915 | return; | ||
916 | |||
917 | free(opts->cert_principals); | ||
918 | free(opts->force_command); | ||
919 | free(opts->required_from_host_cert); | ||
920 | free(opts->required_from_host_keys); | ||
921 | |||
922 | for (i = 0; i < opts->nenv; i++) | ||
923 | free(opts->env[i]); | ||
924 | free(opts->env); | ||
925 | |||
926 | for (i = 0; i < opts->npermitopen; i++) | ||
927 | free(opts->permitopen[i]); | ||
928 | free(opts->permitopen); | ||
929 | |||
930 | explicit_bzero(opts, sizeof(*opts)); | ||
931 | free(opts); | ||
932 | } | ||
933 | |||
934 | struct sshauthopt * | ||
935 | sshauthopt_new_with_keys_defaults(void) | ||
936 | { | ||
937 | struct sshauthopt *ret = NULL; | ||
938 | |||
939 | if ((ret = sshauthopt_new()) == NULL) | ||
940 | return NULL; | ||
941 | |||
942 | /* Defaults for authorized_keys flags */ | ||
943 | ret->permit_port_forwarding_flag = 1; | ||
944 | ret->permit_agent_forwarding_flag = 1; | ||
945 | ret->permit_x11_forwarding_flag = 1; | ||
946 | ret->permit_pty_flag = 1; | ||
947 | ret->permit_user_rc = 1; | ||
948 | return ret; | ||
949 | } | ||
950 | |||
951 | struct sshauthopt * | ||
952 | sshauthopt_parse(const char *opts, const char **errstrp) | ||
953 | { | ||
954 | char **oarray, *opt, *cp, *tmp, *host; | ||
955 | int r; | ||
956 | struct sshauthopt *ret = NULL; | ||
957 | const char *errstr = "unknown error"; | ||
958 | |||
959 | if (errstrp != NULL) | ||
960 | *errstrp = NULL; | ||
961 | if ((ret = sshauthopt_new_with_keys_defaults()) == NULL) | ||
962 | goto alloc_fail; | ||
963 | |||
964 | if (opts == NULL) | ||
965 | return ret; | ||
966 | |||
967 | while (*opts && *opts != ' ' && *opts != '\t') { | ||
968 | /* flag options */ | ||
969 | if ((r = opt_flag("restrict", 0, &opts)) != -1) { | ||
970 | ret->restricted = 1; | ||
971 | ret->permit_port_forwarding_flag = 0; | ||
972 | ret->permit_agent_forwarding_flag = 0; | ||
973 | ret->permit_x11_forwarding_flag = 0; | ||
974 | ret->permit_pty_flag = 0; | ||
975 | ret->permit_user_rc = 0; | ||
976 | } else if ((r = opt_flag("cert-authority", 0, &opts)) != -1) { | ||
977 | ret->cert_authority = r; | ||
978 | } else if ((r = opt_flag("port-forwarding", 1, &opts)) != -1) { | ||
979 | ret->permit_port_forwarding_flag = r == 1; | ||
980 | } else if ((r = opt_flag("agent-forwarding", 1, &opts)) != -1) { | ||
981 | ret->permit_agent_forwarding_flag = r == 1; | ||
982 | } else if ((r = opt_flag("x11-forwarding", 1, &opts)) != -1) { | ||
983 | ret->permit_x11_forwarding_flag = r == 1; | ||
984 | } else if ((r = opt_flag("pty", 1, &opts)) != -1) { | ||
985 | ret->permit_pty_flag = r == 1; | ||
986 | } else if ((r = opt_flag("user-rc", 1, &opts)) != -1) { | ||
987 | ret->permit_user_rc = r == 1; | ||
988 | } else if (opt_match(&opts, "command")) { | ||
989 | if (ret->force_command != NULL) { | ||
990 | errstr = "multiple \"command\" clauses"; | ||
991 | goto fail; | ||
992 | } | ||
993 | ret->force_command = opt_dequote(&opts, &errstr); | ||
994 | if (ret->force_command == NULL) | ||
995 | goto fail; | ||
996 | } else if (opt_match(&opts, "principals")) { | ||
997 | if (ret->cert_principals != NULL) { | ||
998 | errstr = "multiple \"principals\" clauses"; | ||
999 | goto fail; | ||
1000 | } | ||
1001 | ret->cert_principals = opt_dequote(&opts, &errstr); | ||
1002 | if (ret->cert_principals == NULL) | ||
1003 | goto fail; | ||
1004 | } else if (opt_match(&opts, "from")) { | ||
1005 | if (ret->required_from_host_keys != NULL) { | ||
1006 | errstr = "multiple \"from\" clauses"; | ||
1007 | goto fail; | ||
1008 | } | ||
1009 | ret->required_from_host_keys = opt_dequote(&opts, | ||
1010 | &errstr); | ||
1011 | if (ret->required_from_host_keys == NULL) | ||
1012 | goto fail; | ||
1013 | } else if (opt_match(&opts, "environment")) { | ||
1014 | if (ret->nenv > INT_MAX) { | ||
1015 | errstr = "too many environment strings"; | ||
1016 | goto fail; | ||
1017 | } | ||
1018 | if ((opt = opt_dequote(&opts, &errstr)) == NULL) | ||
1019 | goto fail; | ||
1020 | /* env name must be alphanumeric and followed by '=' */ | ||
1021 | if ((tmp = strchr(opt, '=')) == NULL) { | ||
1022 | free(opt); | ||
1023 | errstr = "invalid environment string"; | ||
1024 | goto fail; | ||
1025 | } | ||
1026 | for (cp = opt; cp < tmp; cp++) { | ||
1027 | if (!isalnum((u_char)*cp)) { | ||
1028 | free(opt); | ||
1029 | errstr = "invalid environment string"; | ||
1030 | goto fail; | ||
1031 | } | ||
1032 | } | ||
1033 | /* Append it. */ | ||
1034 | oarray = ret->env; | ||
1035 | if ((ret->env = recallocarray(ret->env, ret->nenv, | ||
1036 | ret->nenv + 1, sizeof(*ret->env))) == NULL) { | ||
1037 | free(opt); | ||
1038 | ret->env = oarray; /* put it back for cleanup */ | ||
1039 | goto alloc_fail; | ||
1040 | } | ||
1041 | ret->env[ret->nenv++] = opt; | ||
1042 | } else if (opt_match(&opts, "permitopen")) { | ||
1043 | if (ret->npermitopen > INT_MAX) { | ||
1044 | errstr = "too many permitopens"; | ||
1045 | goto fail; | ||
1046 | } | ||
1047 | if ((opt = opt_dequote(&opts, &errstr)) == NULL) | ||
1048 | goto fail; | ||
1049 | if ((tmp = strdup(opt)) == NULL) { | ||
1050 | free(opt); | ||
1051 | goto alloc_fail; | ||
1052 | } | ||
1053 | cp = tmp; | ||
1054 | /* validate syntax of permitopen before recording it. */ | ||
1055 | host = hpdelim(&cp); | ||
1056 | if (host == NULL || strlen(host) >= NI_MAXHOST) { | ||
1057 | free(tmp); | ||
1058 | free(opt); | ||
1059 | errstr = "invalid permitopen hostname"; | ||
1060 | goto fail; | ||
1061 | } | ||
1062 | /* | ||
1063 | * don't want to use permitopen_port to avoid | ||
1064 | * dependency on channels.[ch] here. | ||
1065 | */ | ||
1066 | if (cp == NULL || | ||
1067 | (strcmp(cp, "*") != 0 && a2port(cp) <= 0)) { | ||
1068 | free(tmp); | ||
1069 | free(opt); | ||
1070 | errstr = "invalid permitopen port"; | ||
1071 | goto fail; | ||
1072 | } | ||
1073 | /* XXX - add streamlocal support */ | ||
1074 | free(tmp); | ||
1075 | /* Record it */ | ||
1076 | oarray = ret->permitopen; | ||
1077 | if ((ret->permitopen = recallocarray(ret->permitopen, | ||
1078 | ret->npermitopen, ret->npermitopen + 1, | ||
1079 | sizeof(*ret->permitopen))) == NULL) { | ||
1080 | free(opt); | ||
1081 | ret->permitopen = oarray; | ||
1082 | goto alloc_fail; | ||
1083 | } | ||
1084 | ret->permitopen[ret->npermitopen++] = opt; | ||
1085 | } else if (opt_match(&opts, "tunnel")) { | ||
1086 | if ((opt = opt_dequote(&opts, &errstr)) == NULL) | ||
1087 | goto fail; | ||
1088 | ret->force_tun_device = a2tun(opt, NULL); | ||
1089 | free(opt); | ||
1090 | if (ret->force_tun_device == SSH_TUNID_ERR) { | ||
1091 | errstr = "invalid tun device"; | ||
1092 | goto fail; | ||
1093 | } | ||
1094 | } | ||
1095 | /* | ||
1096 | * Skip the comma, and move to the next option | ||
1097 | * (or break out if there are no more). | ||
1098 | */ | ||
1099 | if (*opts == '\0' || *opts == ' ' || *opts == '\t') | ||
1100 | break; /* End of options. */ | ||
1101 | /* Anything other than a comma is an unknown option */ | ||
1102 | if (*opts != ',') { | ||
1103 | errstr = "unknown key option"; | ||
1104 | goto fail; | ||
1105 | } | ||
1106 | opts++; | ||
1107 | if (*opts == '\0') { | ||
1108 | errstr = "unexpected end-of-options"; | ||
1109 | goto fail; | ||
1110 | } | ||
1111 | } | ||
1112 | |||
1113 | /* success */ | ||
1114 | if (errstrp != NULL) | ||
1115 | *errstrp = NULL; | ||
1116 | return ret; | ||
1117 | |||
1118 | alloc_fail: | ||
1119 | errstr = "memory allocation failed"; | ||
1120 | fail: | ||
1121 | sshauthopt_free(ret); | ||
1122 | if (errstrp != NULL) | ||
1123 | *errstrp = errstr; | ||
1124 | return NULL; | ||
1125 | } | ||
1126 | |||
1127 | struct sshauthopt * | ||
1128 | sshauthopt_from_cert(struct sshkey *k) | ||
1129 | { | ||
1130 | struct sshauthopt *ret; | ||
1131 | |||
1132 | if (k == NULL || !sshkey_type_is_cert(k->type) || k->cert == NULL || | ||
1133 | k->cert->type != SSH2_CERT_TYPE_USER) | ||
1134 | return NULL; | ||
1135 | |||
1136 | if ((ret = sshauthopt_new()) == NULL) | ||
1137 | return NULL; | ||
1138 | |||
1139 | /* Handle options and critical extensions separately */ | ||
1140 | if (cert_option_list(ret, k->cert->critical, | ||
1141 | OPTIONS_CRITICAL, 1) == -1) { | ||
1142 | sshauthopt_free(ret); | ||
1143 | return NULL; | ||
1144 | } | ||
1145 | if (cert_option_list(ret, k->cert->extensions, | ||
1146 | OPTIONS_EXTENSIONS, 0) == -1) { | ||
1147 | sshauthopt_free(ret); | ||
1148 | return NULL; | ||
1149 | } | ||
1150 | /* success */ | ||
1151 | return ret; | ||
1152 | } | ||
1153 | |||
1154 | /* | ||
1155 | * Merges "additional" options to "primary" and returns the result. | ||
1156 | * NB. Some options from primary have primacy. | ||
1157 | */ | ||
1158 | struct sshauthopt * | ||
1159 | sshauthopt_merge(const struct sshauthopt *primary, | ||
1160 | const struct sshauthopt *additional, const char **errstrp) | ||
1161 | { | ||
1162 | struct sshauthopt *ret; | ||
1163 | const char *errstr = "internal error"; | ||
1164 | const char *tmp; | ||
1165 | |||
1166 | if (errstrp != NULL) | ||
1167 | *errstrp = NULL; | ||
1168 | |||
1169 | if ((ret = sshauthopt_new()) == NULL) | ||
1170 | goto alloc_fail; | ||
1171 | |||
1172 | /* cert_authority and cert_principals are cleared in result */ | ||
1173 | |||
1174 | /* Prefer access lists from primary. */ | ||
1175 | /* XXX err is both set and mismatch? */ | ||
1176 | tmp = primary->required_from_host_cert; | ||
1177 | if (tmp == NULL) | ||
1178 | tmp = additional->required_from_host_cert; | ||
1179 | if (tmp != NULL && (ret->required_from_host_cert = strdup(tmp)) == NULL) | ||
1180 | goto alloc_fail; | ||
1181 | tmp = primary->required_from_host_keys; | ||
1182 | if (tmp == NULL) | ||
1183 | tmp = additional->required_from_host_keys; | ||
1184 | if (tmp != NULL && (ret->required_from_host_keys = strdup(tmp)) == NULL) | ||
1185 | goto alloc_fail; | ||
1186 | |||
1187 | /* force_tun_device, permitopen and environment prefer the primary. */ | ||
1188 | ret->force_tun_device = primary->force_tun_device; | ||
1189 | if (ret->force_tun_device == -1) | ||
1190 | ret->force_tun_device = additional->force_tun_device; | ||
1191 | if (primary->nenv > 0) { | ||
1192 | if (dup_strings(&ret->env, &ret->nenv, | ||
1193 | primary->env, primary->nenv) != 0) | ||
1194 | goto alloc_fail; | ||
1195 | } else if (additional->nenv) { | ||
1196 | if (dup_strings(&ret->env, &ret->nenv, | ||
1197 | additional->env, additional->nenv) != 0) | ||
1198 | goto alloc_fail; | ||
1199 | } | ||
1200 | if (primary->npermitopen > 0) { | ||
1201 | if (dup_strings(&ret->permitopen, &ret->npermitopen, | ||
1202 | primary->permitopen, primary->npermitopen) != 0) | ||
1203 | goto alloc_fail; | ||
1204 | } else if (additional->npermitopen > 0) { | ||
1205 | if (dup_strings(&ret->permitopen, &ret->npermitopen, | ||
1206 | additional->permitopen, additional->npermitopen) != 0) | ||
1207 | goto alloc_fail; | ||
1208 | } | ||
1209 | |||
1210 | /* Flags are logical-AND (i.e. must be set in both for permission) */ | ||
1211 | #define OPTFLAG(x) ret->x = (primary->x == 1) && (additional->x == 1) | ||
1212 | OPTFLAG(permit_port_forwarding_flag); | ||
1213 | OPTFLAG(permit_agent_forwarding_flag); | ||
1214 | OPTFLAG(permit_x11_forwarding_flag); | ||
1215 | OPTFLAG(permit_pty_flag); | ||
1216 | OPTFLAG(permit_user_rc); | ||
1217 | #undef OPTFLAG | ||
1218 | |||
1219 | /* | ||
1220 | * When both multiple forced-command are specified, only | ||
1221 | * proceed if they are identical, otherwise fail. | ||
1222 | */ | ||
1223 | if (primary->force_command != NULL && | ||
1224 | additional->force_command != NULL) { | ||
1225 | if (strcmp(primary->force_command, | ||
1226 | additional->force_command) == 0) { | ||
1227 | /* ok */ | ||
1228 | ret->force_command = strdup(primary->force_command); | ||
1229 | if (ret->force_command == NULL) | ||
1230 | goto alloc_fail; | ||
1231 | } else { | ||
1232 | errstr = "forced command options do not match"; | ||
1233 | goto fail; | ||
1234 | } | ||
1235 | } else if (primary->force_command != NULL) { | ||
1236 | if ((ret->force_command = strdup( | ||
1237 | primary->force_command)) == NULL) | ||
1238 | goto alloc_fail; | ||
1239 | } else if (additional->force_command != NULL) { | ||
1240 | if ((ret->force_command = strdup( | ||
1241 | additional->force_command)) == NULL) | ||
1242 | goto alloc_fail; | ||
1243 | } | ||
1244 | /* success */ | ||
1245 | if (errstrp != NULL) | ||
1246 | *errstrp = NULL; | ||
1247 | return ret; | ||
1248 | |||
1249 | alloc_fail: | ||
1250 | errstr = "memory allocation failed"; | ||
1251 | fail: | ||
1252 | if (errstrp != NULL) | ||
1253 | *errstrp = errstr; | ||
1254 | sshauthopt_free(ret); | ||
1255 | return NULL; | ||
1256 | } | ||
1257 | |||
1258 | /* | ||
1259 | * Copy options | ||
1260 | */ | ||
1261 | struct sshauthopt * | ||
1262 | sshauthopt_copy(const struct sshauthopt *orig) | ||
1263 | { | ||
1264 | struct sshauthopt *ret; | ||
1265 | |||
1266 | if ((ret = sshauthopt_new()) == NULL) | ||
1267 | return NULL; | ||
1268 | |||
1269 | #define OPTSCALAR(x) ret->x = orig->x | ||
1270 | OPTSCALAR(permit_port_forwarding_flag); | ||
1271 | OPTSCALAR(permit_agent_forwarding_flag); | ||
1272 | OPTSCALAR(permit_x11_forwarding_flag); | ||
1273 | OPTSCALAR(permit_pty_flag); | ||
1274 | OPTSCALAR(permit_user_rc); | ||
1275 | OPTSCALAR(restricted); | ||
1276 | OPTSCALAR(cert_authority); | ||
1277 | OPTSCALAR(force_tun_device); | ||
1278 | #undef OPTSCALAR | ||
1279 | #define OPTSTRING(x) \ | ||
1280 | do { \ | ||
1281 | if (orig->x != NULL && (ret->x = strdup(orig->x)) == NULL) { \ | ||
1282 | sshauthopt_free(ret); \ | ||
1283 | return NULL; \ | ||
1284 | } \ | ||
1285 | } while (0) | ||
1286 | OPTSTRING(cert_principals); | ||
1287 | OPTSTRING(force_command); | ||
1288 | OPTSTRING(required_from_host_cert); | ||
1289 | OPTSTRING(required_from_host_keys); | ||
1290 | #undef OPTSTRING | ||
1291 | |||
1292 | if (dup_strings(&ret->env, &ret->nenv, orig->env, orig->nenv) != 0 || | ||
1293 | dup_strings(&ret->permitopen, &ret->npermitopen, | ||
1294 | orig->permitopen, orig->npermitopen) != 0) { | ||
1295 | sshauthopt_free(ret); | ||
1296 | return NULL; | ||
1297 | } | ||
1298 | return ret; | ||
1299 | } | ||
1300 | |||
1301 | static int | ||
1302 | serialise_array(struct sshbuf *m, char **a, size_t n) | ||
1303 | { | ||
1304 | struct sshbuf *b; | ||
1305 | size_t i; | ||
1306 | int r; | ||
1307 | |||
1308 | if (n > INT_MAX) | ||
1309 | return SSH_ERR_INTERNAL_ERROR; | ||
1310 | |||
1311 | if ((b = sshbuf_new()) == NULL) { | ||
1312 | return SSH_ERR_ALLOC_FAIL; | ||
1313 | } | ||
1314 | for (i = 0; i < n; i++) { | ||
1315 | if ((r = sshbuf_put_cstring(b, a[i])) != 0) { | ||
1316 | sshbuf_free(b); | ||
1317 | return r; | ||
1318 | } | ||
1319 | } | ||
1320 | if ((r = sshbuf_put_u32(m, n)) != 0 || | ||
1321 | (r = sshbuf_put_stringb(m, b)) != 0) { | ||
1322 | sshbuf_free(b); | ||
1323 | return r; | ||
1324 | } | ||
1325 | /* success */ | ||
1326 | return 0; | ||
1327 | } | ||
1328 | |||
1329 | static int | ||
1330 | deserialise_array(struct sshbuf *m, char ***ap, size_t *np) | ||
1331 | { | ||
1332 | char **a = NULL; | ||
1333 | size_t i, n = 0; | ||
1334 | struct sshbuf *b = NULL; | ||
1335 | u_int tmp; | ||
1336 | int r = SSH_ERR_INTERNAL_ERROR; | ||
1337 | |||
1338 | if ((r = sshbuf_get_u32(m, &tmp)) != 0 || | ||
1339 | (r = sshbuf_froms(m, &b)) != 0) | ||
1340 | goto out; | ||
1341 | if (tmp > INT_MAX) { | ||
1342 | r = SSH_ERR_INVALID_FORMAT; | ||
1343 | goto out; | ||
1344 | } | ||
1345 | n = tmp; | ||
1346 | if (n > 0 && (a = calloc(n, sizeof(*a))) == NULL) { | ||
1347 | r = SSH_ERR_ALLOC_FAIL; | ||
1348 | goto out; | ||
1349 | } | ||
1350 | for (i = 0; i < n; i++) { | ||
1351 | if ((r = sshbuf_get_cstring(b, &a[i], NULL)) != 0) | ||
1352 | goto out; | ||
1353 | } | ||
1354 | /* success */ | ||
1355 | r = 0; | ||
1356 | *ap = a; | ||
1357 | a = NULL; | ||
1358 | *np = n; | ||
1359 | n = 0; | ||
1360 | out: | ||
1361 | for (i = 0; i < n; i++) | ||
1362 | free(a[i]); | ||
1363 | free(a); | ||
1364 | sshbuf_free(b); | ||
1365 | return r; | ||
1366 | } | ||
1367 | |||
1368 | static int | ||
1369 | serialise_nullable_string(struct sshbuf *m, const char *s) | ||
1370 | { | ||
1371 | int r; | ||
1372 | |||
1373 | if ((r = sshbuf_put_u8(m, s == NULL)) != 0 || | ||
1374 | (r = sshbuf_put_cstring(m, s)) != 0) | ||
1375 | return r; | ||
1376 | return 0; | ||
1377 | } | ||
1378 | |||
1379 | static int | ||
1380 | deserialise_nullable_string(struct sshbuf *m, char **sp) | ||
1381 | { | ||
1382 | int r; | ||
1383 | u_char flag; | ||
1384 | |||
1385 | *sp = NULL; | ||
1386 | if ((r = sshbuf_get_u8(m, &flag)) != 0 || | ||
1387 | (r = sshbuf_get_cstring(m, flag ? NULL : sp, NULL)) != 0) | ||
1388 | return r; | ||
1389 | return 0; | ||
1390 | } | ||
1391 | |||
1392 | int | ||
1393 | sshauthopt_serialise(const struct sshauthopt *opts, struct sshbuf *m, | ||
1394 | int untrusted) | ||
1395 | { | ||
1396 | int r = SSH_ERR_INTERNAL_ERROR; | ||
1397 | |||
1398 | /* Flag options */ | ||
1399 | if ((r = sshbuf_put_u8(m, opts->permit_port_forwarding_flag)) != 0 || | ||
1400 | (r = sshbuf_put_u8(m, opts->permit_agent_forwarding_flag)) != 0 || | ||
1401 | (r = sshbuf_put_u8(m, opts->permit_x11_forwarding_flag)) != 0 || | ||
1402 | (r = sshbuf_put_u8(m, opts->permit_pty_flag)) != 0 || | ||
1403 | (r = sshbuf_put_u8(m, opts->permit_user_rc)) != 0 || | ||
1404 | (r = sshbuf_put_u8(m, opts->restricted)) != 0 || | ||
1405 | (r = sshbuf_put_u8(m, opts->cert_authority)) != 0) | ||
1406 | return r; | ||
1407 | |||
1408 | /* tunnel number can be negative to indicate "unset" */ | ||
1409 | if ((r = sshbuf_put_u8(m, opts->force_tun_device == -1)) != 0 || | ||
1410 | (r = sshbuf_put_u32(m, (opts->force_tun_device < 0) ? | ||
1411 | 0 : (u_int)opts->force_tun_device)) != 0) | ||
1412 | return r; | ||
1413 | |||
1414 | /* String options; these may be NULL */ | ||
1415 | if ((r = serialise_nullable_string(m, | ||
1416 | untrusted ? "yes" : opts->cert_principals)) != 0 || | ||
1417 | (r = serialise_nullable_string(m, | ||
1418 | untrusted ? "true" : opts->force_command)) != 0 || | ||
1419 | (r = serialise_nullable_string(m, | ||
1420 | untrusted ? NULL : opts->required_from_host_cert)) != 0 || | ||
1421 | (r = serialise_nullable_string(m, | ||
1422 | untrusted ? NULL : opts->required_from_host_keys)) != 0) | ||
1423 | return r; | ||
1424 | |||
1425 | /* Array options */ | ||
1426 | if ((r = serialise_array(m, opts->env, | ||
1427 | untrusted ? 0 : opts->nenv)) != 0 || | ||
1428 | (r = serialise_array(m, opts->permitopen, | ||
1429 | untrusted ? 0 : opts->npermitopen)) != 0) | ||
1430 | return r; | ||
1431 | |||
1432 | /* success */ | ||
1433 | return 0; | ||
1434 | } | ||
1435 | |||
1436 | int | ||
1437 | sshauthopt_deserialise(struct sshbuf *m, struct sshauthopt **optsp) | ||
1438 | { | ||
1439 | struct sshauthopt *opts = NULL; | ||
1440 | int r = SSH_ERR_INTERNAL_ERROR; | ||
1441 | u_char f; | ||
1442 | u_int tmp; | ||
1443 | |||
1444 | if ((opts = calloc(1, sizeof(*opts))) == NULL) | ||
1445 | return SSH_ERR_ALLOC_FAIL; | ||
1446 | |||
1447 | #define OPT_FLAG(x) \ | ||
1448 | do { \ | ||
1449 | if ((r = sshbuf_get_u8(m, &f)) != 0) \ | ||
1450 | goto out; \ | ||
1451 | opts->x = f; \ | ||
1452 | } while (0) | ||
1453 | OPT_FLAG(permit_port_forwarding_flag); | ||
1454 | OPT_FLAG(permit_agent_forwarding_flag); | ||
1455 | OPT_FLAG(permit_x11_forwarding_flag); | ||
1456 | OPT_FLAG(permit_pty_flag); | ||
1457 | OPT_FLAG(permit_user_rc); | ||
1458 | OPT_FLAG(restricted); | ||
1459 | OPT_FLAG(cert_authority); | ||
1460 | #undef OPT_FLAG | ||
1461 | |||
1462 | /* tunnel number can be negative to indicate "unset" */ | ||
1463 | if ((r = sshbuf_get_u8(m, &f)) != 0 || | ||
1464 | (r = sshbuf_get_u32(m, &tmp)) != 0) | ||
1465 | goto out; | ||
1466 | opts->force_tun_device = f ? -1 : (int)tmp; | ||
1467 | |||
1468 | /* String options may be NULL */ | ||
1469 | if ((r = deserialise_nullable_string(m, &opts->cert_principals)) != 0 || | ||
1470 | (r = deserialise_nullable_string(m, &opts->force_command)) != 0 || | ||
1471 | (r = deserialise_nullable_string(m, | ||
1472 | &opts->required_from_host_cert)) != 0 || | ||
1473 | (r = deserialise_nullable_string(m, | ||
1474 | &opts->required_from_host_keys)) != 0) | ||
1475 | goto out; | ||
1476 | |||
1477 | /* Array options */ | ||
1478 | if ((r = deserialise_array(m, &opts->env, &opts->nenv)) != 0 || | ||
1479 | (r = deserialise_array(m, | ||
1480 | &opts->permitopen, &opts->npermitopen)) != 0) | ||
1481 | goto out; | ||
1482 | |||
1483 | /* success */ | ||
1484 | r = 0; | ||
1485 | *optsp = opts; | ||
1486 | opts = NULL; | ||
1487 | out: | ||
1488 | sshauthopt_free(opts); | ||
1489 | return r; | ||
1490 | } | ||
diff --git a/auth-options.h b/auth-options.h index 547f01635..0dbfc325e 100644 --- a/auth-options.h +++ b/auth-options.h | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: auth-options.h,v 1.23 2017/05/31 10:54:00 markus Exp $ */ | 1 | /* $OpenBSD: auth-options.h,v 1.24 2018/03/03 03:06:02 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
@@ -15,6 +15,9 @@ | |||
15 | #ifndef AUTH_OPTIONS_H | 15 | #ifndef AUTH_OPTIONS_H |
16 | #define AUTH_OPTIONS_H | 16 | #define AUTH_OPTIONS_H |
17 | 17 | ||
18 | struct passwd; | ||
19 | struct sshkey; | ||
20 | |||
18 | /* Linked list of custom environment strings */ | 21 | /* Linked list of custom environment strings */ |
19 | struct envstring { | 22 | struct envstring { |
20 | struct envstring *next; | 23 | struct envstring *next; |
@@ -37,4 +40,69 @@ int auth_parse_options(struct passwd *, char *, const char *, u_long); | |||
37 | void auth_clear_options(void); | 40 | void auth_clear_options(void); |
38 | int auth_cert_options(struct sshkey *, struct passwd *, const char **); | 41 | int auth_cert_options(struct sshkey *, struct passwd *, const char **); |
39 | 42 | ||
43 | /* authorized_keys options handling */ | ||
44 | |||
45 | /* | ||
46 | * sshauthopt represents key options parsed from authorized_keys or | ||
47 | * from certificate extensions/options. | ||
48 | */ | ||
49 | struct sshauthopt { | ||
50 | /* Feature flags */ | ||
51 | int permit_port_forwarding_flag; | ||
52 | int permit_agent_forwarding_flag; | ||
53 | int permit_x11_forwarding_flag; | ||
54 | int permit_pty_flag; | ||
55 | int permit_user_rc; | ||
56 | |||
57 | /* "restrict" keyword was invoked */ | ||
58 | int restricted; | ||
59 | |||
60 | /* Certificate-related options */ | ||
61 | int cert_authority; | ||
62 | char *cert_principals; | ||
63 | |||
64 | int force_tun_device; | ||
65 | char *force_command; | ||
66 | |||
67 | /* Custom environment */ | ||
68 | size_t nenv; | ||
69 | char **env; | ||
70 | |||
71 | /* Permitted port forwardings */ | ||
72 | size_t npermitopen; | ||
73 | char **permitopen; | ||
74 | |||
75 | /* | ||
76 | * Permitted host/addresses (comma-separated) | ||
77 | * Caller must check source address matches both lists (if present). | ||
78 | */ | ||
79 | char *required_from_host_cert; | ||
80 | char *required_from_host_keys; | ||
81 | }; | ||
82 | |||
83 | struct sshauthopt *sshauthopt_new(void); | ||
84 | struct sshauthopt *sshauthopt_new_with_keys_defaults(void); | ||
85 | void sshauthopt_free(struct sshauthopt *opts); | ||
86 | struct sshauthopt *sshauthopt_copy(const struct sshauthopt *orig); | ||
87 | int sshauthopt_serialise(const struct sshauthopt *opts, struct sshbuf *m, int); | ||
88 | int sshauthopt_deserialise(struct sshbuf *m, struct sshauthopt **opts); | ||
89 | |||
90 | /* | ||
91 | * Parse authorized_keys options. Returns an options structure on success | ||
92 | * or NULL on failure. Will set errstr on failure. | ||
93 | */ | ||
94 | struct sshauthopt *sshauthopt_parse(const char *s, const char **errstr); | ||
95 | |||
96 | /* | ||
97 | * Parse certification options to a struct sshauthopt. | ||
98 | * Returns options on success or NULL on failure. | ||
99 | */ | ||
100 | struct sshauthopt *sshauthopt_from_cert(struct sshkey *k); | ||
101 | |||
102 | /* | ||
103 | * Merge key options. | ||
104 | */ | ||
105 | struct sshauthopt *sshauthopt_merge(const struct sshauthopt *primary, | ||
106 | const struct sshauthopt *additional, const char **errstrp); | ||
107 | |||
40 | #endif | 108 | #endif |