OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome. | 6 """This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome. |
7 | 7 |
8 It supports several test URLs, as specified by the handlers in TestPageHandler. | 8 It supports several test URLs, as specified by the handlers in TestPageHandler. |
9 By default, it listens on an ephemeral port and sends the port number back to | 9 By default, it listens on an ephemeral port and sends the port number back to |
10 the originating process over a pipe. The originating process can specify an | 10 the originating process over a pipe. The originating process can specify an |
11 explicit port if necessary. | 11 explicit port if necessary. |
12 It can use https if you specify the flag --https=CERT where CERT is the path | 12 It can use https if you specify the flag --https=CERT where CERT is the path |
13 to a pem file containing the certificate and private key that should be used. | 13 to a pem file containing the certificate and private key that should be used. |
14 """ | 14 """ |
15 | 15 |
16 import asyncore | 16 import asyncore |
17 import base64 | 17 import base64 |
18 import BaseHTTPServer | 18 import BaseHTTPServer |
19 import cgi | 19 import cgi |
20 import errno | 20 import errno |
| 21 import hashlib |
21 import httplib | 22 import httplib |
22 import minica | 23 import minica |
23 import optparse | |
24 import os | 24 import os |
25 import random | 25 import random |
26 import re | 26 import re |
27 import select | 27 import select |
28 import socket | 28 import socket |
29 import SocketServer | 29 import SocketServer |
30 import struct | |
31 import sys | 30 import sys |
32 import threading | 31 import threading |
33 import time | 32 import time |
34 import urllib | 33 import urllib |
35 import urlparse | 34 import urlparse |
36 import warnings | |
37 import zlib | 35 import zlib |
38 | 36 |
39 # Ignore deprecation warnings, they make our output more cluttered. | |
40 warnings.filterwarnings("ignore", category=DeprecationWarning) | |
41 | |
42 import echo_message | 37 import echo_message |
43 import pyftpdlib.ftpserver | 38 import pyftpdlib.ftpserver |
| 39 import testserver_base |
44 import tlslite | 40 import tlslite |
45 import tlslite.api | 41 import tlslite.api |
46 | 42 |
47 try: | |
48 import hashlib | |
49 _new_md5 = hashlib.md5 | |
50 except ImportError: | |
51 import md5 | |
52 _new_md5 = md5.new | |
53 | 43 |
54 try: | 44 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
55 import json | |
56 except ImportError: | |
57 import simplejson as json | |
58 | |
59 if sys.platform == 'win32': | |
60 import msvcrt | |
61 | 45 |
62 SERVER_HTTP = 0 | 46 SERVER_HTTP = 0 |
63 SERVER_FTP = 1 | 47 SERVER_FTP = 1 |
64 SERVER_SYNC = 2 | 48 SERVER_SYNC = 2 |
65 SERVER_TCP_ECHO = 3 | 49 SERVER_TCP_ECHO = 3 |
66 SERVER_UDP_ECHO = 4 | 50 SERVER_UDP_ECHO = 4 |
67 | 51 |
| 52 |
68 # Using debug() seems to cause hangs on XP: see http://crbug.com/64515 . | 53 # Using debug() seems to cause hangs on XP: see http://crbug.com/64515 . |
69 debug_output = sys.stderr | 54 debug_output = sys.stderr |
70 def debug(str): | 55 def debug(str): |
71 debug_output.write(str + "\n") | 56 debug_output.write(str + "\n") |
72 debug_output.flush() | 57 debug_output.flush() |
73 | 58 |
| 59 |
74 class RecordingSSLSessionCache(object): | 60 class RecordingSSLSessionCache(object): |
75 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of | 61 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of |
76 lookups and inserts in order to test session cache behaviours.""" | 62 lookups and inserts in order to test session cache behaviours.""" |
77 | 63 |
78 def __init__(self): | 64 def __init__(self): |
79 self.log = [] | 65 self.log = [] |
80 | 66 |
81 def __getitem__(self, sessionID): | 67 def __getitem__(self, sessionID): |
82 self.log.append(('lookup', sessionID)) | 68 self.log.append(('lookup', sessionID)) |
83 raise KeyError() | 69 raise KeyError() |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
118 | 104 |
119 def serve_forever_on_thread(self): | 105 def serve_forever_on_thread(self): |
120 self.thread = threading.Thread(target = self.serve_forever, | 106 self.thread = threading.Thread(target = self.serve_forever, |
121 name = "OCSPServerThread") | 107 name = "OCSPServerThread") |
122 self.thread.start() | 108 self.thread.start() |
123 | 109 |
124 def stop_serving(self): | 110 def stop_serving(self): |
125 self.shutdown() | 111 self.shutdown() |
126 self.thread.join() | 112 self.thread.join() |
127 | 113 |
| 114 |
128 class HTTPSServer(tlslite.api.TLSSocketServerMixIn, | 115 class HTTPSServer(tlslite.api.TLSSocketServerMixIn, |
129 ClientRestrictingServerMixIn, | 116 ClientRestrictingServerMixIn, |
130 StoppableHTTPServer): | 117 StoppableHTTPServer): |
131 """This is a specialization of StoppableHTTPServer that add https support and | 118 """This is a specialization of StoppableHTTPServer that add https support and |
132 client verification.""" | 119 client verification.""" |
133 | 120 |
134 def __init__(self, server_address, request_hander_class, pem_cert_and_key, | 121 def __init__(self, server_address, request_hander_class, pem_cert_and_key, |
135 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers, | 122 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers, |
136 record_resume_info, tls_intolerant): | 123 record_resume_info, tls_intolerant): |
137 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key) | 124 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key) |
(...skipping 1172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1310 This is a fake implementation. A real implementation would only use a given | 1297 This is a fake implementation. A real implementation would only use a given |
1311 nonce a single time (hence the name n-once). However, for the purposes of | 1298 nonce a single time (hence the name n-once). However, for the purposes of |
1312 unittesting, we don't care about the security of the nonce. | 1299 unittesting, we don't care about the security of the nonce. |
1313 | 1300 |
1314 Args: | 1301 Args: |
1315 force_reset: Iff set, the nonce will be changed. Useful for testing the | 1302 force_reset: Iff set, the nonce will be changed. Useful for testing the |
1316 "stale" response. | 1303 "stale" response. |
1317 """ | 1304 """ |
1318 if force_reset or not self.server.nonce_time: | 1305 if force_reset or not self.server.nonce_time: |
1319 self.server.nonce_time = time.time() | 1306 self.server.nonce_time = time.time() |
1320 return _new_md5('privatekey%s%d' % | 1307 return hashlib.md5('privatekey%s%d' % |
1321 (self.path, self.server.nonce_time)).hexdigest() | 1308 (self.path, self.server.nonce_time)).hexdigest() |
1322 | 1309 |
1323 def AuthDigestHandler(self): | 1310 def AuthDigestHandler(self): |
1324 """This handler tests 'Digest' authentication. | 1311 """This handler tests 'Digest' authentication. |
1325 | 1312 |
1326 It just sends a page with title 'user/pass' if you succeed. | 1313 It just sends a page with title 'user/pass' if you succeed. |
1327 | 1314 |
1328 A stale response is sent iff "stale" is present in the request path. | 1315 A stale response is sent iff "stale" is present in the request path. |
1329 """ | 1316 """ |
1330 if not self._ShouldHandleRequest("/auth-digest"): | 1317 if not self._ShouldHandleRequest("/auth-digest"): |
1331 return False | 1318 return False |
1332 | 1319 |
1333 stale = 'stale' in self.path | 1320 stale = 'stale' in self.path |
1334 nonce = self.GetNonce(force_reset=stale) | 1321 nonce = self.GetNonce(force_reset=stale) |
1335 opaque = _new_md5('opaque').hexdigest() | 1322 opaque = hashlib.md5('opaque').hexdigest() |
1336 password = 'secret' | 1323 password = 'secret' |
1337 realm = 'testrealm' | 1324 realm = 'testrealm' |
1338 | 1325 |
1339 auth = self.headers.getheader('authorization') | 1326 auth = self.headers.getheader('authorization') |
1340 pairs = {} | 1327 pairs = {} |
1341 try: | 1328 try: |
1342 if not auth: | 1329 if not auth: |
1343 raise Exception('no auth') | 1330 raise Exception('no auth') |
1344 if not auth.startswith('Digest'): | 1331 if not auth.startswith('Digest'): |
1345 raise Exception('not digest') | 1332 raise Exception('not digest') |
1346 # Pull out all the name="value" pairs as a dictionary. | 1333 # Pull out all the name="value" pairs as a dictionary. |
1347 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth)) | 1334 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth)) |
1348 | 1335 |
1349 # Make sure it's all valid. | 1336 # Make sure it's all valid. |
1350 if pairs['nonce'] != nonce: | 1337 if pairs['nonce'] != nonce: |
1351 raise Exception('wrong nonce') | 1338 raise Exception('wrong nonce') |
1352 if pairs['opaque'] != opaque: | 1339 if pairs['opaque'] != opaque: |
1353 raise Exception('wrong opaque') | 1340 raise Exception('wrong opaque') |
1354 | 1341 |
1355 # Check the 'response' value and make sure it matches our magic hash. | 1342 # Check the 'response' value and make sure it matches our magic hash. |
1356 # See http://www.ietf.org/rfc/rfc2617.txt | 1343 # See http://www.ietf.org/rfc/rfc2617.txt |
1357 hash_a1 = _new_md5( | 1344 hash_a1 = hashlib.md5( |
1358 ':'.join([pairs['username'], realm, password])).hexdigest() | 1345 ':'.join([pairs['username'], realm, password])).hexdigest() |
1359 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest() | 1346 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest() |
1360 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs: | 1347 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs: |
1361 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'], | 1348 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'], |
1362 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest() | 1349 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest() |
1363 else: | 1350 else: |
1364 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest() | 1351 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest() |
1365 | 1352 |
1366 if pairs['response'] != response: | 1353 if pairs['response'] != response: |
1367 raise Exception('wrong password') | 1354 raise Exception('wrong password') |
1368 except Exception, e: | 1355 except Exception, e: |
1369 # Authentication failed. | 1356 # Authentication failed. |
1370 self.send_response(401) | 1357 self.send_response(401) |
1371 hdr = ('Digest ' | 1358 hdr = ('Digest ' |
1372 'realm="%s", ' | 1359 'realm="%s", ' |
1373 'domain="/", ' | 1360 'domain="/", ' |
1374 'qop="auth", ' | 1361 'qop="auth", ' |
(...skipping 559 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1934 return False | 1921 return False |
1935 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks() | 1922 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks() |
1936 self.send_response(result) | 1923 self.send_response(result) |
1937 self.send_header('Content-Type', 'text/html') | 1924 self.send_header('Content-Type', 'text/html') |
1938 self.send_header('Content-Length', len(raw_reply)) | 1925 self.send_header('Content-Length', len(raw_reply)) |
1939 self.end_headers() | 1926 self.end_headers() |
1940 self.wfile.write(raw_reply) | 1927 self.wfile.write(raw_reply) |
1941 return True; | 1928 return True; |
1942 | 1929 |
1943 | 1930 |
1944 def MakeDataDir(): | |
1945 if options.data_dir: | |
1946 if not os.path.isdir(options.data_dir): | |
1947 print 'specified data dir not found: ' + options.data_dir + ' exiting...' | |
1948 return None | |
1949 my_data_dir = options.data_dir | |
1950 else: | |
1951 # Create the default path to our data dir, relative to the exe dir. | |
1952 my_data_dir = os.path.dirname(sys.argv[0]) | |
1953 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..", | |
1954 "test", "data") | |
1955 | |
1956 #TODO(ibrar): Must use Find* funtion defined in google\tools | |
1957 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data") | |
1958 | |
1959 return my_data_dir | |
1960 | |
1961 class OCSPHandler(BasePageHandler): | 1931 class OCSPHandler(BasePageHandler): |
1962 def __init__(self, request, client_address, socket_server): | 1932 def __init__(self, request, client_address, socket_server): |
1963 handlers = [self.OCSPResponse] | 1933 handlers = [self.OCSPResponse] |
1964 self.ocsp_response = socket_server.ocsp_response | 1934 self.ocsp_response = socket_server.ocsp_response |
1965 BasePageHandler.__init__(self, request, client_address, socket_server, | 1935 BasePageHandler.__init__(self, request, client_address, socket_server, |
1966 [], handlers, [], handlers, []) | 1936 [], handlers, [], handlers, []) |
1967 | 1937 |
1968 def OCSPResponse(self): | 1938 def OCSPResponse(self): |
1969 self.send_response(200) | 1939 self.send_response(200) |
1970 self.send_header('Content-Type', 'application/ocsp-response') | 1940 self.send_header('Content-Type', 'application/ocsp-response') |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2012 # "echo response" message if "echo request" message is valid. | 1982 # "echo response" message if "echo request" message is valid. |
2013 try: | 1983 try: |
2014 return_data = echo_message.GetEchoResponseData(data) | 1984 return_data = echo_message.GetEchoResponseData(data) |
2015 if not return_data: | 1985 if not return_data: |
2016 return | 1986 return |
2017 except ValueError: | 1987 except ValueError: |
2018 return | 1988 return |
2019 socket.sendto(return_data, self.client_address) | 1989 socket.sendto(return_data, self.client_address) |
2020 | 1990 |
2021 | 1991 |
2022 class FileMultiplexer: | 1992 class ServerRunner(testserver_base.TestServerRunner): |
2023 def __init__(self, fd1, fd2) : | 1993 """TestServerRunner for the net test servers.""" |
2024 self.__fd1 = fd1 | 1994 |
2025 self.__fd2 = fd2 | 1995 def __init__(self): |
2026 | 1996 super(ServerRunner, self).__init__() |
2027 def __del__(self) : | 1997 self.__ocsp_server = None |
2028 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr: | 1998 |
2029 self.__fd1.close() | 1999 def __make_data_dir(self): |
2030 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr: | 2000 if self.options.data_dir: |
2031 self.__fd2.close() | 2001 if not os.path.isdir(self.options.data_dir): |
2032 | 2002 raise testserver_base.OptionError('specified data dir not found: ' + |
2033 def write(self, text) : | 2003 self.options.data_dir + ' exiting...') |
2034 self.__fd1.write(text) | 2004 my_data_dir = self.options.data_dir |
2035 self.__fd2.write(text) | 2005 else: |
2036 | 2006 # Create the default path to our data dir, relative to the exe dir. |
2037 def flush(self) : | 2007 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..", |
2038 self.__fd1.flush() | 2008 "test", "data") |
2039 self.__fd2.flush() | 2009 |
2040 | 2010 #TODO(ibrar): Must use Find* funtion defined in google\tools |
2041 def main(options, args): | 2011 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data") |
2042 logfile = open('testserver.log', 'w') | 2012 |
2043 sys.stderr = FileMultiplexer(sys.stderr, logfile) | 2013 return my_data_dir |
2044 if options.log_to_console: | 2014 |
2045 sys.stdout = FileMultiplexer(sys.stdout, logfile) | 2015 def create_server(self, server_data): |
2046 else: | 2016 port = self.options.port |
2047 sys.stdout = logfile | 2017 host = self.options.host |
2048 | 2018 |
2049 port = options.port | 2019 if self.options.server_type == SERVER_HTTP: |
2050 host = options.host | 2020 if self.options.https: |
2051 | 2021 pem_cert_and_key = None |
2052 server_data = {} | 2022 if self.options.cert_and_key_file: |
2053 server_data['host'] = host | 2023 if not os.path.isfile(self.options.cert_and_key_file): |
2054 | 2024 raise testserver_base.OptionError( |
2055 ocsp_server = None | 2025 'specified server cert file not found: ' + |
2056 | 2026 self.options.cert_and_key_file + ' exiting...') |
2057 if options.server_type == SERVER_HTTP: | 2027 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read() |
2058 if options.https: | 2028 else: |
2059 pem_cert_and_key = None | 2029 # generate a new certificate and run an OCSP server for it. |
2060 if options.cert_and_key_file: | 2030 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler) |
2061 if not os.path.isfile(options.cert_and_key_file): | 2031 print ('OCSP server started on %s:%d...' % |
2062 print ('specified server cert file not found: ' + | 2032 (host, self.__ocsp_server.server_port)) |
2063 options.cert_and_key_file + ' exiting...') | 2033 |
2064 return | 2034 ocsp_der = None |
2065 pem_cert_and_key = file(options.cert_and_key_file, 'r').read() | 2035 ocsp_state = None |
| 2036 |
| 2037 if self.options.ocsp == 'ok': |
| 2038 ocsp_state = minica.OCSP_STATE_GOOD |
| 2039 elif self.options.ocsp == 'revoked': |
| 2040 ocsp_state = minica.OCSP_STATE_REVOKED |
| 2041 elif self.options.ocsp == 'invalid': |
| 2042 ocsp_state = minica.OCSP_STATE_INVALID |
| 2043 elif self.options.ocsp == 'unauthorized': |
| 2044 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED |
| 2045 elif self.options.ocsp == 'unknown': |
| 2046 ocsp_state = minica.OCSP_STATE_UNKNOWN |
| 2047 else: |
| 2048 raise testserver_base.OptionError('unknown OCSP status: ' + |
| 2049 self.options.ocsp_status) |
| 2050 |
| 2051 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP( |
| 2052 subject = "127.0.0.1", |
| 2053 ocsp_url = ("http://%s:%d/ocsp" % |
| 2054 (host, self.__ocsp_server.server_port)), |
| 2055 ocsp_state = ocsp_state) |
| 2056 |
| 2057 self.__ocsp_server.ocsp_response = ocsp_der |
| 2058 |
| 2059 for ca_cert in self.options.ssl_client_ca: |
| 2060 if not os.path.isfile(ca_cert): |
| 2061 raise testserver_base.OptionError( |
| 2062 'specified trusted client CA file not found: ' + ca_cert + |
| 2063 ' exiting...') |
| 2064 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key, |
| 2065 self.options.ssl_client_auth, |
| 2066 self.options.ssl_client_ca, |
| 2067 self.options.ssl_bulk_cipher, |
| 2068 self.options.record_resume, |
| 2069 self.options.tls_intolerant) |
| 2070 print 'HTTPS server started on %s:%d...' % (host, server.server_port) |
2066 else: | 2071 else: |
2067 # generate a new certificate and run an OCSP server for it. | 2072 server = HTTPServer((host, port), TestPageHandler) |
2068 ocsp_server = OCSPServer((host, 0), OCSPHandler) | 2073 print 'HTTP server started on %s:%d...' % (host, server.server_port) |
2069 print ('OCSP server started on %s:%d...' % | 2074 |
2070 (host, ocsp_server.server_port)) | 2075 server.data_dir = self.__make_data_dir() |
2071 | 2076 server.file_root_url = self.options.file_root_url |
2072 ocsp_der = None | 2077 server_data['port'] = server.server_port |
2073 ocsp_state = None | 2078 server._device_management_handler = None |
2074 | 2079 server.policy_keys = self.options.policy_keys |
2075 if options.ocsp == 'ok': | 2080 server.policy_user = self.options.policy_user |
2076 ocsp_state = minica.OCSP_STATE_GOOD | 2081 server.gdata_auth_token = self.options.auth_token |
2077 elif options.ocsp == 'revoked': | 2082 elif self.options.server_type == SERVER_SYNC: |
2078 ocsp_state = minica.OCSP_STATE_REVOKED | 2083 xmpp_port = self.options.xmpp_port |
2079 elif options.ocsp == 'invalid': | 2084 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler) |
2080 ocsp_state = minica.OCSP_STATE_INVALID | 2085 print 'Sync HTTP server started on port %d...' % server.server_port |
2081 elif options.ocsp == 'unauthorized': | 2086 print 'Sync XMPP server started on port %d...' % server.xmpp_port |
2082 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED | 2087 server_data['port'] = server.server_port |
2083 elif options.ocsp == 'unknown': | 2088 server_data['xmpp_port'] = server.xmpp_port |
2084 ocsp_state = minica.OCSP_STATE_UNKNOWN | 2089 elif self.options.server_type == SERVER_TCP_ECHO: |
2085 else: | 2090 # Used for generating the key (randomly) that encodes the "echo request" |
2086 print 'unknown OCSP status: ' + options.ocsp_status | 2091 # message. |
2087 return | 2092 random.seed() |
2088 | 2093 server = TCPEchoServer((host, port), TCPEchoHandler) |
2089 (pem_cert_and_key, ocsp_der) = \ | 2094 print 'Echo TCP server started on port %d...' % server.server_port |
2090 minica.GenerateCertKeyAndOCSP( | 2095 server_data['port'] = server.server_port |
2091 subject = "127.0.0.1", | 2096 elif self.options.server_type == SERVER_UDP_ECHO: |
2092 ocsp_url = ("http://%s:%d/ocsp" % | 2097 # Used for generating the key (randomly) that encodes the "echo request" |
2093 (host, ocsp_server.server_port)), | 2098 # message. |
2094 ocsp_state = ocsp_state) | 2099 random.seed() |
2095 | 2100 server = UDPEchoServer((host, port), UDPEchoHandler) |
2096 ocsp_server.ocsp_response = ocsp_der | 2101 print 'Echo UDP server started on port %d...' % server.server_port |
2097 | 2102 server_data['port'] = server.server_port |
2098 for ca_cert in options.ssl_client_ca: | 2103 elif self.options.server_type == SERVER_FTP: |
2099 if not os.path.isfile(ca_cert): | 2104 my_data_dir = self.__make_data_dir() |
2100 print 'specified trusted client CA file not found: ' + ca_cert + \ | 2105 |
2101 ' exiting...' | 2106 # Instantiate a dummy authorizer for managing 'virtual' users |
2102 return | 2107 authorizer = pyftpdlib.ftpserver.DummyAuthorizer() |
2103 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key, | 2108 |
2104 options.ssl_client_auth, options.ssl_client_ca, | 2109 # Define a new user having full r/w permissions and a read-only |
2105 options.ssl_bulk_cipher, options.record_resume, | 2110 # anonymous user |
2106 options.tls_intolerant) | 2111 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw') |
2107 print 'HTTPS server started on %s:%d...' % (host, server.server_port) | 2112 |
| 2113 authorizer.add_anonymous(my_data_dir) |
| 2114 |
| 2115 # Instantiate FTP handler class |
| 2116 ftp_handler = pyftpdlib.ftpserver.FTPHandler |
| 2117 ftp_handler.authorizer = authorizer |
| 2118 |
| 2119 # Define a customized banner (string returned when client connects) |
| 2120 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." % |
| 2121 pyftpdlib.ftpserver.__ver__) |
| 2122 |
| 2123 # Instantiate FTP server class and listen to address:port |
| 2124 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler) |
| 2125 server_data['port'] = server.socket.getsockname()[1] |
| 2126 print 'FTP server started on port %d...' % server_data['port'] |
2108 else: | 2127 else: |
2109 server = HTTPServer((host, port), TestPageHandler) | 2128 raise testserver_base.OptionError('unknown server type' + |
2110 print 'HTTP server started on %s:%d...' % (host, server.server_port) | 2129 self.options.server_type) |
2111 | 2130 |
2112 server.data_dir = MakeDataDir() | 2131 return server |
2113 server.file_root_url = options.file_root_url | 2132 |
2114 server_data['port'] = server.server_port | 2133 def run_server(self): |
2115 server._device_management_handler = None | 2134 if self.__ocsp_server: |
2116 server.policy_keys = options.policy_keys | 2135 self.__ocsp_server.serve_forever_on_thread() |
2117 server.policy_user = options.policy_user | 2136 |
2118 server.gdata_auth_token = options.auth_token | 2137 testserver_base.TestServerRunner.run_server(self) |
2119 elif options.server_type == SERVER_SYNC: | 2138 |
2120 xmpp_port = options.xmpp_port | 2139 if self.__ocsp_server: |
2121 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler) | 2140 self.__ocsp_server.stop_serving() |
2122 print 'Sync HTTP server started on port %d...' % server.server_port | 2141 |
2123 print 'Sync XMPP server started on port %d...' % server.xmpp_port | 2142 def add_options(self): |
2124 server_data['port'] = server.server_port | 2143 testserver_base.TestServerRunner.add_options(self) |
2125 server_data['xmpp_port'] = server.xmpp_port | 2144 self.option_parser.add_option('-f', '--ftp', action='store_const', |
2126 elif options.server_type == SERVER_TCP_ECHO: | 2145 const=SERVER_FTP, default=SERVER_HTTP, |
2127 # Used for generating the key (randomly) that encodes the "echo request" | 2146 dest='server_type', |
2128 # message. | 2147 help='start up an FTP server.') |
2129 random.seed() | 2148 self.option_parser.add_option('--sync', action='store_const', |
2130 server = TCPEchoServer((host, port), TCPEchoHandler) | 2149 const=SERVER_SYNC, default=SERVER_HTTP, |
2131 print 'Echo TCP server started on port %d...' % server.server_port | 2150 dest='server_type', |
2132 server_data['port'] = server.server_port | 2151 help='start up a sync server.') |
2133 elif options.server_type == SERVER_UDP_ECHO: | 2152 self.option_parser.add_option('--tcp-echo', action='store_const', |
2134 # Used for generating the key (randomly) that encodes the "echo request" | 2153 const=SERVER_TCP_ECHO, default=SERVER_HTTP, |
2135 # message. | 2154 dest='server_type', |
2136 random.seed() | 2155 help='start up a tcp echo server.') |
2137 server = UDPEchoServer((host, port), UDPEchoHandler) | 2156 self.option_parser.add_option('--udp-echo', action='store_const', |
2138 print 'Echo UDP server started on port %d...' % server.server_port | 2157 const=SERVER_UDP_ECHO, default=SERVER_HTTP, |
2139 server_data['port'] = server.server_port | 2158 dest='server_type', |
2140 # means FTP Server | 2159 help='start up a udp echo server.') |
2141 else: | 2160 self.option_parser.add_option('--xmpp-port', default='0', type='int', |
2142 my_data_dir = MakeDataDir() | 2161 help='Port used by the XMPP server. If ' |
2143 | 2162 'unspecified, the XMPP server will listen on ' |
2144 # Instantiate a dummy authorizer for managing 'virtual' users | 2163 'an ephemeral port.') |
2145 authorizer = pyftpdlib.ftpserver.DummyAuthorizer() | 2164 self.option_parser.add_option('--data-dir', dest='data_dir', |
2146 | 2165 help='Directory from which to read the ' |
2147 # Define a new user having full r/w permissions and a read-only | 2166 'files.') |
2148 # anonymous user | 2167 self.option_parser.add_option('--https', action='store_true', |
2149 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw') | 2168 dest='https', help='Specify that https ' |
2150 | 2169 'should be used.') |
2151 authorizer.add_anonymous(my_data_dir) | 2170 self.option_parser.add_option('--cert-and-key-file', |
2152 | 2171 dest='cert_and_key_file', help='specify the ' |
2153 # Instantiate FTP handler class | 2172 'path to the file containing the certificate ' |
2154 ftp_handler = pyftpdlib.ftpserver.FTPHandler | 2173 'and private key for the server in PEM ' |
2155 ftp_handler.authorizer = authorizer | 2174 'format') |
2156 | 2175 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok', |
2157 # Define a customized banner (string returned when client connects) | 2176 help='The type of OCSP response generated ' |
2158 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." % | 2177 'for the automatically generated ' |
2159 pyftpdlib.ftpserver.__ver__) | 2178 'certificate. One of [ok,revoked,invalid]') |
2160 | 2179 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant', |
2161 # Instantiate FTP server class and listen to address:port | 2180 default='0', type='int', |
2162 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler) | 2181 help='If nonzero, certain TLS connections ' |
2163 server_data['port'] = server.socket.getsockname()[1] | 2182 'will be aborted in order to test version ' |
2164 print 'FTP server started on port %d...' % server_data['port'] | 2183 'fallback. 1 means all TLS versions will be ' |
2165 | 2184 'aborted. 2 means TLS 1.1 or higher will be ' |
2166 # Notify the parent that we've started. (BaseServer subclasses | 2185 'aborted. 3 means TLS 1.2 or higher will be ' |
2167 # bind their sockets on construction.) | 2186 'aborted.') |
2168 if options.startup_pipe is not None: | 2187 self.option_parser.add_option('--https-record-resume', |
2169 server_data_json = json.dumps(server_data) | 2188 dest='record_resume', const=True, |
2170 server_data_len = len(server_data_json) | 2189 default=False, action='store_const', |
2171 print 'sending server_data: %s (%d bytes)' % ( | 2190 help='Record resumption cache events rather ' |
2172 server_data_json, server_data_len) | 2191 'than resuming as normal. Allows the use of ' |
2173 if sys.platform == 'win32': | 2192 'the /ssl-session-cache request') |
2174 fd = msvcrt.open_osfhandle(options.startup_pipe, 0) | 2193 self.option_parser.add_option('--ssl-client-auth', action='store_true', |
2175 else: | 2194 help='Require SSL client auth on every ' |
2176 fd = options.startup_pipe | 2195 'connection.') |
2177 startup_pipe = os.fdopen(fd, "w") | 2196 self.option_parser.add_option('--ssl-client-ca', action='append', |
2178 # First write the data length as an unsigned 4-byte value. This | 2197 default=[], help='Specify that the client ' |
2179 # is _not_ using network byte ordering since the other end of the | 2198 'certificate request should include the CA ' |
2180 # pipe is on the same machine. | 2199 'named in the subject of the DER-encoded ' |
2181 startup_pipe.write(struct.pack('=L', server_data_len)) | 2200 'certificate contained in the specified ' |
2182 startup_pipe.write(server_data_json) | 2201 'file. This option may appear multiple ' |
2183 startup_pipe.close() | 2202 'times, indicating multiple CA names should ' |
2184 | 2203 'be sent in the request.') |
2185 if ocsp_server is not None: | 2204 self.option_parser.add_option('--ssl-bulk-cipher', action='append', |
2186 ocsp_server.serve_forever_on_thread() | 2205 help='Specify the bulk encryption ' |
2187 | 2206 'algorithm(s) that will be accepted by the ' |
2188 try: | 2207 'SSL server. Valid values are "aes256", ' |
2189 server.serve_forever() | 2208 '"aes128", "3des", "rc4". If omitted, all ' |
2190 except KeyboardInterrupt: | 2209 'algorithms will be used. This option may ' |
2191 print 'shutting down server' | 2210 'appear multiple times, indicating ' |
2192 if ocsp_server is not None: | 2211 'multiple algorithms should be enabled.'); |
2193 ocsp_server.stop_serving() | 2212 self.option_parser.add_option('--file-root-url', default='/files/', |
2194 server.stop = True | 2213 help='Specify a root URL for files served.') |
| 2214 self.option_parser.add_option('--policy-key', action='append', |
| 2215 dest='policy_keys', |
| 2216 help='Specify a path to a PEM-encoded ' |
| 2217 'private key to use for policy signing. May ' |
| 2218 'be specified multiple times in order to ' |
| 2219 'load multipe keys into the server. If the ' |
| 2220 'server has multiple keys, it will rotate ' |
| 2221 'through them in at each request a ' |
| 2222 'round-robin fashion. The server will ' |
| 2223 'generate a random key if none is specified ' |
| 2224 'on the command line.') |
| 2225 self.option_parser.add_option('--policy-user', |
| 2226 default='user@example.com', |
| 2227 dest='policy_user', |
| 2228 help='Specify the user name the server ' |
| 2229 'should report back to the client as the ' |
| 2230 'user owning the token used for making the ' |
| 2231 'policy request.') |
| 2232 self.option_parser.add_option('--auth-token', dest='auth_token', |
| 2233 help='Specify the auth token which should be ' |
| 2234 'used in the authorization header for GData.') |
| 2235 |
2195 | 2236 |
2196 if __name__ == '__main__': | 2237 if __name__ == '__main__': |
2197 option_parser = optparse.OptionParser() | 2238 sys.exit(ServerRunner().main()) |
2198 option_parser.add_option("-f", '--ftp', action='store_const', | |
2199 const=SERVER_FTP, default=SERVER_HTTP, | |
2200 dest='server_type', | |
2201 help='start up an FTP server.') | |
2202 option_parser.add_option('', '--sync', action='store_const', | |
2203 const=SERVER_SYNC, default=SERVER_HTTP, | |
2204 dest='server_type', | |
2205 help='start up a sync server.') | |
2206 option_parser.add_option('', '--tcp-echo', action='store_const', | |
2207 const=SERVER_TCP_ECHO, default=SERVER_HTTP, | |
2208 dest='server_type', | |
2209 help='start up a tcp echo server.') | |
2210 option_parser.add_option('', '--udp-echo', action='store_const', | |
2211 const=SERVER_UDP_ECHO, default=SERVER_HTTP, | |
2212 dest='server_type', | |
2213 help='start up a udp echo server.') | |
2214 option_parser.add_option('', '--log-to-console', action='store_const', | |
2215 const=True, default=False, | |
2216 dest='log_to_console', | |
2217 help='Enables or disables sys.stdout logging to ' | |
2218 'the console.') | |
2219 option_parser.add_option('', '--port', default='0', type='int', | |
2220 help='Port used by the server. If unspecified, the ' | |
2221 'server will listen on an ephemeral port.') | |
2222 option_parser.add_option('', '--xmpp-port', default='0', type='int', | |
2223 help='Port used by the XMPP server. If unspecified, ' | |
2224 'the XMPP server will listen on an ephemeral port.') | |
2225 option_parser.add_option('', '--data-dir', dest='data_dir', | |
2226 help='Directory from which to read the files.') | |
2227 option_parser.add_option('', '--https', action='store_true', dest='https', | |
2228 help='Specify that https should be used.') | |
2229 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file', | |
2230 help='specify the path to the file containing the ' | |
2231 'certificate and private key for the server in PEM ' | |
2232 'format') | |
2233 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok', | |
2234 help='The type of OCSP response generated for the ' | |
2235 'automatically generated certificate. One of ' | |
2236 '[ok,revoked,invalid]') | |
2237 option_parser.add_option('', '--tls-intolerant', dest='tls_intolerant', | |
2238 default='0', type='int', | |
2239 help='If nonzero, certain TLS connections will be' | |
2240 ' aborted in order to test version fallback. 1' | |
2241 ' means all TLS versions will be aborted. 2 means' | |
2242 ' TLS 1.1 or higher will be aborted. 3 means TLS' | |
2243 ' 1.2 or higher will be aborted.') | |
2244 option_parser.add_option('', '--https-record-resume', dest='record_resume', | |
2245 const=True, default=False, action='store_const', | |
2246 help='Record resumption cache events rather than' | |
2247 ' resuming as normal. Allows the use of the' | |
2248 ' /ssl-session-cache request') | |
2249 option_parser.add_option('', '--ssl-client-auth', action='store_true', | |
2250 help='Require SSL client auth on every connection.') | |
2251 option_parser.add_option('', '--ssl-client-ca', action='append', default=[], | |
2252 help='Specify that the client certificate request ' | |
2253 'should include the CA named in the subject of ' | |
2254 'the DER-encoded certificate contained in the ' | |
2255 'specified file. This option may appear multiple ' | |
2256 'times, indicating multiple CA names should be ' | |
2257 'sent in the request.') | |
2258 option_parser.add_option('', '--ssl-bulk-cipher', action='append', | |
2259 help='Specify the bulk encryption algorithm(s)' | |
2260 'that will be accepted by the SSL server. Valid ' | |
2261 'values are "aes256", "aes128", "3des", "rc4". If ' | |
2262 'omitted, all algorithms will be used. This ' | |
2263 'option may appear multiple times, indicating ' | |
2264 'multiple algorithms should be enabled.'); | |
2265 option_parser.add_option('', '--file-root-url', default='/files/', | |
2266 help='Specify a root URL for files served.') | |
2267 option_parser.add_option('', '--startup-pipe', type='int', | |
2268 dest='startup_pipe', | |
2269 help='File handle of pipe to parent process') | |
2270 option_parser.add_option('', '--policy-key', action='append', | |
2271 dest='policy_keys', | |
2272 help='Specify a path to a PEM-encoded private key ' | |
2273 'to use for policy signing. May be specified ' | |
2274 'multiple times in order to load multipe keys into ' | |
2275 'the server. If ther server has multiple keys, it ' | |
2276 'will rotate through them in at each request a ' | |
2277 'round-robin fashion. The server will generate a ' | |
2278 'random key if none is specified on the command ' | |
2279 'line.') | |
2280 option_parser.add_option('', '--policy-user', default='user@example.com', | |
2281 dest='policy_user', | |
2282 help='Specify the user name the server should ' | |
2283 'report back to the client as the user owning the ' | |
2284 'token used for making the policy request.') | |
2285 option_parser.add_option('', '--host', default='127.0.0.1', | |
2286 dest='host', | |
2287 help='Hostname or IP upon which the server will ' | |
2288 'listen. Client connections will also only be ' | |
2289 'allowed from this address.') | |
2290 option_parser.add_option('', '--auth-token', dest='auth_token', | |
2291 help='Specify the auth token which should be used' | |
2292 'in the authorization header for GData.') | |
2293 options, args = option_parser.parse_args() | |
2294 | |
2295 sys.exit(main(options, args)) | |
OLD | NEW |