Index: Makefile =================================================================== RCS file: /cvs/src/usr.bin/ssh/Makefile,v retrieving revision 1.9 diff -u -u -r1.9 Makefile --- Makefile 28 Jun 2001 21:55:27 -0000 1.9 +++ Makefile 28 Nov 2001 03:56:35 -0000 @@ -10,5 +10,7 @@ ${DESTDIR}/etc/ssh_config install -C -o root -g wheel -m 0644 ${.CURDIR}/sshd_config \ ${DESTDIR}/etc/sshd_config + install -C -o root -g wheel -m 0600 ${.CURDIR}/sshd_policy \ + ${DESTDIR}/etc/sshd_policy .include Index: README.keynote =================================================================== RCS file: README.keynote diff -N README.keynote --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ README.keynote 28 Nov 2001 03:56:35 -0000 @@ -0,0 +1,99 @@ +Keynote in OpenSSH +------------------ + +System policies go in /etc/sshd_policy and are always read. User policies +are conditional on server config directive "LoadUserPolicies" and live in +~/.ssh/policy. Both policies are loaded with ASSERT_FLAG_LOCAL set. + +Later we can do signed policies sent over the wire. + +Global action variables + + app_domain = "SSH" + user + user_id + group_id + shell + remote_ip + remote_port + time_of_day (encoded as HHMMSS) + day_of_week (Sunday = 0) + date (encoded as YYYYMMDD) + protocol ("1" or "2") + client_version + + Protocol 2 specific: + kex_type (key exchange type) + cipher2_ctos (cipher used client->server for ssh2) + mac2_ctos + comp2_ctos + cipher2_stoc + mac2_stoc + comp2_stoc + + Protocol 1 specific: + cipher1 + +XXX Perhaps: + + dhgex_bits + system_load + no_login (set if /etc/nologin exists) + +Specific access control checks and local action variables: + +Authentication - tested in each AuthMethod + + action = "auth" + auth_type = "none" / "password" / "pubkey" / "krb4" / "krb5" / "rhosts" / + "hostbased" / "challenge-response" + XXX: pubkey auth should add each successful pubkey as a keynote authoriser. + XXX: how to handle kbd-int subtypes? + + return values: allow, reject + XXX: allow-chroot [assume homedir as target] + XXX: allow-quiet [allow, but use quieter loglevel for auth messages] + +Remote '-R' port forwarding setup - tested when port forwarding was requested + + action = "rport-forward" + listen_port + +Port forwarding connect - check in channel_post_port_listener after accept() + + action = "lport-forward" + target_host + target_port + +Command execution + + action = "exec" + command (may have been modified by forced commands in public key files) + original_command (original command specified by user) + +Shell execution + + action = "shell" + command (will only be present if set by forced commands) + +Subsystem execution + + action = "subsys" + subsys + +Agent forwarding + + action = "agent-forward" + +PTY request + + action = "pty-request" + +SSH1 compression request + + action = "request-compression1" + compression_level = (1 -> 9) + +X11 forwarding request + action = "x11-forward" + Index: auth1.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/auth1.c,v retrieving revision 1.25 diff -u -u -r1.25 auth1.c --- auth1.c 26 Jun 2001 16:15:23 -0000 1.25 +++ auth1.c 28 Nov 2001 03:56:35 -0000 @@ -26,6 +26,10 @@ #include "misc.h" #include "uidswap.h" +#ifdef KEYNOTE +#include "policy.h" +#endif + /* import */ extern ServerOptions options; @@ -84,6 +88,9 @@ #if defined(KRB4) || defined(KRB5) (!options.kerberos_authentication || options.kerberos_or_local_passwd) && #endif +#ifdef KEYNOTE + policy_auth_check("none") && +#endif auth_password(authctxt, "")) { auth_log(authctxt, 1, "without authentication", ""); return; @@ -118,32 +125,43 @@ if (kdata[0] == 4) { /* KRB_PROT_VERSION */ #ifdef KRB4 KTEXT_ST tkt; - + tkt.length = dlen; if (tkt.length < MAX_KTXT_LEN) - memcpy(tkt.dat, kdata, tkt.length); - - if (auth_krb4(authctxt, &tkt, &client_user)) { + memcpy(tkt.dat, kdata, + tkt.length); + + if (auth_krb4(authctxt, &tkt, + &client_user)) { authenticated = 1; snprintf(info, sizeof(info), " tktuser %.100s", client_user); xfree(client_user); } +#ifdef KEYNOTE + authenticated = policy_auth_check("krb4") ? + authenticated : 0; +#endif #endif /* KRB4 */ } else { #ifdef KRB5 krb5_data tkt; tkt.length = dlen; tkt.data = kdata; - - if (auth_krb5(authctxt, &tkt, &client_user)) { + + if (auth_krb5(authctxt, &tkt, + &client_user)) { authenticated = 1; snprintf(info, sizeof(info), " tktuser %.100s", client_user); xfree(client_user); } +#ifdef KEYNOTE + authenticated = policy_auth_check("krb5") ? + authenticated : 0; +#endif #endif /* KRB5 */ } xfree(kdata); @@ -168,6 +186,10 @@ verbose("Rhosts authentication disabled."); break; } +#ifdef KEYNOTE + if (!policy_auth_check("rhosts")) + break; +#endif /* * Get client user name. Note that we just have to * trust the client; this is one reason why rhosts @@ -189,6 +211,10 @@ verbose("Rhosts with RSA authentication disabled."); break; } +#ifdef KEYNOTE + if (!policy_auth_check("hostbased")) + break; +#endif /* * Get client user name. Note that we just have to * trust the client; root on the client machine can @@ -225,6 +251,10 @@ verbose("RSA authentication disabled."); break; } +#ifdef KEYNOTE + if (!policy_auth_check("pubkey")) + break; +#endif /* RSA authentication requested. */ n = BN_new(); packet_get_bignum(n, &nlen); @@ -238,6 +268,10 @@ verbose("Password authentication disabled."); break; } +#ifdef KEYNOTE + if (!policy_auth_check("password")) + break; +#endif /* * Read user password. It is in plain text, but was * transmitted over the encrypted channel so it is @@ -255,7 +289,12 @@ case SSH_CMSG_AUTH_TIS: debug("rcvd SSH_CMSG_AUTH_TIS"); +#ifdef KEYNOTE + if (options.challenge_response_authentication == 1 && + policy_auth_check("challenge-response")) { +#else if (options.challenge_response_authentication == 1) { +#endif char *challenge = get_challenge(authctxt); if (challenge != NULL) { debug("sending challenge '%s'", challenge); @@ -270,7 +309,12 @@ break; case SSH_CMSG_AUTH_TIS_RESPONSE: debug("rcvd SSH_CMSG_AUTH_TIS_RESPONSE"); +#ifdef KEYNOTE + if (options.challenge_response_authentication == 1 && + policy_auth_check("challenge-response")) { +#else if (options.challenge_response_authentication == 1) { +#endif char *response = packet_get_string(&dlen); debug("got response '%s'", response); packet_integrity_check(plen, 4 + dlen, type); @@ -354,6 +398,9 @@ if (pw && allowed_user(pw)) { authctxt->valid = 1; pw = pwcopy(pw); +#ifdef KEYNOTE + policy_setup_user(pw, options.load_user_policies); +#endif } else { debug("do_authentication: illegal user %s", user); pw = NULL; Index: auth2.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/auth2.c,v retrieving revision 1.73 diff -u -u -r1.73 auth2.c --- auth2.c 17 Nov 2001 19:14:34 -0000 1.73 +++ auth2.c 28 Nov 2001 03:56:45 -0000 @@ -52,6 +52,10 @@ #include "canohost.h" #include "match.h" +#ifdef KEYNOTE +#include "policy.h" +#endif + /* import */ extern ServerOptions options; extern u_char *session_id2; @@ -198,6 +202,9 @@ authctxt->pw = pwcopy(pw); authctxt->valid = 1; debug2("input_userauth_request: setting up authctxt for %s", user); +#ifdef KEYNOTE + policy_setup_user(pw, options.load_user_policies); +#endif } else { log("input_userauth_request: illegal user %s", user); } @@ -316,7 +323,13 @@ m->enabled = NULL; packet_done(); userauth_banner(); - return authctxt->valid ? auth_password(authctxt, "") : 0; + if (!authctxt->valid) + return(0); +#ifdef KEYNOTE + return auth_password(authctxt, "") && policy_auth_check("none"); +#else + return auth_password(authctxt, ""); +#endif } static int @@ -336,6 +349,9 @@ authenticated = 1; memset(password, 0, len); xfree(password); +#ifdef KEYNOTE + authenticated = policy_auth_check("password") ? authenticated : 0; +#endif return authenticated; } @@ -356,6 +372,10 @@ xfree(devs); xfree(lang); +#ifdef KEYNOTE + authenticated = policy_auth_check("challenge-response") ? + authenticated : 0; +#endif return authenticated; } @@ -442,7 +462,12 @@ * if a user is not allowed to login. is this an * issue? -markus */ +#ifdef KEYNOTE + if (user_key_allowed(authctxt->pw, key) && + policy_auth_check("pubkey")) { +#else if (user_key_allowed(authctxt->pw, key)) { +#endif packet_start(SSH2_MSG_USERAUTH_PK_OK); packet_put_string(pkalg, alen); packet_put_string(pkblob, blen); @@ -458,6 +483,9 @@ debug2("userauth_pubkey: authenticated %d pkalg %s", authenticated, pkalg); xfree(pkalg); xfree(pkblob); +#ifdef KEYNOTE + authenticated = policy_auth_check("pubkey") ? authenticated : 0; +#endif return authenticated; } @@ -533,6 +561,9 @@ xfree(cuser); xfree(chost); xfree(sig); +#ifdef KEYNOTE + authenticated = policy_auth_check("hostbased") ? authenticated : 0; +#endif return authenticated; } Index: channels.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.c,v retrieving revision 1.140 diff -u -u -r1.140 channels.c --- channels.c 10 Oct 2001 22:18:47 -0000 1.140 +++ channels.c 28 Nov 2001 03:57:06 -0000 @@ -2006,23 +2006,12 @@ } void -channel_input_port_open(int type, int plen, void *ctxt) +channel_process_input_port_open(int remote_id, char *host, + u_short host_port, char *originator_string) { Channel *c = NULL; - u_short host_port; - char *host, *originator_string; - int remote_id, sock = -1; + int sock = -1; - remote_id = packet_get_int(); - host = packet_get_string(NULL); - host_port = packet_get_int(); - - if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) { - originator_string = packet_get_string(NULL); - } else { - originator_string = xstrdup("unknown (remote did not supply name)"); - } - packet_done(); sock = channel_connect_to(host, host_port); if (sock != -1) { c = channel_new("connected socket", @@ -2040,9 +2029,31 @@ packet_put_int(remote_id); packet_send(); } - xfree(host); } +void +channel_input_port_open(int type, int plen, void *ctxt) +{ + u_short host_port; + char *host, *originator_string; + int remote_id; + + remote_id = packet_get_int(); + host = packet_get_string(NULL); + host_port = packet_get_int(); + + if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) { + originator_string = packet_get_string(NULL); + } else { + originator_string = xstrdup("unknown (remote did not supply name)"); + } + packet_done(); + + channel_process_input_port_open(remote_id, host, host_port, + originator_string); + + xfree(host); +} /* -- tcp forwarding */ @@ -2235,7 +2246,8 @@ */ void -channel_input_port_forward_request(int is_root, int gateway_ports) +channel_input_port_forward_request(int is_root, int gateway_ports, + int (*policy_callback)(u_short)) { u_short port, host_port; char *hostname; @@ -2245,13 +2257,18 @@ hostname = packet_get_string(NULL); host_port = packet_get_int(); + if (policy_callback != NULL && !policy_callback(port)) + packet_disconnect("Requested for forwarding of port " + "%d denied.", port); + /* * Check that an unprivileged user is not trying to forward a * privileged port. */ if (port < IPPORT_RESERVED && !is_root) - packet_disconnect("Requested forwarding of port %d but user is not root.", - port); + packet_disconnect("Requested forwarding of port %d but " + "user is not root.", port); + /* Initiate forwarding */ channel_request_local_forwarding(port, hostname, host_port, gateway_ports); Index: channels.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/channels.h,v retrieving revision 1.51 diff -u -u -r1.51 channels.h --- channels.h 7 Nov 2001 22:53:21 -0000 1.51 +++ channels.h 28 Nov 2001 03:57:11 -0000 @@ -167,6 +167,8 @@ void channel_input_open_failure(int, int, void *); void channel_input_port_open(int, int, void *); void channel_input_window_adjust(int, int, void *); +void channel_process_input_port_open(int remote_id, char *host, + u_short host_port, char *originator_string); /* file descriptor handling (read/write) */ @@ -185,7 +187,8 @@ void channel_permit_all_opens(void); void channel_add_permitted_opens(char *, int); void channel_clear_permitted_opens(void); -void channel_input_port_forward_request(int, int); +void channel_input_port_forward_request(int is_root, int gateway_ports, + int (*policy_callback)(u_short)); int channel_connect_to(const char *, u_short); int channel_connect_by_listen_address(u_short); void channel_request_remote_forwarding(u_short, const char *, u_short); Index: kex.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/kex.c,v retrieving revision 1.36 diff -u -u -r1.36 kex.c --- kex.c 25 Jun 2001 08:25:37 -0000 1.36 +++ kex.c 28 Nov 2001 03:57:17 -0000 @@ -430,11 +430,7 @@ Newkeys * kex_get_newkeys(int mode) { - Newkeys *ret; - - ret = current_keys[mode]; - current_keys[mode] = NULL; - return ret; + return current_keys[mode]; } #if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) Index: pathnames.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/pathnames.h,v retrieving revision 1.9 diff -u -u -r1.9 pathnames.h --- pathnames.h 23 Jun 2001 02:34:30 -0000 1.9 +++ pathnames.h 28 Nov 2001 03:57:17 -0000 @@ -35,6 +35,7 @@ #define _PATH_DH_MODULI ETCDIR "/moduli" /* Backwards compatibility */ #define _PATH_DH_PRIMES ETCDIR "/primes" +#define _PATH_SERVER_POLICY_FILE ETCDIR "/sshd_policy" #define _PATH_SSH_PROGRAM "/usr/bin/ssh" @@ -45,7 +46,7 @@ #define _PATH_SSH_DAEMON_PID_FILE _PATH_SSH_PIDDIR "/sshd.pid" /* - * The directory in user\'s home directory in which the files reside. The + * The directory in user's home directory in which the files reside. The * directory should be world-readable (though not all files are). */ #define _PATH_SSH_USER_DIR ".ssh" @@ -68,17 +69,23 @@ #define _PATH_SSH_CLIENT_ID_RSA ".ssh/id_rsa" /* - * Configuration file in user\'s home directory. This file need not be + * Configuration file in user's home directory. This file need not be * readable by anyone but the user him/herself, but does not contain anything - * particularly secret. If the user\'s home directory resides on an NFS + * particularly secret. If the user's home directory resides on an NFS * volume where root is mapped to nobody, this may need to be world-readable. */ #define _PATH_SSH_USER_CONFFILE ".ssh/config" /* + * Keynote policy file in user's home directory. This file should not be + * readable by anyone other than the user (i.e mode 0600). + */ +#define _PATH_SSH_USER_POLICY_FILE ".ssh/policy" + +/* * File containing a list of those rsa keys that permit logging in as this * user. This file need not be readable by anyone but the user him/herself, - * but does not contain anything particularly secret. If the user\'s home + * but does not contain anything particularly secret. If the user's home * directory resides on an NFS volume where root is mapped to nobody, this * may need to be world-readable. (This file is read by the daemon which is * running as root.) Index: policy.c =================================================================== RCS file: policy.c diff -N policy.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ policy.c 28 Nov 2001 03:57:17 -0000 @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2001 Damien Miller. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "includes.h" + +#ifdef KEYNOTE + +#include "ssh.h" +#include "log.h" +#include "atomicio.h" +#include "pathnames.h" +#include "xmalloc.h" +#include "policy.h" + +#include +#include + +RCSID("$Id$"); + +#define MAX_FILE_LENGTH (1024 * 256) /* 256kb should be heaps */ + +int keynote_id = -1; + +/* + * Possible results from policy query + */ +#define NUM_POLICY_RESULTS 2 +char *policy_results_str[] = { "reject", "allow" }; +enum policy_result { + POLICY_REJECT = 0, + POLICY_ALLOW = 1 +}; + +static int +add_assertions(int kid, char *s, size_t len, int trusted) +{ + char **a; + int num_assertions; + int i; + + a = kn_read_asserts(s, len, &num_assertions); + if (a == NULL) { + error("Couldn't split assertions: %d", keynote_errno); + return -1; + } + + debug2("policy - Found %d assertions", num_assertions); + for (i = 0; i < num_assertions; i++) { + if (kn_add_assertion(kid, a[i], strlen(a[i]), + trusted?ASSERT_FLAG_LOCAL:0) == -1) { + error("Couldn't add assertion %d: %d", i, + keynote_errno); + } + free(a[i]); + } + free(a); + + return 0; +} + +static int +add_assertions_from_file(int kid, const char *path, int trusted, + uid_t expected_owner) +{ + int fd; + struct stat st; + char *buf; + + debug3("policy - Reading keynote policies from %.200s", path); + + if ((fd = open(path, O_RDONLY)) == -1) { + debug("policy - Couldn't open \"%s\": %s", path, + strerror(errno)); + return -1; + } + + if (fstat(fd, &st) == -1) + fatal("Couldn't stat \"%s\": %s", path, strerror(errno)); + + if (!S_ISREG(st.st_mode)) + fatal("\"%s\" is not a regular file", path); + + if (st.st_size >= MAX_FILE_LENGTH) + fatal("File \"%s\" is longer than maximum permitted " + "length %u", path, MAX_FILE_LENGTH); + + if (st.st_uid != expected_owner) + fatal("\"%s\" has incorrect ownership", path); + + if ((st.st_mode & 0777) != 0600) + fatal("\"%s\" has incorrect permissions", path); + + buf = xmalloc(st.st_size + 1); + + if (atomicio(read, fd, buf, st.st_size) != st.st_size) { + free(buf); + fatal("Couldn't read \"%s\": %s", path, strerror(errno)); + } + + close(fd); + + buf[st.st_size] = '\0'; + + if (add_assertions(kid, buf, st.st_size, trusted) == -1) + return -1; + + free(buf); + + return 0; +} + +void +policy_del_action(const char *name) +{ + char *n; + + /* Duplicate so we can have const args */ + n = xstrdup(name); + + if (kn_remove_action(keynote_id, n) == -1 && + keynote_errno != ERROR_NOTFOUND) + fatal("kn_remove_action failed, error %d", keynote_errno); + + xfree(n); +} + +void +policy_add_action(const char *name, const char *value) +{ + char *n, *v; + + /* Duplicate so we can have const args */ + n = xstrdup(name); + v = xstrdup(value); + + debug2("policy - Adding variable %s = \"%s\"", n, v); + if (kn_add_action(keynote_id, n, v, 0) == -1) + fatal("kn_add_action failed, error %d", keynote_errno); + + xfree(n); + xfree(v); +} + +void +policy_add_action_int(const char *name, int value) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "%d", value); + + policy_add_action(name, buf); +} + +void +policy_init(void) +{ + keynote_id = kn_init(); + if (keynote_id == -1) + fatal("kn_init failed, error %d", keynote_errno); + + if (add_assertions_from_file(keynote_id, _PATH_SERVER_POLICY_FILE, + 1, 0) == -1) + fatal("Couldn't read system policies"); + + policy_add_action("app_domain", "SSH"); + + if (kn_add_authorizer(keynote_id, "SSHD") == -1) + fatal("kn_add_authorizer failed, error %d", keynote_errno); +} + +void +policy_setup_user(struct passwd *pw, int add_user_asserts) +{ + char buf[1024]; + struct group *gp; + time_t t; + struct tm *tm; + + if (add_user_asserts) { + snprintf(buf, sizeof(buf), "%.100s/%.100s/%.100s", pw->pw_dir, + _PATH_SSH_USER_DIR, _PATH_SSH_USER_POLICY_FILE); + add_assertions_from_file(keynote_id, buf, 1, pw->pw_uid); + } + + /* Add user-specific and generic action variables */ + policy_add_action("user", pw->pw_name); + + snprintf(buf, sizeof(buf), "%d", pw->pw_uid); + policy_add_action("uid", buf); + + gp = getgrgid(pw->pw_gid); + if (gp) + policy_add_action("group", gp->gr_name); + + snprintf(buf, sizeof(buf), "%d", pw->pw_gid); + policy_add_action("gid", buf); + + policy_add_action("shell", pw->pw_shell); + + t = time(NULL); + tm = localtime(&t); + + snprintf(buf, sizeof(buf), "%02d%02d%02d", tm->tm_hour, + tm->tm_min, tm->tm_sec); + policy_add_action("time_of_day", buf); + + snprintf(buf, sizeof(buf), "%d", tm->tm_wday); + policy_add_action("day_of_week", buf); + + snprintf(buf, sizeof(buf), "%04d%02d%02d", tm->tm_year + 1900, + tm->tm_mon + 1, tm->tm_mday); + policy_add_action("date", buf); +} + +int +policy_check(const char *action_name) +{ + int ret; + + policy_add_action("action", action_name); + + ret = kn_do_query(keynote_id, policy_results_str, NUM_POLICY_RESULTS); + debug3("policy - Test ret=%d", ret); + + policy_del_action("action"); + + if (ret == -1) + fatal("KeyNote policy test failed, error %d", keynote_errno); + + if (ret != POLICY_ALLOW) { + log("Action \"%s\" denied by policy", action_name); + return 0; + } + + return 1; +} + +int +policy_auth_check(const char *authtype) +{ + int ret; + + policy_add_action("auth_type", authtype); + + ret = policy_check("auth"); + + policy_del_action("auth_type"); + + if (ret != POLICY_ALLOW) { + log("Authentication \"%s\" denied by policy", authtype); + return 0; + } + + return 1; +} + +int +policy_exec_check(const char *command, const char *original_command) +{ + int ret; + + policy_add_action("command", command != NULL ? command : ""); + policy_add_action("original_command", + original_command != NULL ? original_command : ""); + + if (original_command == NULL) + ret = policy_check("shell"); + else + ret = policy_check("exec"); + + policy_del_action("command"); + policy_del_action("original_command"); + + return(ret); +} + +int +policy_rportfwd_check(u_short port) +{ + int ret; + + policy_add_action_int("listen_port", port); + ret = policy_check("rport-forward"); + policy_del_action("listen_port"); + + return ret; +} + +int +policy_lportfwd_check(char *target_host, u_short target_port) +{ + int ret; + + policy_add_action("target_host", target_host); + policy_add_action_int("target_port", target_port); + ret = policy_check("lport-forward"); + policy_del_action("target_host"); + policy_del_action("target_port"); + + return ret; +} + +#endif /* KEYNOTE */ Index: policy.h =================================================================== RCS file: policy.h diff -N policy.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ policy.h 28 Nov 2001 03:57:22 -0000 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2001 Damien Miller. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +void +policy_del_action(const char *name); + +void +policy_add_action(const char *name, const char *value); + +void +policy_add_action_int(const char *name, int value); + +void +policy_init(void); + +void +policy_setup_user(struct passwd *pw, int add_user_asserts); + +int +policy_check(const char *action_name); + +int +policy_auth_check(const char *authtype); + +int +policy_exec_check(const char *command, const char *original_command); + +int +policy_rportfwd_check(u_short port); + +int +policy_lportfwd_check(char *target_host, u_short target_port); + Index: servconf.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/servconf.c,v retrieving revision 1.92 diff -u -u -r1.92 servconf.c --- servconf.c 17 Nov 2001 19:14:34 -0000 1.92 +++ servconf.c 28 Nov 2001 03:57:27 -0000 @@ -104,6 +104,9 @@ options->client_alive_count_max = -1; options->authorized_keys_file = NULL; options->authorized_keys_file2 = NULL; +#ifdef KEYNOTE + options->load_user_policies = -1; +#endif } void @@ -219,6 +222,10 @@ } if (options->authorized_keys_file == NULL) options->authorized_keys_file = _PATH_SSH_USER_PERMITTED_KEYS; +#ifdef KEYNOTE + if (options->load_user_policies == -1) + options->load_user_policies = 1; +#endif } /* Keyword tokens. */ @@ -248,6 +255,9 @@ sBanner, sReverseMappingCheck, sHostbasedAuthentication, sHostbasedUsesNameFromPacketOnly, sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2, +#ifdef KEYNOTE + sLoadUserPolicies, +#endif sDeprecated } ServerOpCodes; @@ -318,6 +328,9 @@ { "clientalivecountmax", sClientAliveCountMax }, { "authorizedkeysfile", sAuthorizedKeysFile }, { "authorizedkeysfile2", sAuthorizedKeysFile2 }, +#ifdef KEYNOTE + { "loaduserpolicies", sLoadUserPolicies }, +#endif { NULL, sBadOption } }; @@ -835,7 +848,11 @@ case sClientAliveCountMax: intptr = &options->client_alive_count_max; goto parse_int; - +#ifdef KEYNOTE + case sLoadUserPolicies: + intptr = &options->load_user_policies; + goto parse_flag; +#endif case sDeprecated: log("%s line %d: Deprecated option %s", filename, linenum, arg); Index: servconf.h =================================================================== RCS file: /cvs/src/usr.bin/ssh/servconf.h,v retrieving revision 1.49 diff -u -u -r1.49 servconf.h --- servconf.h 17 Aug 2001 18:59:47 -0000 1.49 +++ servconf.h 28 Nov 2001 03:57:33 -0000 @@ -128,6 +128,9 @@ char *authorized_keys_file; /* File containing public keys */ char *authorized_keys_file2; +#ifdef KEYNOTE + int load_user_policies; +#endif } ServerOptions; Index: serverloop.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/serverloop.c,v retrieving revision 1.84 diff -u -u -r1.84 serverloop.c --- serverloop.c 22 Nov 2001 12:34:22 -0000 1.84 +++ serverloop.c 28 Nov 2001 03:57:40 -0000 @@ -55,6 +55,10 @@ #include "misc.h" #include "kex.h" +#ifdef KEYNOTE +#include "policy.h" +#endif + extern ServerOptions options; /* XXX */ @@ -791,7 +795,7 @@ } static Channel * -server_request_direct_tcpip(char *ctype) +server_request_direct_tcpip(char *ctype, const char **reason) { Channel *c; int sock; @@ -807,12 +811,23 @@ debug("server_request_direct_tcpip: originator %s port %d, target %s port %d", originator, originator_port, target, target_port); +#ifdef KEYNOTE + if (!policy_lportfwd_check(target, target_port)) { + xfree(target); + xfree(originator); + *reason = "permission denied"; + return(NULL); + } +#endif + /* XXX check permission */ sock = channel_connect_to(target, target_port); xfree(target); xfree(originator); - if (sock < 0) + if (sock < 0) { + *reason = "Connection failed"; return NULL; + } c = channel_new(ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, xstrdup("direct-tcpip"), 1); @@ -824,7 +839,7 @@ } static Channel * -server_request_session(char *ctype) +server_request_session(char *ctype, const char **reason) { Channel *c; @@ -844,6 +859,7 @@ return NULL; } if (session_open(xxx_authctxt, c->self) != 1) { + *reason = "session open failed"; debug("session open failed, free channel %d", c->self); channel_free(c); return NULL; @@ -863,6 +879,7 @@ int rchan; int rmaxpack; int rwindow; + const char *reason; ctype = packet_get_string(&len); rchan = packet_get_int(); @@ -872,10 +889,11 @@ debug("server_input_channel_open: ctype %s rchan %d win %d max %d", ctype, rchan, rwindow, rmaxpack); + reason = NULL; if (strcmp(ctype, "session") == 0) { - c = server_request_session(ctype); + c = server_request_session(ctype, &reason); } else if (strcmp(ctype, "direct-tcpip") == 0) { - c = server_request_direct_tcpip(ctype); + c = server_request_direct_tcpip(ctype, &reason); } if (c != NULL) { debug("server_input_channel_open: confirm %s", ctype); @@ -896,7 +914,7 @@ packet_put_int(rchan); packet_put_int(SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED); if (!(datafellows & SSH_BUG_OPENFAILURE)) { - packet_put_cstring("open failed"); + packet_put_cstring(reason ? reason : "open failed"); packet_put_cstring(""); } packet_send(); @@ -931,7 +949,10 @@ /* check permissions */ if (!options.allow_tcp_forwarding || - no_port_forwarding_flag || + no_port_forwarding_flag || +#ifdef KEYNOTE + !policy_rportfwd_check(listen_port) || +#endif (listen_port < IPPORT_RESERVED && pw->pw_uid != 0)) { success = 0; packet_send_debug("Server has disabled port forwarding."); @@ -955,6 +976,42 @@ } static void +server_input_port_open(int type, int plen, void *ctxt) +{ + u_short host_port; + char *host, *originator_string; + int remote_id; + + remote_id = packet_get_int(); + host = packet_get_string(NULL); + host_port = packet_get_int(); + + if (packet_get_protocol_flags() & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) { + originator_string = packet_get_string(NULL); + } else { + originator_string = xstrdup("unknown (remote did not supply name)"); + } + packet_done(); + +#ifdef KEYNOTE + if (policy_lportfwd_check(host, host_port)) + channel_process_input_port_open(remote_id, host, host_port, + originator_string); + else { + xfree(originator_string); + packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE); + packet_put_int(remote_id); + packet_send(); + } +#else + channel_process_input_port_open(remote_id, host, host_port, + originator_string); +#endif + + xfree(host); +} + +static void server_init_dispatch_20(void) { debug("server_init_dispatch_20"); @@ -987,7 +1044,7 @@ dispatch_set(SSH_MSG_CHANNEL_DATA, &channel_input_data); dispatch_set(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); dispatch_set(SSH_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); - dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open); + dispatch_set(SSH_MSG_PORT_OPEN, &server_input_port_open); } static void server_init_dispatch_15(void) Index: session.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/session.c,v retrieving revision 1.108 diff -u -u -r1.108 session.c --- session.c 11 Oct 2001 13:45:21 -0000 1.108 +++ session.c 28 Nov 2001 03:57:57 -0000 @@ -57,6 +57,10 @@ #include "canohost.h" #include "session.h" +#ifdef KEYNOTE +#include "policy.h" +#endif + /* types */ #define TTYSZ 64 @@ -204,11 +208,23 @@ case SSH_CMSG_REQUEST_COMPRESSION: packet_integrity_check(plen, 4, type); compression_level = packet_get_int(); +#ifdef KEYNOTE + policy_add_action_int("compression_level", + compression_level); + if (compression_level < 1 || compression_level > 9 || + !policy_check("request-compression1")) { + policy_del_action("compression_level"); + packet_send_debug("Received illegal compression level %d.", + compression_level); + break; + } +#else if (compression_level < 1 || compression_level > 9) { packet_send_debug("Received illegal compression level %d.", compression_level); break; } +#endif /* KEYNOTE */ /* Enable compression after we have responded with SUCCESS. */ enable_compression_after_reply = 1; success = 1; @@ -250,6 +266,10 @@ break; } debug("Received authentication agent forwarding request."); +#ifdef KEYNOTE + if (!policy_check("agent-forward")) + break; +#endif success = auth_input_request_forwarding(s->pw); break; @@ -263,7 +283,13 @@ break; } debug("Received TCP/IP port forwarding request."); - channel_input_port_forward_request(s->pw->pw_uid == 0, options.gateway_ports); +#ifdef KEYNOTE + channel_input_port_forward_request(s->pw->pw_uid == 0, + options.gateway_ports, policy_rportfwd_check); +#else + channel_input_port_forward_request(s->pw->pw_uid == 0, + options.gateway_ports, NULL); +#endif success = 1; break; @@ -567,6 +593,11 @@ debug("Forced command '%.900s'", command); } +#ifdef KEYNOTE + if (!policy_exec_check(command, original_command)) + packet_disconnect("Execution not permitted"); +#endif + if (s->ttyfd != -1) do_exec_pty(s, command); else @@ -1241,6 +1272,11 @@ return 0; } +#ifdef KEYNOTE + if (!policy_check("pty-request")) + return 0; +#endif + s->term = packet_get_string(&len); if (compat20) { @@ -1375,6 +1411,12 @@ debug("session_auth_agent_req: no_agent_forwarding_flag"); return 0; } + +#ifdef KEYNOTE + if (!policy_check("agent-forward")) + return 0; +#endif + if (called) { return 0; } else { @@ -1667,6 +1709,12 @@ debug("X11 display already set."); return 0; } + +#ifdef KEYNOTE + if (!policy_check("x11-forward")) + return 0; +#endif + s->display = x11_create_display_inet(s->screen, options.x11_display_offset); if (s->display == NULL) { debug("x11_create_display_inet failed."); Index: sshd.c =================================================================== RCS file: /cvs/src/usr.bin/ssh/sshd.c,v retrieving revision 1.212 diff -u -u -r1.212 sshd.c --- sshd.c 22 Nov 2001 12:34:22 -0000 1.212 +++ sshd.c 28 Nov 2001 03:58:12 -0000 @@ -73,6 +73,10 @@ #include "dispatch.h" #include "channels.h" +#ifdef KEYNOTE +#include "policy.h" +#endif + #ifdef LIBWRAP #include #include @@ -445,6 +449,10 @@ server_version_string, client_version_string); fatal_cleanup(); } +#ifdef KEYNOTE + policy_add_action_int("protocol", compat20 ? 2 : 1); + policy_add_action("client_version", remote_version); +#endif } @@ -689,6 +697,11 @@ /* Fill in default values for those options not explicitly set. */ fill_default_server_options(&options); +#ifdef KEYNOTE + /* Init Keynote policies */ + policy_init(); +#endif + /* Check that there are no remaining arguments. */ if (optind < ac) { fprintf(stderr, "Extra argument %s.\n", av[optind]); @@ -817,7 +830,7 @@ startup_pipe = -1; /* * We intentionally do not close the descriptors 0, 1, and 2 - * as our code for setting the descriptors won\'t work if + * as our code for setting the descriptors won't work if * ttyfd happens to be one of those. */ debug("inetd sockets after dupping: %d, %d", sock_in, sock_out); @@ -1139,11 +1152,17 @@ /* Log the connection. */ verbose("Connection from %.500s port %d", remote_ip, remote_port); +#ifdef KEYNOTE + policy_add_action("remote_ip", remote_ip); + snprintf(strport, sizeof(strport), "%d", remote_port); + policy_add_action("remote_port", strport); +#endif + /* - * We don\'t want to listen forever unless the other side + * We don't want to listen forever unless the other side * successfully authenticates itself. So we set up an alarm which is * cleared after successful authentication. A limit of zero - * indicates no limit. Note that we don\'t set the alarm in debugging + * indicates no limit. Note that we don't set the alarm in debugging * mode; it is just annoying to have the server exit just when you * are about to discover the bug. */ @@ -1418,6 +1437,10 @@ packet_start(SSH_SMSG_SUCCESS); packet_send(); packet_write_wait(); + +#ifdef KEYNOTE + policy_add_action("cipher1", cipher_name(cipher_type)); +#endif } /* @@ -1465,4 +1488,26 @@ packet_write_wait(); #endif debug("KEX done"); + +#ifdef KEYNOTE + policy_add_action("cipher2_ctos", kex_get_newkeys(MODE_IN)->enc.name); + policy_add_action("mac2_ctos", kex_get_newkeys(MODE_IN)->mac.name); + policy_add_action("comp_alg2_ctos", + kex_get_newkeys(MODE_IN)->comp.name); + policy_add_action("cipher2_stoc", kex_get_newkeys(MODE_OUT)->enc.name); + policy_add_action("mac2_stoc", kex_get_newkeys(MODE_OUT)->mac.name); + policy_add_action("comp_alg2_stoc", + kex_get_newkeys(MODE_OUT)->comp.name); + + switch(kex->kex_type) { + case DH_GRP1_SHA1: + policy_add_action("kex_type", KEX_DH1); + break; + case DH_GEX_SHA1: + policy_add_action("kex_type", KEX_DHGEX); + break; + default: + debug("XXX: Unknown kex type %d", kex->kex_type); + } +#endif /* KEYNOTE */ } Index: sshd_config =================================================================== RCS file: /cvs/src/usr.bin/ssh/sshd_config,v retrieving revision 1.42 diff -u -u -r1.42 sshd_config --- sshd_config 20 Sep 2001 20:57:51 -0000 1.42 +++ sshd_config 28 Nov 2001 03:58:12 -0000 @@ -48,6 +48,9 @@ PasswordAuthentication yes PermitEmptyPasswords no +# Load per-user keynote policies +# LoadUserPolicies yes + # Uncomment to disable s/key passwords #ChallengeResponseAuthentication no Index: sshd_policy =================================================================== RCS file: sshd_policy diff -N sshd_policy --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sshd_policy 28 Nov 2001 03:58:12 -0000 @@ -0,0 +1,4 @@ +keynote-version: 2 +authorizer: "POLICY" +conditions: app_domain == "SSH" -> "allow"; + Index: sshd/Makefile =================================================================== RCS file: /cvs/src/usr.bin/ssh/sshd/Makefile,v retrieving revision 1.45 diff -u -u -r1.45 Makefile --- sshd/Makefile 7 Oct 2001 18:14:20 -0000 1.45 +++ sshd/Makefile 28 Nov 2001 03:58:12 -0000 @@ -16,6 +16,15 @@ auth-skey.c auth-bsdauth.c .include # for KERBEROS and AFS + +.ifdef KEYNOTE +.if (${KEYNOTE:L} == "yes") +SRCS+= policy.c +CFLAGS+=-DKEYNOTE +LDADD+= -lkeynote -lm +DPADD+= ${LIBKEYNOTE} ${LIBM} +.endif # KEYNOTE +.endif .if (${KERBEROS5:L} == "yes") CFLAGS+=-DKRB5 -I${DESTDIR}/usr/include/kerberosV