socket_server.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. """A sample socket server and client using SSPI authentication and encryption.
  2. You must run with either 'client' or 'server' as arguments. A server must be
  3. running before a client can connect.
  4. To use with Kerberos you should include in the client options
  5. --target-spn=username, where 'username' is the user under which the server is
  6. being run.
  7. Running either the client or server as a different user can be informative.
  8. A command-line such as the following may be useful:
  9. `runas /user:{user} {fqp}\python.exe {fqp}\socket_server.py --wait client|server`
  10. {fqp} should specify the relevant fully-qualified path names.
  11. To use 'runas' with Kerberos, the client program will need to
  12. specify --target-spn with the username under which the *server* is running.
  13. See the SSPI documentation for more details.
  14. """
  15. import sys
  16. import struct
  17. import SocketServer
  18. import win32api
  19. import httplib
  20. import traceback
  21. import win32security
  22. import sspi, sspicon
  23. import optparse # sorry, this demo needs 2.3+
  24. options = None # set to optparse object.
  25. def GetUserName():
  26. try:
  27. return win32api.GetUserName()
  28. except win32api.error, details:
  29. # Seeing 'access denied' errors here for non-local users (presumably
  30. # without permission to login locally). Get the fully-qualified
  31. # username, although a side-effect of these permission-denied errors
  32. # is a lack of Python codecs - so printing the Unicode value fails.
  33. # So just return the repr(), and avoid codecs completely.
  34. return repr(win32api.GetUserNameEx(win32api.NameSamCompatible))
  35. # Send a simple "message" over a socket - send the number of bytes first,
  36. # then the string. Ditto for receive.
  37. def _send_msg(s, m):
  38. s.send(struct.pack("i", len(m)))
  39. s.send(m)
  40. def _get_msg(s):
  41. size_data = s.recv(struct.calcsize("i"))
  42. if not size_data:
  43. return None
  44. cb = struct.unpack("i", size_data)[0]
  45. return s.recv(cb)
  46. class SSPISocketServer(SocketServer.TCPServer):
  47. def __init__(self, *args, **kw):
  48. SocketServer.TCPServer.__init__(self, *args, **kw)
  49. self.sa = sspi.ServerAuth(options.package)
  50. def verify_request(self, sock, ca):
  51. # Do the sspi auth dance
  52. self.sa.reset()
  53. while 1:
  54. data = _get_msg(sock)
  55. if data is None:
  56. return False
  57. try:
  58. err, sec_buffer = self.sa.authorize(data)
  59. except sspi.error, details:
  60. print "FAILED to authorize client:", details
  61. return False
  62. if err==0:
  63. break
  64. _send_msg(sock, sec_buffer[0].Buffer)
  65. return True
  66. def process_request(self, request, client_address):
  67. # An example using the connection once it is established.
  68. print "The server is running as user", GetUserName()
  69. self.sa.ctxt.ImpersonateSecurityContext()
  70. try:
  71. print "Having conversation with client as user", GetUserName()
  72. while 1:
  73. # we need to grab 2 bits of data - the encrypted data, and the
  74. # 'key'
  75. data = _get_msg(request)
  76. key = _get_msg(request)
  77. if data is None or key is None:
  78. break
  79. data = self.sa.decrypt(data, key)
  80. print "Client sent:", repr(data)
  81. finally:
  82. self.sa.ctxt.RevertSecurityContext()
  83. self.close_request(request)
  84. print "The server is back to user", GetUserName()
  85. def serve():
  86. s = SSPISocketServer(("localhost", options.port), None)
  87. print "Running test server..."
  88. s.serve_forever()
  89. def sspi_client():
  90. c = httplib.HTTPConnection("localhost", options.port)
  91. c.connect()
  92. # Do the auth dance.
  93. ca = sspi.ClientAuth(options.package, targetspn=options.target_spn)
  94. data = None
  95. while 1:
  96. err, out_buf = ca.authorize(data)
  97. _send_msg(c.sock, out_buf[0].Buffer)
  98. if err==0:
  99. break
  100. data = _get_msg(c.sock)
  101. print "Auth dance complete - sending a few encryted messages"
  102. # Assume out data is sensitive - encrypt the message.
  103. for data in "Hello from the client".split():
  104. blob, key = ca.encrypt(data)
  105. _send_msg(c.sock, blob)
  106. _send_msg(c.sock, key)
  107. c.sock.close()
  108. print "Client completed."
  109. if __name__=='__main__':
  110. parser = optparse.OptionParser("%prog [options] client|server",
  111. description=__doc__)
  112. parser.add_option("", "--package", action="store", default="NTLM",
  113. help="The SSPI package to use (eg, Kerberos) - default is NTLM")
  114. parser.add_option("", "--target-spn", action="store",
  115. help="""The target security provider name to use. The
  116. string contents are security-package specific. For
  117. example, 'Kerberos' or 'Negotiate' require the server
  118. principal name (SPN) (ie, the username) of the remote
  119. process. For NTLM this must be blank.""")
  120. parser.add_option("", "--port", action="store", default="8181",
  121. help="The port number to use (default=8181)")
  122. parser.add_option("", "--wait", action="store_true",
  123. help="""Cause the program to wait for input just before
  124. terminating. Useful when using via runas to see
  125. any error messages before termination.
  126. """)
  127. options, args = parser.parse_args()
  128. try:
  129. options.port = int(options.port)
  130. except (ValueError, TypeError):
  131. parser.error("--port must be an integer")
  132. try:
  133. try:
  134. if not args:
  135. args = ['']
  136. if args[0]=="client":
  137. sspi_client()
  138. elif args[0]=="server":
  139. serve()
  140. else:
  141. parser.error("You must supply 'client' or 'server' - " \
  142. "use --help for details")
  143. except KeyboardInterrupt:
  144. pass
  145. except SystemExit:
  146. pass
  147. except:
  148. traceback.print_exc()
  149. finally:
  150. if options.wait:
  151. raw_input("Press enter to continue")