Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1''' Stem helper stuff. ''' 

2from stem.control import Controller # type: ignore 

3from stem.connection import IncorrectSocketType # type: ignore 

4from stem.process import launch_tor_with_config # type: ignore 

5from stem.response import ControlMessage # type: ignore 

6from stem import SocketError, ProtocolError # type: ignore 

7import copy 

8import os 

9import logging 

10from typing import Optional, List, Union, Dict 

11from .tor_ctrl_msg import TorCtrlMsg 

12 

13 

14log = logging.getLogger(__name__) 

15 

16 

17# A dictionary of torrc options we need before launching tor and that do not 

18# depend on runtime configuration. Options only known at runtime (e.g. the 

19# DataDirectory) are added in launch(...) 

20TORRC_BASE: Dict[str, Union[str, List]] = { 

21 # SocksPort not needed 

22 'SocksPort': '0', 

23 # Easier than any other type of auth 

24 'CookieAuthentication': '1', 

25 # Unecessary, and avoids path bias warnings 

26 'UseEntryGuards': '0', 

27 # To make logs more useful 

28 'SafeLogging': '0', 

29 'LogTimeGranularity': '1', 

30 # We want measurers to be sending as much as possible, and for coordinators 

31 # it doesn't matter. KIST has poor single-socket performance, and while we 

32 # do measure with more than one socket, this won't hurt. 

33 # https://bugs.torproject.org/29427 

34 'Schedulers': 'Vanilla', 

35} 

36 

37 

38def _connect(loc: str): 

39 ''' Connect to a Tor client via its ControlSocket at *loc* 

40 

41 Returns None if unable to connect for any reason. Tor must not require 

42 password authentication; i.e. it requires cookie authentication or no 

43 authentication. 

44 ''' 

45 try: 

46 c = Controller.from_socket_file(path=loc) 

47 except (IncorrectSocketType, SocketError) as e: 

48 log.error('Error connecting to Tor control socket %s: %s', loc, e) 

49 return None 

50 try: 

51 c.authenticate() 

52 except (IncorrectSocketType, ProtocolError) as e: 

53 log.error('Error authenticating to Tor on %s: %s', loc, e) 

54 return None 

55 log.info('Connected to Tor at %s', loc) 

56 return c 

57 

58 

59def _update_torrc( 

60 torrc: Dict[str, Union[str, List]], 

61 key: str, val: str) -> None: 

62 ''' Update the given torrc to contain the given key/value pair. If the key 

63 already exists and the associated value is not a list, make it a list and 

64 append the new value to that list. If the key already exists and the value 

65 is already a list, append the new value to that list. Otherwise the key is 

66 not in the torrc, so add it and its associate value. ''' 

67 log.debug('Adding key="%s" val="%s" to the torrc', key, val) 

68 if key not in torrc: 

69 torrc.update({key: val}) 

70 return 

71 # Turn the existing `val` into `[val]` and append the new value to the 

72 # list. Or if it's already a list, simply append. 

73 existing = torrc[key] 

74 if isinstance(existing, str): 

75 torrc.update({key: [existing, val]}) 

76 return 

77 assert isinstance(existing, list) 

78 existing.append(val) 

79 return 

80 

81 

82def _parse_torrc_str( 

83 s: str, 

84 torrc: Dict[str, Union[str, List]] = None 

85 ) -> Dict[str, Union[str, List]]: 

86 ''' Take the given multi-line string `s` thats the contents of a torrc 

87 file. Optionally take an existing torrc as a starting point, otherwise 

88 start with an empty dict. Parse each line of `s` into the torrc dict, and 

89 return the final result. ''' 

90 if torrc is None: 

91 torrc = {} 

92 for line in s.split('\n'): 

93 # Remove leading/trailing whitespace 

94 line = line.strip() 

95 # Ignore blank lines and comment lines 

96 if not len(line) or line[0] == '#': 

97 continue 

98 kv = line.split(None, 1) 

99 # If this is ever not true, look at how sbws handles torrc options 

100 # without a value. For some reason for sbws I claimed that torrc 

101 # options can be just a key with no value, but right now I can't 

102 # picture why that would ever be the case. - Matt 

103 assert len(kv) > 1 

104 _update_torrc(torrc, kv[0], kv[1]) 

105 return torrc 

106 

107 

108def launch( 

109 tor_bin: str, tor_datadir: str, torrc_extra: str 

110 ) -> Optional[Controller]: 

111 ''' Launch and connect to Tor, returning the 

112 :class:`stem.control.Controller` on success, or ``None`` on failure. 

113 

114 :param tor_bin: How to execute tor. I.e. either "tor" or "./path/to/tor" 

115 :param tor_datadir: DataDirectory to use 

116 :param torrc_extra: Extra arbitrary lines to add to the torrc we use 

117 ''' 

118 ''' Launch and connect to Tor using the given tor binary (or path to tor 

119 binary) and using the given Tor DataDirectory. Returns an authenticated 

120 stem Controller object when successful. If any error occurs, this module 

121 logs about it and returns ``None``. ''' 

122 opj = os.path.join 

123 os.makedirs(tor_datadir, mode=0o700, exist_ok=True) 

124 # Get a copy of the starting torrc without any dynamic options 

125 torrc = copy.deepcopy(TORRC_BASE) 

126 # Save a copy of this as it will be used a few times in this function 

127 sock_path = os.path.abspath(opj(tor_datadir, 'control')) 

128 # Update the torrc with everything that depends on runtime config 

129 torrc.update({ 

130 'DataDirectory': tor_datadir, 

131 'PidFile': opj(tor_datadir, 'tor.pid'), 

132 'ControlSocket': sock_path, 

133 'Log': ['NOTICE file ' + opj(tor_datadir, 'notice.log')], 

134 }) 

135 torrc = _parse_torrc_str(torrc_extra, torrc) 

136 # log.debug(torrc) 

137 # Blocks while launching Tor 

138 try: 

139 launch_tor_with_config( 

140 torrc, tor_cmd=tor_bin, init_msg_handler=log.debug, 

141 take_ownership=True) 

142 except OSError as e: 

143 log.error('Problem launching Tor: %s', e) 

144 return None 

145 c = _connect(sock_path) 

146 if c is None: 

147 log.error('Unable to connect to Tor') 

148 return None 

149 assert isinstance(c, Controller) 

150 log.info( 

151 'Started and connected to Tor %s via %s', 

152 c.get_version(), 

153 sock_path) 

154 return c 

155 

156 

157def send_msg(c: Controller, m: TorCtrlMsg) -> ControlMessage: 

158 ''' Send a message to Tor on the given Controller, wait for the response, 

159 and return it. 

160 

161 This should only be used for messages for which stem doesn't already 

162 provide an interface. 

163 This is a thin wrapper. The reasons for it existing are: 

164 

165 - To avoid using :meth:`stem.control.BaseController.msg` directly. 

166 - Only allow ourselves to send specific messages. 

167 - Make it "impossible" to send malformed messages by only accepting 

168 :class:`TorCtrlMsg` subtypes and using static analyses 

169 ''' 

170 return c.msg(str(m))