tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

standalone.py (18871B)


      1 #!/usr/bin/env python
      2 #
      3 # Copyright 2012, Google Inc.
      4 # All rights reserved.
      5 #
      6 # Redistribution and use in source and binary forms, with or without
      7 # modification, are permitted provided that the following conditions are
      8 # met:
      9 #
     10 #     * Redistributions of source code must retain the above copyright
     11 # notice, this list of conditions and the following disclaimer.
     12 #     * Redistributions in binary form must reproduce the above
     13 # copyright notice, this list of conditions and the following disclaimer
     14 # in the documentation and/or other materials provided with the
     15 # distribution.
     16 #     * Neither the name of Google Inc. nor the names of its
     17 # contributors may be used to endorse or promote products derived from
     18 # this software without specific prior written permission.
     19 #
     20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     24 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     25 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     26 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     27 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     28 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     30 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31 """Standalone WebSocket server.
     32 
     33 Use this file to launch pywebsocket as a standalone server.
     34 
     35 
     36 BASIC USAGE
     37 ===========
     38 
     39 Go to the src directory and run
     40 
     41  $ python mod_pywebsocket/standalone.py [-p <ws_port>]
     42                                         [-w <websock_handlers>]
     43                                         [-d <document_root>]
     44 
     45 <ws_port> is the port number to use for ws:// connection.
     46 
     47 <document_root> is the path to the root directory of HTML files.
     48 
     49 <websock_handlers> is the path to the root directory of WebSocket handlers.
     50 If not specified, <document_root> will be used. See __init__.py (or
     51 run $ pydoc mod_pywebsocket) for how to write WebSocket handlers.
     52 
     53 For more detail and other options, run
     54 
     55  $ python mod_pywebsocket/standalone.py --help
     56 
     57 or see _build_option_parser method below.
     58 
     59 For trouble shooting, adding "--log_level debug" might help you.
     60 
     61 
     62 TRY DEMO
     63 ========
     64 
     65 Go to the src directory and run standalone.py with -d option to set the
     66 document root to the directory containing example HTMLs and handlers like this:
     67 
     68  $ cd src
     69  $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example
     70 
     71 to launch pywebsocket with the sample handler and html on port 80. Open
     72 http://localhost/console.html, click the connect button, type something into
     73 the text box next to the send button and click the send button. If everything
     74 is working, you'll see the message you typed echoed by the server.
     75 
     76 
     77 USING TLS
     78 =========
     79 
     80 To run the standalone server with TLS support, run it with -t, -k, and -c
     81 options. When TLS is enabled, the standalone server accepts only TLS connection.
     82 
     83 Note that when ssl module is used and the key/cert location is incorrect,
     84 TLS connection silently fails while pyOpenSSL fails on startup.
     85 
     86 Example:
     87 
     88  $ PYTHONPATH=. python mod_pywebsocket/standalone.py \
     89        -d example \
     90        -p 10443 \
     91        -t \
     92        -c ../test/cert/cert.pem \
     93        -k ../test/cert/key.pem \
     94 
     95 Note that when passing a relative path to -c and -k option, it will be resolved
     96 using the document root directory as the base.
     97 
     98 
     99 USING CLIENT AUTHENTICATION
    100 ===========================
    101 
    102 To run the standalone server with TLS client authentication support, run it with
    103 --tls-client-auth and --tls-client-ca options in addition to ones required for
    104 TLS support.
    105 
    106 Example:
    107 
    108  $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example -p 10443 -t \
    109        -c ../test/cert/cert.pem -k ../test/cert/key.pem \
    110        --tls-client-auth \
    111        --tls-client-ca=../test/cert/cacert.pem
    112 
    113 Note that when passing a relative path to --tls-client-ca option, it will be
    114 resolved using the document root directory as the base.
    115 
    116 
    117 CONFIGURATION FILE
    118 ==================
    119 
    120 You can also write a configuration file and use it by specifying the path to
    121 the configuration file by --config option. Please write a configuration file
    122 following the documentation of the Python ConfigParser library. Name of each
    123 entry must be the long version argument name. E.g. to set log level to debug,
    124 add the following line:
    125 
    126 log_level=debug
    127 
    128 For options which doesn't take value, please add some fake value. E.g. for
    129 --tls option, add the following line:
    130 
    131 tls=True
    132 
    133 Note that tls will be enabled even if you write tls=False as the value part is
    134 fake.
    135 
    136 When both a command line argument and a configuration file entry are set for
    137 the same configuration item, the command line value will override one in the
    138 configuration file.
    139 
    140 
    141 THREADING
    142 =========
    143 
    144 This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
    145 used for each request.
    146 
    147 
    148 SECURITY WARNING
    149 ================
    150 
    151 This uses CGIHTTPServer and CGIHTTPServer is not secure.
    152 It may execute arbitrary Python code or external programs. It should not be
    153 used outside a firewall.
    154 """
    155 
    156 from __future__ import absolute_import
    157 from six.moves import configparser
    158 import base64
    159 import logging
    160 import argparse
    161 import os
    162 import six
    163 import sys
    164 import traceback
    165 
    166 from mod_pywebsocket import common
    167 from mod_pywebsocket import util
    168 from mod_pywebsocket import server_util
    169 from mod_pywebsocket.websocket_server import WebSocketServer
    170 
    171 _DEFAULT_LOG_MAX_BYTES = 1024 * 256
    172 _DEFAULT_LOG_BACKUP_COUNT = 5
    173 
    174 _DEFAULT_REQUEST_QUEUE_SIZE = 128
    175 
    176 
    177 def _build_option_parser():
    178    parser = argparse.ArgumentParser()
    179 
    180    parser.add_argument(
    181        '--config',
    182        dest='config_file',
    183        type=six.text_type,
    184        default=None,
    185        help=('Path to configuration file. See the file comment '
    186              'at the top of this file for the configuration '
    187              'file format'))
    188    parser.add_argument('-H',
    189                        '--server-host',
    190                        '--server_host',
    191                        dest='server_host',
    192                        default='',
    193                        help='server hostname to listen to')
    194    parser.add_argument('-V',
    195                        '--validation-host',
    196                        '--validation_host',
    197                        dest='validation_host',
    198                        default=None,
    199                        help='server hostname to validate in absolute path.')
    200    parser.add_argument('-p',
    201                        '--port',
    202                        dest='port',
    203                        type=int,
    204                        default=common.DEFAULT_WEB_SOCKET_PORT,
    205                        help='port to listen to')
    206    parser.add_argument('-P',
    207                        '--validation-port',
    208                        '--validation_port',
    209                        dest='validation_port',
    210                        type=int,
    211                        default=None,
    212                        help='server port to validate in absolute path.')
    213    parser.add_argument(
    214        '-w',
    215        '--websock-handlers',
    216        '--websock_handlers',
    217        dest='websock_handlers',
    218        default='.',
    219        help=('The root directory of WebSocket handler files. '
    220              'If the path is relative, --document-root is used '
    221              'as the base.'))
    222    parser.add_argument('-m',
    223                        '--websock-handlers-map-file',
    224                        '--websock_handlers_map_file',
    225                        dest='websock_handlers_map_file',
    226                        default=None,
    227                        help=('WebSocket handlers map file. '
    228                              'Each line consists of alias_resource_path and '
    229                              'existing_resource_path, separated by spaces.'))
    230    parser.add_argument('-s',
    231                        '--scan-dir',
    232                        '--scan_dir',
    233                        dest='scan_dir',
    234                        default=None,
    235                        help=('Must be a directory under --websock-handlers. '
    236                              'Only handlers under this directory are scanned '
    237                              'and registered to the server. '
    238                              'Useful for saving scan time when the handler '
    239                              'root directory contains lots of files that are '
    240                              'not handler file or are handler files but you '
    241                              'don\'t want them to be registered. '))
    242    parser.add_argument(
    243        '--allow-handlers-outside-root-dir',
    244        '--allow_handlers_outside_root_dir',
    245        dest='allow_handlers_outside_root_dir',
    246        action='store_true',
    247        default=False,
    248        help=('Scans WebSocket handlers even if their canonical '
    249              'path is not under --websock-handlers.'))
    250    parser.add_argument('-d',
    251                        '--document-root',
    252                        '--document_root',
    253                        dest='document_root',
    254                        default='.',
    255                        help='Document root directory.')
    256    parser.add_argument('-x',
    257                        '--cgi-paths',
    258                        '--cgi_paths',
    259                        dest='cgi_paths',
    260                        default=None,
    261                        help=('CGI paths relative to document_root.'
    262                              'Comma-separated. (e.g -x /cgi,/htbin) '
    263                              'Files under document_root/cgi_path are handled '
    264                              'as CGI programs. Must be executable.'))
    265    parser.add_argument('-t',
    266                        '--tls',
    267                        dest='use_tls',
    268                        action='store_true',
    269                        default=False,
    270                        help='use TLS (wss://)')
    271    parser.add_argument('-k',
    272                        '--private-key',
    273                        '--private_key',
    274                        dest='private_key',
    275                        default='',
    276                        help='TLS private key file.')
    277    parser.add_argument('-c',
    278                        '--certificate',
    279                        dest='certificate',
    280                        default='',
    281                        help='TLS certificate file.')
    282    parser.add_argument('--tls-client-auth',
    283                        dest='tls_client_auth',
    284                        action='store_true',
    285                        default=False,
    286                        help='Requests TLS client auth on every connection.')
    287    parser.add_argument('--tls-client-cert-optional',
    288                        dest='tls_client_cert_optional',
    289                        action='store_true',
    290                        default=False,
    291                        help=('Makes client certificate optional even though '
    292                              'TLS client auth is enabled.'))
    293    parser.add_argument('--tls-client-ca',
    294                        dest='tls_client_ca',
    295                        default='',
    296                        help=('Specifies a pem file which contains a set of '
    297                              'concatenated CA certificates which are used to '
    298                              'validate certificates passed from clients'))
    299    parser.add_argument('--basic-auth',
    300                        dest='use_basic_auth',
    301                        action='store_true',
    302                        default=False,
    303                        help='Requires Basic authentication.')
    304    parser.add_argument(
    305        '--basic-auth-credential',
    306        dest='basic_auth_credential',
    307        default='test:test',
    308        help='Specifies the credential of basic authentication '
    309        'by username:password pair (e.g. test:test).')
    310    parser.add_argument('-l',
    311                        '--log-file',
    312                        '--log_file',
    313                        dest='log_file',
    314                        default='',
    315                        help='Log file.')
    316    # Custom log level:
    317    # - FINE: Prints status of each frame processing step
    318    parser.add_argument('--log-level',
    319                        '--log_level',
    320                        type=six.text_type,
    321                        dest='log_level',
    322                        default='warn',
    323                        choices=[
    324                            'fine', 'debug', 'info', 'warning', 'warn',
    325                            'error', 'critical'
    326                        ],
    327                        help='Log level.')
    328    parser.add_argument(
    329        '--deflate-log-level',
    330        '--deflate_log_level',
    331        type=six.text_type,
    332        dest='deflate_log_level',
    333        default='warn',
    334        choices=['debug', 'info', 'warning', 'warn', 'error', 'critical'],
    335        help='Log level for _Deflater and _Inflater.')
    336    parser.add_argument('--thread-monitor-interval-in-sec',
    337                        '--thread_monitor_interval_in_sec',
    338                        dest='thread_monitor_interval_in_sec',
    339                        type=int,
    340                        default=-1,
    341                        help=('If positive integer is specified, run a thread '
    342                              'monitor to show the status of server threads '
    343                              'periodically in the specified inteval in '
    344                              'second. If non-positive integer is specified, '
    345                              'disable the thread monitor.'))
    346    parser.add_argument('--log-max',
    347                        '--log_max',
    348                        dest='log_max',
    349                        type=int,
    350                        default=_DEFAULT_LOG_MAX_BYTES,
    351                        help='Log maximum bytes')
    352    parser.add_argument('--log-count',
    353                        '--log_count',
    354                        dest='log_count',
    355                        type=int,
    356                        default=_DEFAULT_LOG_BACKUP_COUNT,
    357                        help='Log backup count')
    358    parser.add_argument('-q',
    359                        '--queue',
    360                        dest='request_queue_size',
    361                        type=int,
    362                        default=_DEFAULT_REQUEST_QUEUE_SIZE,
    363                        help='request queue size')
    364    parser.add_argument(
    365        '--handler-encoding',
    366        '--handler_encoding',
    367        dest='handler_encoding',
    368        type=six.text_type,
    369        default=None,
    370        help=('Text encoding used for loading handlers. '
    371              'By default, the encoding from the locale is used when '
    372              'reading handler files, but this option can override it. '
    373              'Any encoding supported by the codecs module may be used.'))
    374 
    375    return parser
    376 
    377 
    378 def _parse_args_and_config(args):
    379    parser = _build_option_parser()
    380 
    381    # First, parse options without configuration file.
    382    temporary_options, temporary_args = parser.parse_known_args(args=args)
    383    if temporary_args:
    384        logging.critical('Unrecognized positional arguments: %r',
    385                         temporary_args)
    386        sys.exit(1)
    387 
    388    if temporary_options.config_file:
    389        try:
    390            config_fp = open(temporary_options.config_file, 'r')
    391        except IOError as e:
    392            logging.critical('Failed to open configuration file %r: %r',
    393                             temporary_options.config_file, e)
    394            sys.exit(1)
    395 
    396        config_parser = configparser.SafeConfigParser()
    397        config_parser.readfp(config_fp)
    398        config_fp.close()
    399 
    400        args_from_config = []
    401        for name, value in config_parser.items('pywebsocket'):
    402            args_from_config.append('--' + name)
    403            args_from_config.append(value)
    404        if args is None:
    405            args = args_from_config
    406        else:
    407            args = args_from_config + args
    408        return parser.parse_known_args(args=args)
    409    else:
    410        return temporary_options, temporary_args
    411 
    412 
    413 def _main(args=None):
    414    """You can call this function from your own program, but please note that
    415    this function has some side-effects that might affect your program. For
    416    example, it changes the current directory.
    417    """
    418 
    419    options, args = _parse_args_and_config(args=args)
    420 
    421    os.chdir(options.document_root)
    422 
    423    server_util.configure_logging(options)
    424 
    425    # TODO(tyoshino): Clean up initialization of CGI related values. Move some
    426    # of code here to WebSocketRequestHandler class if it's better.
    427    options.cgi_directories = []
    428    options.is_executable_method = None
    429    if options.cgi_paths:
    430        options.cgi_directories = options.cgi_paths.split(',')
    431        if sys.platform in ('cygwin', 'win32'):
    432            cygwin_path = None
    433            # For Win32 Python, it is expected that CYGWIN_PATH
    434            # is set to a directory of cygwin binaries.
    435            # For example, websocket_server.py in Chromium sets CYGWIN_PATH to
    436            # full path of third_party/cygwin/bin.
    437            if 'CYGWIN_PATH' in os.environ:
    438                cygwin_path = os.environ['CYGWIN_PATH']
    439 
    440            def __check_script(scriptpath):
    441                return util.get_script_interp(scriptpath, cygwin_path)
    442 
    443            options.is_executable_method = __check_script
    444 
    445    if options.use_tls:
    446        logging.debug('Using ssl module')
    447 
    448        if not options.private_key or not options.certificate:
    449            logging.critical(
    450                'To use TLS, specify private_key and certificate.')
    451            sys.exit(1)
    452 
    453        if (options.tls_client_cert_optional and not options.tls_client_auth):
    454            logging.critical('Client authentication must be enabled to '
    455                             'specify tls_client_cert_optional')
    456            sys.exit(1)
    457    else:
    458        if options.tls_client_auth:
    459            logging.critical('TLS must be enabled for client authentication.')
    460            sys.exit(1)
    461 
    462        if options.tls_client_cert_optional:
    463            logging.critical('TLS must be enabled for client authentication.')
    464            sys.exit(1)
    465 
    466    if not options.scan_dir:
    467        options.scan_dir = options.websock_handlers
    468 
    469    if options.use_basic_auth:
    470        options.basic_auth_credential = 'Basic ' + base64.b64encode(
    471            options.basic_auth_credential.encode('UTF-8')).decode()
    472 
    473    try:
    474        if options.thread_monitor_interval_in_sec > 0:
    475            # Run a thread monitor to show the status of server threads for
    476            # debugging.
    477            server_util.ThreadMonitor(
    478                options.thread_monitor_interval_in_sec).start()
    479 
    480        server = WebSocketServer(options)
    481        server.serve_forever()
    482    except Exception as e:
    483        logging.critical('mod_pywebsocket: %s' % e)
    484        logging.critical('mod_pywebsocket: %s' % traceback.format_exc())
    485        sys.exit(1)
    486 
    487 
    488 if __name__ == '__main__':
    489    _main(sys.argv[1:])
    490 
    491 # vi:sts=4 sw=4 et