diff options
Diffstat (limited to 'auth.c')
-rw-r--r-- | auth.c | 351 |
1 files changed, 346 insertions, 5 deletions
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: auth.c,v 1.124 2017/09/12 06:32:07 djm Exp $ */ | 1 | /* $OpenBSD: auth.c,v 1.127 2018/03/12 00:52:01 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
4 | * | 4 | * |
@@ -28,6 +28,7 @@ | |||
28 | #include <sys/types.h> | 28 | #include <sys/types.h> |
29 | #include <sys/stat.h> | 29 | #include <sys/stat.h> |
30 | #include <sys/socket.h> | 30 | #include <sys/socket.h> |
31 | #include <sys/wait.h> | ||
31 | 32 | ||
32 | #include <netinet/in.h> | 33 | #include <netinet/in.h> |
33 | 34 | ||
@@ -73,12 +74,14 @@ | |||
73 | #include "authfile.h" | 74 | #include "authfile.h" |
74 | #include "ssherr.h" | 75 | #include "ssherr.h" |
75 | #include "compat.h" | 76 | #include "compat.h" |
77 | #include "channels.h" | ||
76 | 78 | ||
77 | /* import */ | 79 | /* import */ |
78 | extern ServerOptions options; | 80 | extern ServerOptions options; |
79 | extern int use_privsep; | 81 | extern int use_privsep; |
80 | extern Buffer loginmsg; | 82 | extern Buffer loginmsg; |
81 | extern struct passwd *privsep_pw; | 83 | extern struct passwd *privsep_pw; |
84 | extern struct sshauthopt *auth_opts; | ||
82 | 85 | ||
83 | /* Debugging messages */ | 86 | /* Debugging messages */ |
84 | Buffer auth_debug; | 87 | Buffer auth_debug; |
@@ -385,10 +388,8 @@ auth_maxtries_exceeded(Authctxt *authctxt) | |||
385 | * Check whether root logins are disallowed. | 388 | * Check whether root logins are disallowed. |
386 | */ | 389 | */ |
387 | int | 390 | int |
388 | auth_root_allowed(const char *method) | 391 | auth_root_allowed(struct ssh *ssh, const char *method) |
389 | { | 392 | { |
390 | struct ssh *ssh = active_state; /* XXX */ | ||
391 | |||
392 | switch (options.permit_root_login) { | 393 | switch (options.permit_root_login) { |
393 | case PERMIT_YES: | 394 | case PERMIT_YES: |
394 | return 1; | 395 | return 1; |
@@ -400,7 +401,7 @@ auth_root_allowed(const char *method) | |||
400 | return 1; | 401 | return 1; |
401 | break; | 402 | break; |
402 | case PERMIT_FORCED_ONLY: | 403 | case PERMIT_FORCED_ONLY: |
403 | if (forced_command) { | 404 | if (auth_opts->force_command != NULL) { |
404 | logit("Root login accepted for forced command."); | 405 | logit("Root login accepted for forced command."); |
405 | return 1; | 406 | return 1; |
406 | } | 407 | } |
@@ -747,3 +748,343 @@ auth_get_canonical_hostname(struct ssh *ssh, int use_dns) | |||
747 | return dnsname; | 748 | return dnsname; |
748 | } | 749 | } |
749 | } | 750 | } |
751 | |||
752 | /* | ||
753 | * Runs command in a subprocess wuth a minimal environment. | ||
754 | * Returns pid on success, 0 on failure. | ||
755 | * The child stdout and stderr maybe captured, left attached or sent to | ||
756 | * /dev/null depending on the contents of flags. | ||
757 | * "tag" is prepended to log messages. | ||
758 | * NB. "command" is only used for logging; the actual command executed is | ||
759 | * av[0]. | ||
760 | */ | ||
761 | pid_t | ||
762 | subprocess(const char *tag, struct passwd *pw, const char *command, | ||
763 | int ac, char **av, FILE **child, u_int flags) | ||
764 | { | ||
765 | FILE *f = NULL; | ||
766 | struct stat st; | ||
767 | int fd, devnull, p[2], i; | ||
768 | pid_t pid; | ||
769 | char *cp, errmsg[512]; | ||
770 | u_int envsize; | ||
771 | char **child_env; | ||
772 | |||
773 | if (child != NULL) | ||
774 | *child = NULL; | ||
775 | |||
776 | debug3("%s: %s command \"%s\" running as %s (flags 0x%x)", __func__, | ||
777 | tag, command, pw->pw_name, flags); | ||
778 | |||
779 | /* Check consistency */ | ||
780 | if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && | ||
781 | (flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) { | ||
782 | error("%s: inconsistent flags", __func__); | ||
783 | return 0; | ||
784 | } | ||
785 | if (((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) != (child == NULL)) { | ||
786 | error("%s: inconsistent flags/output", __func__); | ||
787 | return 0; | ||
788 | } | ||
789 | |||
790 | /* | ||
791 | * If executing an explicit binary, then verify the it exists | ||
792 | * and appears safe-ish to execute | ||
793 | */ | ||
794 | if (*av[0] != '/') { | ||
795 | error("%s path is not absolute", tag); | ||
796 | return 0; | ||
797 | } | ||
798 | temporarily_use_uid(pw); | ||
799 | if (stat(av[0], &st) < 0) { | ||
800 | error("Could not stat %s \"%s\": %s", tag, | ||
801 | av[0], strerror(errno)); | ||
802 | restore_uid(); | ||
803 | return 0; | ||
804 | } | ||
805 | if (safe_path(av[0], &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) { | ||
806 | error("Unsafe %s \"%s\": %s", tag, av[0], errmsg); | ||
807 | restore_uid(); | ||
808 | return 0; | ||
809 | } | ||
810 | /* Prepare to keep the child's stdout if requested */ | ||
811 | if (pipe(p) != 0) { | ||
812 | error("%s: pipe: %s", tag, strerror(errno)); | ||
813 | restore_uid(); | ||
814 | return 0; | ||
815 | } | ||
816 | restore_uid(); | ||
817 | |||
818 | switch ((pid = fork())) { | ||
819 | case -1: /* error */ | ||
820 | error("%s: fork: %s", tag, strerror(errno)); | ||
821 | close(p[0]); | ||
822 | close(p[1]); | ||
823 | return 0; | ||
824 | case 0: /* child */ | ||
825 | /* Prepare a minimal environment for the child. */ | ||
826 | envsize = 5; | ||
827 | child_env = xcalloc(sizeof(*child_env), envsize); | ||
828 | child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH); | ||
829 | child_set_env(&child_env, &envsize, "USER", pw->pw_name); | ||
830 | child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name); | ||
831 | child_set_env(&child_env, &envsize, "HOME", pw->pw_dir); | ||
832 | if ((cp = getenv("LANG")) != NULL) | ||
833 | child_set_env(&child_env, &envsize, "LANG", cp); | ||
834 | |||
835 | for (i = 0; i < NSIG; i++) | ||
836 | signal(i, SIG_DFL); | ||
837 | |||
838 | if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { | ||
839 | error("%s: open %s: %s", tag, _PATH_DEVNULL, | ||
840 | strerror(errno)); | ||
841 | _exit(1); | ||
842 | } | ||
843 | if (dup2(devnull, STDIN_FILENO) == -1) { | ||
844 | error("%s: dup2: %s", tag, strerror(errno)); | ||
845 | _exit(1); | ||
846 | } | ||
847 | |||
848 | /* Set up stdout as requested; leave stderr in place for now. */ | ||
849 | fd = -1; | ||
850 | if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) | ||
851 | fd = p[1]; | ||
852 | else if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0) | ||
853 | fd = devnull; | ||
854 | if (fd != -1 && dup2(fd, STDOUT_FILENO) == -1) { | ||
855 | error("%s: dup2: %s", tag, strerror(errno)); | ||
856 | _exit(1); | ||
857 | } | ||
858 | closefrom(STDERR_FILENO + 1); | ||
859 | |||
860 | /* Don't use permanently_set_uid() here to avoid fatal() */ | ||
861 | if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) { | ||
862 | error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid, | ||
863 | strerror(errno)); | ||
864 | _exit(1); | ||
865 | } | ||
866 | if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) { | ||
867 | error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid, | ||
868 | strerror(errno)); | ||
869 | _exit(1); | ||
870 | } | ||
871 | /* stdin is pointed to /dev/null at this point */ | ||
872 | if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && | ||
873 | dup2(STDIN_FILENO, STDERR_FILENO) == -1) { | ||
874 | error("%s: dup2: %s", tag, strerror(errno)); | ||
875 | _exit(1); | ||
876 | } | ||
877 | |||
878 | execve(av[0], av, child_env); | ||
879 | error("%s exec \"%s\": %s", tag, command, strerror(errno)); | ||
880 | _exit(127); | ||
881 | default: /* parent */ | ||
882 | break; | ||
883 | } | ||
884 | |||
885 | close(p[1]); | ||
886 | if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) | ||
887 | close(p[0]); | ||
888 | else if ((f = fdopen(p[0], "r")) == NULL) { | ||
889 | error("%s: fdopen: %s", tag, strerror(errno)); | ||
890 | close(p[0]); | ||
891 | /* Don't leave zombie child */ | ||
892 | kill(pid, SIGTERM); | ||
893 | while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) | ||
894 | ; | ||
895 | return 0; | ||
896 | } | ||
897 | /* Success */ | ||
898 | debug3("%s: %s pid %ld", __func__, tag, (long)pid); | ||
899 | if (child != NULL) | ||
900 | *child = f; | ||
901 | return pid; | ||
902 | } | ||
903 | |||
904 | /* These functions link key/cert options to the auth framework */ | ||
905 | |||
906 | /* Log sshauthopt options locally and (optionally) for remote transmission */ | ||
907 | void | ||
908 | auth_log_authopts(const char *loc, const struct sshauthopt *opts, int do_remote) | ||
909 | { | ||
910 | int do_env = options.permit_user_env && opts->nenv > 0; | ||
911 | int do_permitopen = opts->npermitopen > 0 && | ||
912 | (options.allow_tcp_forwarding & FORWARD_LOCAL) != 0; | ||
913 | size_t i; | ||
914 | char msg[1024], buf[64]; | ||
915 | |||
916 | snprintf(buf, sizeof(buf), "%d", opts->force_tun_device); | ||
917 | /* Try to keep this alphabetically sorted */ | ||
918 | snprintf(msg, sizeof(msg), "key options:%s%s%s%s%s%s%s%s%s%s%s%s", | ||
919 | opts->permit_agent_forwarding_flag ? " agent-forwarding" : "", | ||
920 | opts->force_command == NULL ? "" : " command", | ||
921 | do_env ? " environment" : "", | ||
922 | opts->valid_before == 0 ? "" : "expires", | ||
923 | do_permitopen ? " permitopen" : "", | ||
924 | opts->permit_port_forwarding_flag ? " port-forwarding" : "", | ||
925 | opts->cert_principals == NULL ? "" : " principals", | ||
926 | opts->permit_pty_flag ? " pty" : "", | ||
927 | opts->force_tun_device == -1 ? "" : " tun=", | ||
928 | opts->force_tun_device == -1 ? "" : buf, | ||
929 | opts->permit_user_rc ? " user-rc" : "", | ||
930 | opts->permit_x11_forwarding_flag ? " x11-forwarding" : ""); | ||
931 | |||
932 | debug("%s: %s", loc, msg); | ||
933 | if (do_remote) | ||
934 | auth_debug_add("%s: %s", loc, msg); | ||
935 | |||
936 | if (options.permit_user_env) { | ||
937 | for (i = 0; i < opts->nenv; i++) { | ||
938 | debug("%s: environment: %s", loc, opts->env[i]); | ||
939 | if (do_remote) { | ||
940 | auth_debug_add("%s: environment: %s", | ||
941 | loc, opts->env[i]); | ||
942 | } | ||
943 | } | ||
944 | } | ||
945 | |||
946 | /* Go into a little more details for the local logs. */ | ||
947 | if (opts->valid_before != 0) { | ||
948 | format_absolute_time(opts->valid_before, buf, sizeof(buf)); | ||
949 | debug("%s: expires at %s", loc, buf); | ||
950 | } | ||
951 | if (opts->cert_principals != NULL) { | ||
952 | debug("%s: authorized principals: \"%s\"", | ||
953 | loc, opts->cert_principals); | ||
954 | } | ||
955 | if (opts->force_command != NULL) | ||
956 | debug("%s: forced command: \"%s\"", loc, opts->force_command); | ||
957 | if ((options.allow_tcp_forwarding & FORWARD_LOCAL) != 0) { | ||
958 | for (i = 0; i < opts->npermitopen; i++) { | ||
959 | debug("%s: permitted open: %s", | ||
960 | loc, opts->permitopen[i]); | ||
961 | } | ||
962 | } | ||
963 | } | ||
964 | |||
965 | /* Activate a new set of key/cert options; merging with what is there. */ | ||
966 | int | ||
967 | auth_activate_options(struct ssh *ssh, struct sshauthopt *opts) | ||
968 | { | ||
969 | struct sshauthopt *old = auth_opts; | ||
970 | const char *emsg = NULL; | ||
971 | |||
972 | debug("%s: setting new authentication options", __func__); | ||
973 | if ((auth_opts = sshauthopt_merge(old, opts, &emsg)) == NULL) { | ||
974 | error("Inconsistent authentication options: %s", emsg); | ||
975 | return -1; | ||
976 | } | ||
977 | return 0; | ||
978 | } | ||
979 | |||
980 | /* Disable forwarding, etc for the session */ | ||
981 | void | ||
982 | auth_restrict_session(struct ssh *ssh) | ||
983 | { | ||
984 | struct sshauthopt *restricted; | ||
985 | |||
986 | debug("%s: restricting session", __func__); | ||
987 | |||
988 | /* A blank sshauthopt defaults to permitting nothing */ | ||
989 | restricted = sshauthopt_new(); | ||
990 | restricted->restricted = 1; | ||
991 | |||
992 | if (auth_activate_options(ssh, restricted) != 0) | ||
993 | fatal("%s: failed to restrict session", __func__); | ||
994 | sshauthopt_free(restricted); | ||
995 | } | ||
996 | |||
997 | int | ||
998 | auth_authorise_keyopts(struct ssh *ssh, struct passwd *pw, | ||
999 | struct sshauthopt *opts, int allow_cert_authority, const char *loc) | ||
1000 | { | ||
1001 | const char *remote_ip = ssh_remote_ipaddr(ssh); | ||
1002 | const char *remote_host = auth_get_canonical_hostname(ssh, | ||
1003 | options.use_dns); | ||
1004 | time_t now = time(NULL); | ||
1005 | char buf[64]; | ||
1006 | |||
1007 | /* | ||
1008 | * Check keys/principals file expiry time. | ||
1009 | * NB. validity interval in certificate is handled elsewhere. | ||
1010 | */ | ||
1011 | if (opts->valid_before && now > 0 && | ||
1012 | opts->valid_before < (uint64_t)now) { | ||
1013 | format_absolute_time(opts->valid_before, buf, sizeof(buf)); | ||
1014 | debug("%s: entry expired at %s", loc, buf); | ||
1015 | auth_debug_add("%s: entry expired at %s", loc, buf); | ||
1016 | return -1; | ||
1017 | } | ||
1018 | /* Consistency checks */ | ||
1019 | if (opts->cert_principals != NULL && !opts->cert_authority) { | ||
1020 | debug("%s: principals on non-CA key", loc); | ||
1021 | auth_debug_add("%s: principals on non-CA key", loc); | ||
1022 | /* deny access */ | ||
1023 | return -1; | ||
1024 | } | ||
1025 | /* cert-authority flag isn't valid in authorized_principals files */ | ||
1026 | if (!allow_cert_authority && opts->cert_authority) { | ||
1027 | debug("%s: cert-authority flag invalid here", loc); | ||
1028 | auth_debug_add("%s: cert-authority flag invalid here", loc); | ||
1029 | /* deny access */ | ||
1030 | return -1; | ||
1031 | } | ||
1032 | |||
1033 | /* Perform from= checks */ | ||
1034 | if (opts->required_from_host_keys != NULL) { | ||
1035 | switch (match_host_and_ip(remote_host, remote_ip, | ||
1036 | opts->required_from_host_keys )) { | ||
1037 | case 1: | ||
1038 | /* Host name matches. */ | ||
1039 | break; | ||
1040 | case -1: | ||
1041 | default: | ||
1042 | debug("%s: invalid from criteria", loc); | ||
1043 | auth_debug_add("%s: invalid from criteria", loc); | ||
1044 | /* FALLTHROUGH */ | ||
1045 | case 0: | ||
1046 | logit("%s: Authentication tried for %.100s with " | ||
1047 | "correct key but not from a permitted " | ||
1048 | "host (host=%.200s, ip=%.200s, required=%.200s).", | ||
1049 | loc, pw->pw_name, remote_host, remote_ip, | ||
1050 | opts->required_from_host_keys); | ||
1051 | auth_debug_add("%s: Your host '%.200s' is not " | ||
1052 | "permitted to use this key for login.", | ||
1053 | loc, remote_host); | ||
1054 | /* deny access */ | ||
1055 | return -1; | ||
1056 | } | ||
1057 | } | ||
1058 | /* Check source-address restriction from certificate */ | ||
1059 | if (opts->required_from_host_cert != NULL) { | ||
1060 | switch (addr_match_cidr_list(remote_ip, | ||
1061 | opts->required_from_host_cert)) { | ||
1062 | case 1: | ||
1063 | /* accepted */ | ||
1064 | break; | ||
1065 | case -1: | ||
1066 | default: | ||
1067 | /* invalid */ | ||
1068 | error("%s: Certificate source-address invalid", | ||
1069 | loc); | ||
1070 | /* FALLTHROUGH */ | ||
1071 | case 0: | ||
1072 | logit("%s: Authentication tried for %.100s with valid " | ||
1073 | "certificate but not from a permitted source " | ||
1074 | "address (%.200s).", loc, pw->pw_name, remote_ip); | ||
1075 | auth_debug_add("%s: Your address '%.200s' is not " | ||
1076 | "permitted to use this certificate for login.", | ||
1077 | loc, remote_ip); | ||
1078 | return -1; | ||
1079 | } | ||
1080 | } | ||
1081 | /* | ||
1082 | * | ||
1083 | * XXX this is spammy. We should report remotely only for keys | ||
1084 | * that are successful in actual auth attempts, and not PK_OK | ||
1085 | * tests. | ||
1086 | */ | ||
1087 | auth_log_authopts(loc, opts, 1); | ||
1088 | |||
1089 | return 0; | ||
1090 | } | ||