This file is indexed.

/usr/share/arm/cli/torrcPanel.py is in tor-arm 1.4.5.0-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
"""
Panel displaying the torrc or armrc with the validation done against it.
"""

import math
import curses
import threading

import popups

from util import conf, enum, panel, torConfig, torTools, uiTools

DEFAULT_CONFIG = {"features.config.file.showScrollbars": True,
                  "features.config.file.maxLinesPerEntry": 8}

# TODO: The armrc use case is incomplete. There should be equivilant reloading
# and validation capabilities to the torrc.
Config = enum.Enum("TORRC", "ARMRC") # configuration file types that can be displayed

class TorrcPanel(panel.Panel):
  """
  Renders the current torrc or armrc with syntax highlighting in a scrollable
  area.
  """
  
  def __init__(self, stdscr, configType, config=None):
    panel.Panel.__init__(self, stdscr, "torrc", 0)
    
    self._config = dict(DEFAULT_CONFIG)
    if config:
      config.update(self._config, {"features.config.file.maxLinesPerEntry": 1})
    
    self.valsLock = threading.RLock()
    self.configType = configType
    self.scroll = 0
    self.showLineNum = True     # shows left aligned line numbers
    self.stripComments = False  # drops comments and extra whitespace
    
    # height of the content when last rendered (the cached value is invalid if
    # _lastContentHeightArgs is None or differs from the current dimensions)
    self._lastContentHeight = 1
    self._lastContentHeightArgs = None
    
    # listens for tor reload (sighup) events
    conn = torTools.getConn()
    conn.addStatusListener(self.resetListener)
    if conn.isAlive(): self.resetListener(conn, torTools.State.INIT)
  
  def resetListener(self, conn, eventType):
    """
    Reloads and displays the torrc on tor reload (sighup) events.
    
    Arguments:
      conn      - tor controller
      eventType - type of event detected
    """
    
    if eventType == torTools.State.INIT:
      # loads the torrc and provides warnings in case of validation errors
      try:
        loadedTorrc = torConfig.getTorrc()
        loadedTorrc.load(True)
        loadedTorrc.logValidationIssues()
        self.redraw(True)
      except: pass
    elif eventType == torTools.State.RESET:
      try:
        torConfig.getTorrc().load(True)
        self.redraw(True)
      except: pass
  
  def setCommentsVisible(self, isVisible):
    """
    Sets if comments and blank lines are shown or stripped.
    
    Arguments:
      isVisible - displayed comments and blank lines if true, strips otherwise
    """
    
    self.stripComments = not isVisible
    self._lastContentHeightArgs = None
    self.redraw(True)
  
  def setLineNumberVisible(self, isVisible):
    """
    Sets if line numbers are shown or hidden.
    
    Arguments:
      isVisible - displays line numbers if true, hides otherwise
    """
    
    self.showLineNum = isVisible
    self._lastContentHeightArgs = None
    self.redraw(True)
  
  def reloadTorrc(self):
    """
    Reloads the torrc, displaying an indicator of success or failure.
    """
    
    try:
      torConfig.getTorrc().load()
      self._lastContentHeightArgs = None
      self.redraw(True)
      resultMsg = "torrc reloaded"
    except IOError:
      resultMsg = "failed to reload torrc"
    
    self._lastContentHeightArgs = None
    self.redraw(True)
    popups.showMsg(resultMsg, 1)
  
  def handleKey(self, key):
    self.valsLock.acquire()
    isKeystrokeConsumed = True
    if uiTools.isScrollKey(key):
      pageHeight = self.getPreferredSize()[0] - 1
      newScroll = uiTools.getScrollPosition(key, self.scroll, pageHeight, self._lastContentHeight)
      
      if self.scroll != newScroll:
        self.scroll = newScroll
        self.redraw(True)
    elif key == ord('n') or key == ord('N'):
      self.setLineNumberVisible(not self.showLineNum)
    elif key == ord('s') or key == ord('S'):
      self.setCommentsVisible(self.stripComments)
    elif key == ord('r') or key == ord('R'):
      self.reloadTorrc()
    else: isKeystrokeConsumed = False
    
    self.valsLock.release()
    return isKeystrokeConsumed
  
  def setVisible(self, isVisible):
    if not isVisible:
      self._lastContentHeightArgs = None # redraws when next displayed
    
    panel.Panel.setVisible(self, isVisible)
  
  def getHelp(self):
    options = []
    options.append(("up arrow", "scroll up a line", None))
    options.append(("down arrow", "scroll down a line", None))
    options.append(("page up", "scroll up a page", None))
    options.append(("page down", "scroll down a page", None))
    options.append(("s", "comment stripping", "on" if self.stripComments else "off"))
    options.append(("n", "line numbering", "on" if self.showLineNum else "off"))
    options.append(("r", "reload torrc", None))
    options.append(("x", "reset tor (issue sighup)", None))
    return options
  
  def draw(self, width, height):
    self.valsLock.acquire()
    
    # If true, we assume that the cached value in self._lastContentHeight is
    # still accurate, and stop drawing when there's nothing more to display.
    # Otherwise the self._lastContentHeight is suspect, and we'll process all
    # the content to check if it's right (and redraw again with the corrected
    # height if not).
    trustLastContentHeight = self._lastContentHeightArgs == (width, height)
    
    # restricts scroll location to valid bounds
    self.scroll = max(0, min(self.scroll, self._lastContentHeight - height + 1))
    
    renderedContents, corrections, confLocation = None, {}, None
    if self.configType == Config.TORRC:
      loadedTorrc = torConfig.getTorrc()
      loadedTorrc.getLock().acquire()
      confLocation = loadedTorrc.getConfigLocation()
      
      if not loadedTorrc.isLoaded():
        renderedContents = ["### Unable to load the torrc ###"]
      else:
        renderedContents = loadedTorrc.getDisplayContents(self.stripComments)
        
        # constructs a mapping of line numbers to the issue on it
        corrections = dict((lineNum, (issue, msg)) for lineNum, issue, msg in loadedTorrc.getCorrections())
      
      loadedTorrc.getLock().release()
    else:
      loadedArmrc = conf.getConfig("arm")
      confLocation = loadedArmrc.path
      renderedContents = list(loadedArmrc.rawContents)
    
    # offset to make room for the line numbers
    lineNumOffset = 0
    if self.showLineNum:
      if len(renderedContents) == 0: lineNumOffset = 2
      else: lineNumOffset = int(math.log10(len(renderedContents))) + 2
    
    # draws left-hand scroll bar if content's longer than the height
    scrollOffset = 0
    if self._config["features.config.file.showScrollbars"] and self._lastContentHeight > height - 1:
      scrollOffset = 3
      self.addScrollBar(self.scroll, self.scroll + height - 1, self._lastContentHeight, 1)
    
    displayLine = -self.scroll + 1 # line we're drawing on
    
    # draws the top label
    if self.isTitleVisible():
      sourceLabel = "Tor" if self.configType == Config.TORRC else "Arm"
      locationLabel = " (%s)" % confLocation if confLocation else ""
      self.addstr(0, 0, "%s Configuration File%s:" % (sourceLabel, locationLabel), curses.A_STANDOUT)
    
    isMultiline = False # true if we're in the middle of a multiline torrc entry
    for lineNumber in range(0, len(renderedContents)):
      lineText = renderedContents[lineNumber]
      lineText = lineText.rstrip() # remove ending whitespace
      
      # blank lines are hidden when stripping comments
      if self.stripComments and not lineText: continue
      
      # splits the line into its component (msg, format) tuples
      lineComp = {"option": ["", curses.A_BOLD | uiTools.getColor("green")],
                  "argument": ["", curses.A_BOLD | uiTools.getColor("cyan")],
                  "correction": ["", curses.A_BOLD | uiTools.getColor("cyan")],
                  "comment": ["", uiTools.getColor("white")]}
      
      # parses the comment
      commentIndex = lineText.find("#")
      if commentIndex != -1:
        lineComp["comment"][0] = lineText[commentIndex:]
        lineText = lineText[:commentIndex]
      
      # splits the option and argument, preserving any whitespace around them
      strippedLine = lineText.strip()
      optionIndex = strippedLine.find(" ")
      if isMultiline:
        # part of a multiline entry started on a previous line so everything
        # is part of the argument
        lineComp["argument"][0] = lineText
      elif optionIndex == -1:
        # no argument provided
        lineComp["option"][0] = lineText
      else:
        optionText = strippedLine[:optionIndex]
        optionEnd = lineText.find(optionText) + len(optionText)
        lineComp["option"][0] = lineText[:optionEnd]
        lineComp["argument"][0] = lineText[optionEnd:]
      
      # flags following lines as belonging to this multiline entry if it ends
      # with a slash
      if strippedLine: isMultiline = strippedLine.endswith("\\")
      
      # gets the correction
      if lineNumber in corrections:
        lineIssue, lineIssueMsg = corrections[lineNumber]
        
        if lineIssue in (torConfig.ValidationError.DUPLICATE, torConfig.ValidationError.IS_DEFAULT):
          lineComp["option"][1] = curses.A_BOLD | uiTools.getColor("blue")
          lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("blue")
        elif lineIssue == torConfig.ValidationError.MISMATCH:
          lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("red")
          lineComp["correction"][0] = " (%s)" % lineIssueMsg
        else:
          # For some types of configs the correction field is simply used to
          # provide extra data (for instance, the type for tor state fields).
          lineComp["correction"][0] = " (%s)" % lineIssueMsg
          lineComp["correction"][1] = curses.A_BOLD | uiTools.getColor("magenta")
      
      # draws the line number
      if self.showLineNum and displayLine < height and displayLine >= 1:
        lineNumStr = ("%%%ii" % (lineNumOffset - 1)) % (lineNumber + 1)
        self.addstr(displayLine, scrollOffset, lineNumStr, curses.A_BOLD | uiTools.getColor("yellow"))
      
      # draws the rest of the components with line wrap
      cursorLoc, lineOffset = lineNumOffset + scrollOffset, 0
      maxLinesPerEntry = self._config["features.config.file.maxLinesPerEntry"]
      displayQueue = [lineComp[entry] for entry in ("option", "argument", "correction", "comment")]
      
      while displayQueue:
        msg, format = displayQueue.pop(0)
        
        maxMsgSize, includeBreak = width - cursorLoc, False
        if len(msg) >= maxMsgSize:
          # message is too long - break it up
          if lineOffset == maxLinesPerEntry - 1:
            msg = uiTools.cropStr(msg, maxMsgSize)
          else:
            includeBreak = True
            msg, remainder = uiTools.cropStr(msg, maxMsgSize, 4, 4, uiTools.Ending.HYPHEN, True)
            displayQueue.insert(0, (remainder.strip(), format))
        
        drawLine = displayLine + lineOffset
        if msg and drawLine < height and drawLine >= 1:
          self.addstr(drawLine, cursorLoc, msg, format)
        
        # If we're done, and have added content to this line, then start
        # further content on the next line.
        cursorLoc += len(msg)
        includeBreak |= not displayQueue and cursorLoc != lineNumOffset + scrollOffset
        
        if includeBreak:
          lineOffset += 1
          cursorLoc = lineNumOffset + scrollOffset
      
      displayLine += max(lineOffset, 1)
      
      if trustLastContentHeight and displayLine >= height: break
    
    if not trustLastContentHeight:
      self._lastContentHeightArgs = (width, height)
      newContentHeight = displayLine + self.scroll - 1
      
      if self._lastContentHeight != newContentHeight:
        self._lastContentHeight = newContentHeight
        self.redraw(True)
    
    self.valsLock.release()