1# Copyright 2015-2019, Damian Johnson and The Tor Project 2# See LICENSE for licensing information 3 4""" 5Interactive interpreter for interacting with Tor directly. This adds usability 6features such as tab completion, history, and IRC-style functions (like /help). 7""" 8 9import os 10import sys 11 12import stem 13import stem.connection 14import stem.prereq 15import stem.process 16import stem.util.conf 17import stem.util.system 18import stem.util.term 19 20from stem.util.term import Attr, Color, format 21 22__all__ = [ 23 'arguments', 24 'autocomplete', 25 'commands', 26 'help', 27] 28 29PROMPT = format('>>> ', Color.GREEN, Attr.BOLD, Attr.READLINE_ESCAPE) 30 31STANDARD_OUTPUT = (Color.BLUE, Attr.LINES) 32BOLD_OUTPUT = (Color.BLUE, Attr.BOLD, Attr.LINES) 33HEADER_OUTPUT = (Color.GREEN, Attr.LINES) 34HEADER_BOLD_OUTPUT = (Color.GREEN, Attr.BOLD, Attr.LINES) 35ERROR_OUTPUT = (Attr.BOLD, Color.RED, Attr.LINES) 36 37settings_path = os.path.join(os.path.dirname(__file__), 'settings.cfg') 38uses_settings = stem.util.conf.uses_settings('stem_interpreter', settings_path) 39 40 41@uses_settings 42def msg(message, config, **attr): 43 return config.get(message).format(**attr) 44 45 46def main(): 47 import readline 48 49 import stem.interpreter.arguments 50 import stem.interpreter.autocomplete 51 import stem.interpreter.commands 52 53 try: 54 args = stem.interpreter.arguments.parse(sys.argv[1:]) 55 except ValueError as exc: 56 print(exc) 57 sys.exit(1) 58 59 if args.print_help: 60 print(stem.interpreter.arguments.get_help()) 61 sys.exit() 62 63 if args.disable_color or not sys.stdout.isatty(): 64 global PROMPT 65 stem.util.term.DISABLE_COLOR_SUPPORT = True 66 PROMPT = '>>> ' 67 68 # If the user isn't connecting to something in particular then offer to start 69 # tor if it isn't running. 70 71 if not (args.user_provided_port or args.user_provided_socket): 72 is_tor_running = stem.util.system.is_running('tor') or stem.util.system.is_running('tor.real') 73 74 if not is_tor_running: 75 if args.tor_path == 'tor' and not stem.util.system.is_available('tor'): 76 print(format(msg('msg.tor_unavailable'), *ERROR_OUTPUT)) 77 sys.exit(1) 78 else: 79 if not args.run_cmd and not args.run_path: 80 print(format(msg('msg.starting_tor'), *HEADER_OUTPUT)) 81 82 control_port = '9051' if args.control_port == 'default' else str(args.control_port) 83 84 try: 85 stem.process.launch_tor_with_config( 86 config = { 87 'SocksPort': '0', 88 'ControlPort': control_port, 89 'CookieAuthentication': '1', 90 'ExitPolicy': 'reject *:*', 91 }, 92 tor_cmd = args.tor_path, 93 completion_percent = 5, 94 take_ownership = True, 95 ) 96 except OSError as exc: 97 print(format(msg('msg.unable_to_start_tor', error = exc), *ERROR_OUTPUT)) 98 sys.exit(1) 99 100 control_port = (args.control_address, args.control_port) 101 control_socket = args.control_socket 102 103 # If the user explicitely specified an endpoint then just try to connect to 104 # that. 105 106 if args.user_provided_socket and not args.user_provided_port: 107 control_port = None 108 elif args.user_provided_port and not args.user_provided_socket: 109 control_socket = None 110 111 controller = stem.connection.connect( 112 control_port = control_port, 113 control_socket = control_socket, 114 password_prompt = True, 115 ) 116 117 if controller is None: 118 sys.exit(1) 119 120 with controller: 121 interpreter = stem.interpreter.commands.ControlInterpreter(controller) 122 showed_close_confirmation = False 123 124 if args.run_cmd: 125 if args.run_cmd.upper().startswith('SETEVENTS '): 126 # TODO: we can use a lambda here when dropping python 2.x support, but 127 # until then print's status as a keyword prevents it from being used in 128 # lambdas 129 130 def handle_event(event_message): 131 print(format(str(event_message), *STANDARD_OUTPUT)) 132 133 controller._handle_event = handle_event 134 135 if sys.stdout.isatty(): 136 events = args.run_cmd.upper().split(' ', 1)[1] 137 print(format('Listening to %s events. Press any key to quit.\n' % events, *HEADER_BOLD_OUTPUT)) 138 139 controller.msg(args.run_cmd) 140 141 try: 142 raw_input() 143 except (KeyboardInterrupt, stem.SocketClosed): 144 pass 145 else: 146 interpreter.run_command(args.run_cmd, print_response = True) 147 elif args.run_path: 148 try: 149 for line in open(args.run_path).readlines(): 150 interpreter.run_command(line.strip(), print_response = True) 151 except IOError as exc: 152 print(format(msg('msg.unable_to_read_file', path = args.run_path, error = exc), *ERROR_OUTPUT)) 153 sys.exit(1) 154 155 else: 156 autocompleter = stem.interpreter.autocomplete.Autocompleter(controller) 157 readline.parse_and_bind('tab: complete') 158 readline.set_completer(autocompleter.complete) 159 readline.set_completer_delims('\n') 160 161 for line in msg('msg.startup_banner').splitlines(): 162 line_format = HEADER_BOLD_OUTPUT if line.startswith(' ') else HEADER_OUTPUT 163 print(format(line, *line_format)) 164 165 print('') 166 167 while True: 168 try: 169 prompt = '... ' if interpreter.is_multiline_context else PROMPT 170 user_input = input(prompt) if stem.prereq.is_python_3() else raw_input(prompt) 171 interpreter.run_command(user_input, print_response = True) 172 except stem.SocketClosed: 173 if showed_close_confirmation: 174 print(format('Unable to run tor commands. The control connection has been closed.', *ERROR_OUTPUT)) 175 else: 176 prompt = format("Tor's control port has closed. Do you want to continue this interpreter? (y/n) ", *HEADER_BOLD_OUTPUT) 177 user_input = input(prompt) if stem.prereq.is_python_3() else raw_input(prompt) 178 print('') # blank line 179 180 if user_input.lower() in ('y', 'yes'): 181 showed_close_confirmation = True 182 else: 183 break 184 except (KeyboardInterrupt, EOFError, stem.SocketClosed): 185 print('') # move cursor to the following line 186 break 187