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 29 Nov 2001 06:34:16 -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 29 Nov 2001 06:34:16 -0000 @@ -0,0 +1,140 @@ +Keynote in OpenSSH +------------------ + +To enable Keynote policy support, build OpenSSH using "make KEYNOTE=yes" + +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. + +XXX 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 + + return values: true, false + XXX: chroot [assume homedir as target] + + action = "auth" + auth_type = "none" / "password" / "pubkey" / "krb4" / "krb5" / "rhosts" / + "hostbased" / "challenge-response" + XXX: how to handle kbd-int subtypes? + +Upon successful pubkey authentication, the public key is added as an +authorizer. SSH Protocol 1 RSA public keys are encoded in the form: + + sshkey:rsa1_142789799855908362[...]11720372574856523841 + +The number (abbreviated in this example) is the third number in the +public identity file (the long one). SSH Protocol 2 keys are encoded as: + + sshkey:ssh-rsa_AAAAB3NzaC1yc2a[...]pMauaWV+G2LBcYtfYD8= + +i.e the public key algorithm name followed by the base 64 encoded public key. + +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" + + +An example sshd_policy showing most of the options: + +KeyNote-Version: 2 +Authorizer: "POLICY" +Licensees: "sshkey:rsa1_142362942992746870816203304480177996474136640964902537245316585105323035035389593007019094003854898216393663129105332254734300064326569758025024624135192288246617067024977035125102278207302333238589481951107404586302477679689977049348701111391733148522899908828766451267592807933218738225311720372574856523841" || "sshkey:ssh-rsa_AAAAB3NzaC1yc2EAAAABIwAAAIEA0zFnigHloAYTCsmLgBsHZeJBjs7Q6LCjjBAXe/vPrHh0SfPZIrcpWxuqbuOniXJ0RNmwJMvq/cVIVF1pS/dgtmkgeP/5I1xPX6IvAtJsNi8Df23ypiV++QVxHd76FM683ZzyZC2SBv4nkpXWMuCaapxx0MauaWV+G2LBcYtfYD8=" +Conditions: + (app_domain == "SSH") -> { + action == "agent-forward" -> "true"; + action == "auth" -> { + auth_type == "none" -> "true"; + auth_type == "challenge-response" -> "true"; + auth_type == "hostbased" -> "true"; + auth_type == "krb4" -> "true"; + auth_type == "password" -> "true"; + auth_type == "pubkey" -> "true"; + auth_type == "rhosts" -> "true"; + }; + action == "exec" -> "true"; + action == "lport-forward" -> "true"; + action == "pty-request" -> "true"; + action == "request-compression1" -> "true"; + action == "rport-forward" -> "true"; + action == "shell" -> "true"; + action == "subsys" -> "true"; + action == "x11-forward" -> "true"; + }; + + 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 29 Nov 2001 06:34:17 -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 @@ -214,6 +236,12 @@ packet_integrity_check(plen, (4 + ulen) + 4 + elen + nlen, type); authenticated = auth_rhosts_rsa(pw, client_user, client_host_key); +#ifdef KEYNOTE + if (authenticated && + !policy_auth_check_pubkey1("hostbased", + client_host_key->n)) + authenticated = 0; +#endif RSA_free(client_host_key); snprintf(info, sizeof info, " ruser %.100s", client_user); @@ -230,6 +258,11 @@ packet_get_bignum(n, &nlen); packet_integrity_check(plen, nlen, type); authenticated = auth_rsa(pw, n); +#ifdef KEYNOTE + if (authenticated && + !policy_auth_check_pubkey1("pubkey", n)) + authenticated = 0; +#endif BN_clear_free(n); break; @@ -238,6 +271,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 +292,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 +312,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 +401,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 29 Nov 2001 06:34:18 -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; } @@ -428,6 +448,11 @@ if (user_key_allowed(authctxt->pw, key) && key_verify(key, sig, slen, buffer_ptr(&b), buffer_len(&b)) == 1) authenticated = 1; +#ifdef KEYNOTE + if (authenticated && + !policy_auth_check_pubkey2("pubkey", key)) + authenticated = 0; +#endif buffer_clear(&b); xfree(sig); } else { @@ -441,6 +466,10 @@ * message is sent. the message is NEVER sent at all * if a user is not allowed to login. is this an * issue? -markus + * + * It might be an issue with Keynote policy + * support, as this leaks info about which keys + * may be valid (if policies were not in effect) -djm */ if (user_key_allowed(authctxt->pw, key)) { packet_start(SSH2_MSG_USERAUTH_PK_OK); @@ -533,6 +562,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 29 Nov 2001 06:34:21 -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 29 Nov 2001 06:34:21 -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 29 Nov 2001 06:34:21 -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 29 Nov 2001 06:34:21 -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 29 Nov 2001 06:34:21 -0000 @@ -0,0 +1,446 @@ +/* + * 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 "key.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[] = { "false", "true" }; +enum policy_result { + POLICY_FALSE = 0, + POLICY_TRUE = 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); + + debug3("policy - Deleting variable %s", n); + 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_TRUE) { + 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_TRUE) { + 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; +} + +static char * +policy_key1_to_string(BIGNUM *client_n) +{ + char buf[1024], *ret, *n; + + if ((n = BN_bn2dec(client_n)) == NULL) { + error("policy_key1_to_string: BN_bn2dec failed"); + return NULL; + } + + snprintf(buf, sizeof(buf), "sshkey:rsa1_%s", n); + ret = xstrdup(buf); + + OPENSSL_free(n); + + return ret; +} + +static char * +policy_key2_to_string(Key *key) +{ + char buf[2048], *ret; + + /* + * NB. We only handle protocol 2 keys, as RSA1 keys are checked + * directly by auth_rsa and are never converted to a Key* + */ + + ret = NULL; + if ((key->type == KEY_DSA && key->dsa != NULL) || + (key->type == KEY_RSA && key->rsa != NULL)) { + int len, n; + u_char *blob, *uu; + + key_to_blob(key, &blob, &len); + uu = xmalloc(2 * len); + n = uuencode(blob, len, uu, 2 * len); + if (n <= 0) { + error("key_to_string: uuencode failed"); + return NULL; + } + snprintf(buf, sizeof(buf), "sshkey:%s_%s", + key_ssh_name(key), uu); + ret = xstrdup(buf); + xfree(blob); + xfree(uu); + } + return ret; +} + +static void +policy_del_authorizer(char *authorizer) +{ + debug3("policy - Removing authorizer: %s", authorizer); + if (kn_remove_authorizer(keynote_id, authorizer) == -1) + fatal("kn_remove_authorizer failed, error %d", keynote_errno); +} + +static void +policy_add_authorizer(char *authorizer) +{ + debug2("policy - Adding authorizer: %s", authorizer); + if (kn_add_authorizer(keynote_id, authorizer) == -1) + fatal("kn_add_authorizer failed, error %d", keynote_errno); +} + +static int +policy_auth_check_authorizer(const char *authtype, char *authorizer) +{ + int ret; + + policy_add_authorizer(authorizer); + + ret = policy_auth_check(authtype); + + /* + * Leave authorizers around upon success so we can delegate + * post-authentication policy (e.g. port forward restrictions) + * to them. + */ + if (ret == 0) + policy_del_authorizer(authorizer); + + return ret; +} + +int +policy_auth_check_pubkey1(const char *authtype, BIGNUM *n) +{ + char *authorizer; + int ret; + + if ((authorizer = policy_key1_to_string(n)) == NULL) + return 0; + + ret = policy_auth_check_authorizer(authtype, authorizer); + + xfree(authorizer); + + return ret; +} + +int +policy_auth_check_pubkey2(const char *authtype, Key *k) +{ + char *authorizer; + int ret; + + if ((authorizer = policy_key2_to_string(k)) == NULL) + return 0; + + ret = policy_auth_check_authorizer(authtype, authorizer); + + xfree(authorizer); + + 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 29 Nov 2001 06:34:21 -0000 @@ -0,0 +1,62 @@ +/* + * 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); + +int +policy_auth_check_pubkey1(const char *authtype, BIGNUM *n); + +int +policy_auth_check_pubkey2(const char *authtype, Key *k); + 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 29 Nov 2001 06:34:22 -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 29 Nov 2001 06:34:22 -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 29 Nov 2001 06:34:23 -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 29 Nov 2001 06:34:24 -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 29 Nov 2001 06:34:26 -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 29 Nov 2001 06:34:26 -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 29 Nov 2001 06:34:26 -0000 @@ -0,0 +1,4 @@ +keynote-version: 2 +authorizer: "POLICY" +conditions: app_domain == "SSH" -> "true"; + 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 29 Nov 2001 06:34:26 -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