8985c7
diff --git a/paramiko/common.py b/paramiko/common.py
8985c7
index 0b0cc2a..50355f6 100644
8985c7
--- a/paramiko/common.py
8985c7
+++ b/paramiko/common.py
8985c7
@@ -32,6 +32,7 @@ MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE = range(60, 62)
8985c7
 MSG_USERAUTH_GSSAPI_RESPONSE, MSG_USERAUTH_GSSAPI_TOKEN = range(60, 62)
8985c7
 MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, MSG_USERAUTH_GSSAPI_ERROR,\
8985c7
 MSG_USERAUTH_GSSAPI_ERRTOK, MSG_USERAUTH_GSSAPI_MIC = range(63, 67)
8985c7
+HIGHEST_USERAUTH_MESSAGE_ID = 79
8985c7
 MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE = range(80, 83)
8985c7
 MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \
8985c7
     MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA, \
8985c7
diff --git a/paramiko/transport.py b/paramiko/transport.py
8985c7
index 7906c9f..31df82a 100644
8985c7
--- a/paramiko/transport.py
8985c7
+++ b/paramiko/transport.py
8985c7
@@ -49,7 +49,8 @@ from paramiko.common import xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \
8985c7
     MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE, MSG_CHANNEL_DATA, \
8985c7
     MSG_CHANNEL_EXTENDED_DATA, MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_REQUEST, \
8985c7
     MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MIN_WINDOW_SIZE, MIN_PACKET_SIZE, \
8985c7
-    MAX_WINDOW_SIZE, DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE
8985c7
+    MAX_WINDOW_SIZE, DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE, \
8985c7
+    HIGHEST_USERAUTH_MESSAGE_ID
8985c7
 from paramiko.compress import ZlibCompressor, ZlibDecompressor
8985c7
 from paramiko.dsskey import DSSKey
8985c7
 from paramiko.kex_gex import KexGex, KexGexSHA256
8985c7
@@ -1720,6 +1721,43 @@ class Transport (threading.Thread, ClosingContextManager):
8985c7
             max_packet_size = self.default_max_packet_size
8985c7
         return clamp_value(MIN_PACKET_SIZE, max_packet_size, MAX_WINDOW_SIZE)
8985c7
 
8985c7
+    def _ensure_authed(self, ptype, message):
8985c7
+        """
8985c7
+        Checks message type against current auth state.
8985c7
+
8985c7
+        If server mode, and auth has not succeeded, and the message is of a
8985c7
+        post-auth type (channel open or global request) an appropriate error
8985c7
+        response Message is crafted and returned to caller for sending.
8985c7
+
8985c7
+        Otherwise (client mode, authed, or pre-auth message) returns None.
8985c7
+        """
8985c7
+        if (
8985c7
+            not self.server_mode
8985c7
+            or ptype <= HIGHEST_USERAUTH_MESSAGE_ID
8985c7
+            or self.is_authenticated()
8985c7
+        ):
8985c7
+            return None
8985c7
+        # WELP. We must be dealing with someone trying to do non-auth things
8985c7
+        # without being authed. Tell them off, based on message class.
8985c7
+        reply = Message()
8985c7
+        # Global requests have no details, just failure.
8985c7
+        if ptype == MSG_GLOBAL_REQUEST:
8985c7
+            reply.add_byte(cMSG_REQUEST_FAILURE)
8985c7
+        # Channel opens let us reject w/ a specific type + message.
8985c7
+        elif ptype == MSG_CHANNEL_OPEN:
8985c7
+            kind = message.get_text()
8985c7
+            chanid = message.get_int()
8985c7
+            reply.add_byte(cMSG_CHANNEL_OPEN_FAILURE)
8985c7
+            reply.add_int(chanid)
8985c7
+            reply.add_int(OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED)
8985c7
+            reply.add_string('')
8985c7
+            reply.add_string('en')
8985c7
+        # NOTE: Post-open channel messages do not need checking; the above will
8985c7
+        # reject attemps to open channels, meaning that even if a malicious
8985c7
+        # user tries to send a MSG_CHANNEL_REQUEST, it will simply fall under
8985c7
+        # the logic that handles unknown channel IDs (as the channel list will
8985c7
+        # be empty.)
8985c7
+        return reply
8985c7
 
8985c7
     def run(self):
8985c7
         # (use the exposed "run" method, because if we specify a thread target
8985c7
@@ -1779,7 +1817,11 @@ class Transport (threading.Thread, ClosingContextManager):
8985c7
                             continue
8985c7
 
8985c7
                     if ptype in self._handler_table:
8985c7
-                        self._handler_table[ptype](self, m)
8985c7
+                        error_msg = self._ensure_authed(ptype, m)
8985c7
+                        if error_msg:
8985c7
+                            self._send_message(error_msg)
8985c7
+                        else:
8985c7
+                            self._handler_table[ptype](self, m)
8985c7
                     elif ptype in self._channel_handler_table:
8985c7
                         chanid = m.get_int()
8985c7
                         chan = self._channels.get(chanid)
8985c7
diff --git a/tests/test_transport.py b/tests/test_transport.py
8985c7
index d81ad8f..1305cd5 100644
8985c7
--- a/tests/test_transport.py
8985c7
+++ b/tests/test_transport.py
8985c7
@@ -32,7 +32,7 @@ from hashlib import sha1
8985c7
 import unittest
8985c7
 
8985c7
 from paramiko import Transport, SecurityOptions, ServerInterface, RSAKey, DSSKey, \
8985c7
-    SSHException, ChannelException, Packetizer
8985c7
+    SSHException, ChannelException, Packetizer, Channel
8985c7
 from paramiko import AUTH_FAILED, AUTH_SUCCESSFUL
8985c7
 from paramiko import OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
8985c7
 from paramiko.common import MSG_KEXINIT, cMSG_CHANNEL_WINDOW_ADJUST, \
8985c7
@@ -87,7 +87,11 @@ class NullServer (ServerInterface):
8985c7
 
8985c7
     def check_global_request(self, kind, msg):
8985c7
         self._global_request = kind
8985c7
-        return False
8985c7
+        # NOTE: for w/e reason, older impl of this returned False always, even
8985c7
+        # tho that's only supposed to occur if the request cannot be served.
8985c7
+        # For now, leaving that the default unless test supplies specific
8985c7
+        # 'acceptable' request kind
8985c7
+        return kind == 'acceptable'
8985c7
 
8985c7
     def check_channel_x11_request(self, channel, single_connection, auth_protocol, auth_cookie, screen_number):
8985c7
         self._x11_single_connection = single_connection
8985c7
@@ -125,7 +129,9 @@ class TransportTest(unittest.TestCase):
8985c7
         self.socks.close()
8985c7
         self.sockc.close()
8985c7
 
8985c7
-    def setup_test_server(self, client_options=None, server_options=None):
8985c7
+    def setup_test_server(
8985c7
+        self, client_options=None, server_options=None, connect_kwargs=None,
8985c7
+    ):
8985c7
         host_key = RSAKey.from_private_key_file(test_path('test_rsa.key'))
8985c7
         public_host_key = RSAKey(data=host_key.asbytes())
8985c7
         self.ts.add_server_key(host_key)
8985c7
@@ -139,8 +145,13 @@ class TransportTest(unittest.TestCase):
8985c7
         self.server = NullServer()
8985c7
         self.assertTrue(not event.is_set())
8985c7
         self.ts.start_server(event, self.server)
8985c7
-        self.tc.connect(hostkey=public_host_key,
8985c7
-                        username='slowdive', password='pygmalion')
8985c7
+        if connect_kwargs is None:
8985c7
+            connect_kwargs = dict(
8985c7
+                hostkey=public_host_key,
8985c7
+                username='slowdive',
8985c7
+                password='pygmalion',
8985c7
+            )
8985c7
+        self.tc.connect(**connect_kwargs)
8985c7
         event.wait(1.0)
8985c7
         self.assertTrue(event.is_set())
8985c7
         self.assertTrue(self.ts.is_active())
8985c7
@@ -846,3 +857,37 @@ class TransportTest(unittest.TestCase):
8985c7
         self.assertEqual([chan], r)
8985c7
         self.assertEqual([], w)
8985c7
         self.assertEqual([], e)
8985c7
+
8985c7
+    def test_server_rejects_open_channel_without_auth(self):
8985c7
+        try:
8985c7
+            self.setup_test_server(connect_kwargs={})
8985c7
+            self.tc.open_session()
8985c7
+        except ChannelException as e:
8985c7
+            assert e.code == OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
8985c7
+        else:
8985c7
+            assert False, "Did not raise ChannelException!"
8985c7
+
8985c7
+    def test_server_rejects_arbitrary_global_request_without_auth(self):
8985c7
+        self.setup_test_server(connect_kwargs={})
8985c7
+        # NOTE: this dummy global request kind would normally pass muster
8985c7
+        # from the test server.
8985c7
+        self.tc.global_request('acceptable')
8985c7
+        # Global requests never raise exceptions, even on failure (not sure why
8985c7
+        # this was the original design...ugh.) Best we can do to tell failure
8985c7
+        # happened is that the client transport's global_response was set back
8985c7
+        # to None; if it had succeeded, it would be the response Message.
8985c7
+        err = "Unauthed global response incorrectly succeeded!"
8985c7
+        assert self.tc.global_response is None, err
8985c7
+
8985c7
+    def test_server_rejects_port_forward_without_auth(self):
8985c7
+        # NOTE: at protocol level port forward requests are treated same as a
8985c7
+        # regular global request, but Paramiko server implements a special-case
8985c7
+        # method for it, so it gets its own test. (plus, THAT actually raises
8985c7
+        # an exception on the client side, unlike the general case...)
8985c7
+        self.setup_test_server(connect_kwargs={})
8985c7
+        try:
8985c7
+            self.tc.request_port_forward('localhost', 1234)
8985c7
+        except SSHException as e:
8985c7
+            assert "forwarding request denied" in str(e)
8985c7
+        else:
8985c7
+            assert False, "Did not raise SSHException!"