123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- """A sample socket server and client using SSPI authentication and encryption.
- You must run with either 'client' or 'server' as arguments. A server must be
- running before a client can connect.
- To use with Kerberos you should include in the client options
- --target-spn=username, where 'username' is the user under which the server is
- being run.
- Running either the client or server as a different user can be informative.
- A command-line such as the following may be useful:
- `runas /user:{user} {fqp}\python.exe {fqp}\socket_server.py --wait client|server`
- {fqp} should specify the relevant fully-qualified path names.
- To use 'runas' with Kerberos, the client program will need to
- specify --target-spn with the username under which the *server* is running.
- See the SSPI documentation for more details.
- """
- import sys
- import struct
- import SocketServer
- import win32api
- import httplib
- import traceback
- import win32security
- import sspi, sspicon
- import optparse # sorry, this demo needs 2.3+
- options = None # set to optparse object.
- def GetUserName():
- try:
- return win32api.GetUserName()
- except win32api.error, details:
- # Seeing 'access denied' errors here for non-local users (presumably
- # without permission to login locally). Get the fully-qualified
- # username, although a side-effect of these permission-denied errors
- # is a lack of Python codecs - so printing the Unicode value fails.
- # So just return the repr(), and avoid codecs completely.
- return repr(win32api.GetUserNameEx(win32api.NameSamCompatible))
-
- # Send a simple "message" over a socket - send the number of bytes first,
- # then the string. Ditto for receive.
- def _send_msg(s, m):
- s.send(struct.pack("i", len(m)))
- s.send(m)
- def _get_msg(s):
- size_data = s.recv(struct.calcsize("i"))
- if not size_data:
- return None
- cb = struct.unpack("i", size_data)[0]
- return s.recv(cb)
- class SSPISocketServer(SocketServer.TCPServer):
- def __init__(self, *args, **kw):
- SocketServer.TCPServer.__init__(self, *args, **kw)
- self.sa = sspi.ServerAuth(options.package)
- def verify_request(self, sock, ca):
- # Do the sspi auth dance
- self.sa.reset()
- while 1:
- data = _get_msg(sock)
- if data is None:
- return False
- try:
- err, sec_buffer = self.sa.authorize(data)
- except sspi.error, details:
- print "FAILED to authorize client:", details
- return False
-
- if err==0:
- break
- _send_msg(sock, sec_buffer[0].Buffer)
- return True
- def process_request(self, request, client_address):
- # An example using the connection once it is established.
- print "The server is running as user", GetUserName()
- self.sa.ctxt.ImpersonateSecurityContext()
- try:
- print "Having conversation with client as user", GetUserName()
- while 1:
- # we need to grab 2 bits of data - the encrypted data, and the
- # 'key'
- data = _get_msg(request)
- key = _get_msg(request)
- if data is None or key is None:
- break
- data = self.sa.decrypt(data, key)
- print "Client sent:", repr(data)
- finally:
- self.sa.ctxt.RevertSecurityContext()
- self.close_request(request)
- print "The server is back to user", GetUserName()
- def serve():
- s = SSPISocketServer(("localhost", options.port), None)
- print "Running test server..."
- s.serve_forever()
- def sspi_client():
- c = httplib.HTTPConnection("localhost", options.port)
- c.connect()
- # Do the auth dance.
- ca = sspi.ClientAuth(options.package, targetspn=options.target_spn)
- data = None
- while 1:
- err, out_buf = ca.authorize(data)
- _send_msg(c.sock, out_buf[0].Buffer)
- if err==0:
- break
- data = _get_msg(c.sock)
- print "Auth dance complete - sending a few encryted messages"
- # Assume out data is sensitive - encrypt the message.
- for data in "Hello from the client".split():
- blob, key = ca.encrypt(data)
- _send_msg(c.sock, blob)
- _send_msg(c.sock, key)
- c.sock.close()
- print "Client completed."
- if __name__=='__main__':
- parser = optparse.OptionParser("%prog [options] client|server",
- description=__doc__)
-
- parser.add_option("", "--package", action="store", default="NTLM",
- help="The SSPI package to use (eg, Kerberos) - default is NTLM")
- parser.add_option("", "--target-spn", action="store",
- help="""The target security provider name to use. The
- string contents are security-package specific. For
- example, 'Kerberos' or 'Negotiate' require the server
- principal name (SPN) (ie, the username) of the remote
- process. For NTLM this must be blank.""")
- parser.add_option("", "--port", action="store", default="8181",
- help="The port number to use (default=8181)")
- parser.add_option("", "--wait", action="store_true",
- help="""Cause the program to wait for input just before
- terminating. Useful when using via runas to see
- any error messages before termination.
- """)
- options, args = parser.parse_args()
- try:
- options.port = int(options.port)
- except (ValueError, TypeError):
- parser.error("--port must be an integer")
- try:
- try:
- if not args:
- args = ['']
- if args[0]=="client":
- sspi_client()
- elif args[0]=="server":
- serve()
- else:
- parser.error("You must supply 'client' or 'server' - " \
- "use --help for details")
- except KeyboardInterrupt:
- pass
- except SystemExit:
- pass
- except:
- traceback.print_exc()
- finally:
- if options.wait:
- raw_input("Press enter to continue")
|