This file is indexed.

/usr/share/arm/resources/torrcOverride/override.py is in tor-arm 1.4.5.0-1.

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

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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
#!/usr/bin/python

"""
This overwrites the system wide torrc, located at /etc/tor/torrc, with the
contents of the ARM_CONFIG_FILE. The system wide torrc is owned by root so
this will effectively need root permissions and for us to be in GROUP.

This file is completely unusable until we make a tor-arm user and perform
other prep by running with the '--init' argument.

After that's done this is meant to either be used by arm automatically after
writing a torrc to ARM_CONFIG_FILE or by users manually. For arm to use this
automatically you'll either need to...

- compile override.c, setuid on the binary, and move it to your path
  cd /usr/share/arm/resources/torrcOverride
  make
  chown root:tor-arm override
  chmod 04750 override
  mv override /usr/bin/torrc-override

- allow passwordless sudo for this script
  edit /etc/sudoers and add a line with:
  <arm user> ALL= NOPASSWD: /usr/share/arm/resources/torrcOverride/override.py

To perform this manually run:
/usr/share/arm/resources/torrcOverride/override.py
pkill -sighup tor
"""

import os
import re
import sys
import grp
import pwd
import time
import shutil
import tempfile
import signal

USER = "tor-arm"
GROUP = "tor-arm"
TOR_CONFIG_FILE = "/etc/tor/torrc"
ARM_CONFIG_FILE = "/var/lib/tor-arm/torrc"
RUN_VERIFY = True # use 'tor --verify-config' to check the torrc

# regex patterns for options the wizard may include
WIZARD_OPT = ("^DataDirectory \S+$",
              "^Log notice file \S+$",
              "^ControlPort 9052$",
              "^CookieAuthentication 1$",
              "^RunAsDaemon 1$",
              "^User \S+$",
              "^ORPort (443|9001)$",
              "^DirPort (80|9030)$",
              "^Nickname ",
              "^ContactInfo ",
              "^BridgeRelay 1$",
              "^PublishServerDescriptor 0$",
              "^RelayBandwidthRate ",
              "^RelayBandwidthBurst ",
              "^AccountingMax ",
              "^SocksPort 0$",
              "^PortForwarding 1$",
              "^ExitPolicy (accept|reject) \S+$",
              "^DirPortFrontPage \S+$",
              "^ClientOnly 1$",
              "^MaxCircuitDirtiness ",
              "^UseBridges 1$",
              "^Bridge ")

HELP_MSG = """Usage %s [OPTION]
  Backup the system wide torrc (%s) and replace it with the
  contents of %s.

  --init            creates the necessary user and paths
  --remove          reverts changes made with --init
  --validate PATH   checks if the given file is wizard generated
""" % (os.path.basename(sys.argv[0]), TOR_CONFIG_FILE, ARM_CONFIG_FILE)

def init():
  """
  Performs system preparation needed for this script to run, adding the tor-arm
  user and setting up paths/permissions.
  """
  
  # the following is just here if we have a custom destination directory (which
  # arm doesn't currently account for)
  if not os.path.exists("/bin/"):
    print "making '/bin'..."
    os.mkdir("/bin")
  
  if not os.path.exists("/var/lib/tor-arm/"):
    print "making '/var/lib/tor-arm'..."
    os.makedirs("/var/lib/tor-arm")
  
  if not os.path.exists("/var/lib/tor-arm/torrc"):
    print "making '/var/lib/tor-arm/torrc'..."
    open("/var/lib/tor-arm/torrc", 'w').close()
  
  try: gid = grp.getgrnam(GROUP).gr_gid
  except KeyError:
    print "adding %s group..." % GROUP
    os.system("addgroup --quiet --system %s" % GROUP)
    gid = grp.getgrnam(GROUP).gr_gid
    print "  done, gid: %s" % gid
  
  try: pwd.getpwnam(USER).pw_uid
  except KeyError:
    print "adding %s user..." % USER
    os.system("adduser --quiet --ingroup %s --no-create-home --home /var/lib/tor-arm/ --shell /bin/sh --system %s" % (GROUP, USER))
    uid = pwd.getpwnam(USER).pw_uid
    print "  done, uid: %s" % uid
  
  os.chown("/bin", 0, 0)
  os.chown("/var/lib/tor-arm", 0, gid)
  os.chmod("/var/lib/tor-arm", 0750)
  os.chown("/var/lib/tor-arm/torrc", 0, gid)
  os.chmod("/var/lib/tor-arm/torrc", 0760)

def remove():
  """
  Reverts the changes made by init, and also removes the optional
  /bin/torrc-override binary if it exists.
  """
  
  print "removing %s user..." % USER
  os.system("deluser --quiet %s" % USER)
  
  print "removing %s group..." % GROUP
  os.system("delgroup --quiet %s" % GROUP)
  
  if os.path.exists("/bin/torrc-override"):
    try:
      print "removing '/bin/torrc-override'..."
      os.remove("/bin/torrc-override")
    except OSError, exc:
      print "  unsuccessful: %s" % exc
  
  if os.path.exists("/var/lib/tor-arm/"):
    try:
      print "removing '/var/lib/tor-arm'..."
      shutil.rmtree("/var/lib/tor-arm/")
    except Exception, exc:
      print "  unsuccessful: %s" % exc

def replaceTorrc():
  # TODO: The setresgid and setresuid functions are only available in
  # python 2.7 (arm aims for 2.5 compatability). I'm not spotting a method
  # for setting the saved user id without it, though. :/
  
  majorVersion, minorVersion = sys.version_info[:2]
  canSetSavedUid = majorVersion >= 3 or (majorVersion == 2 and minorVersion >= 7)
  
  orig_uid = os.getuid()
  orig_euid = os.geteuid()
  
  # the USER and GROUP must exist on this system
  try:
    dropped_uid = pwd.getpwnam(USER).pw_uid
    dropped_gid = grp.getgrnam(GROUP).gr_gid
    dropped_euid, dropped_egid = dropped_uid, dropped_gid
  except KeyError:
    print "tor-arm user and group was not found, have you run this script with '--init'?"
    exit(1)
  
  # if we're actually root, we skip this group check
  # root can get away with all of this
  if orig_uid != 0:
    # check that the user is in GROUP
    if not dropped_gid in os.getgroups():
      print "Your user needs to be a member of the %s group for this to work" % GROUP
      sys.exit(1)
  
  # drop to the unprivileged group, and lose the rest of the groups
  os.setgid(dropped_gid)
  os.setegid(dropped_egid)
  
  if canSetSavedUid:
    # only usable in python 2.7 or later
    os.setresgid(dropped_gid, dropped_egid, dropped_gid)
  else:
    os.setregid(dropped_gid, dropped_egid)
  
  os.setgroups([dropped_gid])
  
  # make a tempfile and write out the contents
  try:
    tf = tempfile.NamedTemporaryFile(delete=False) # uses mkstemp internally
    
    # allows our child process to write to tf.name (not only if their uid matches, not their gid) 
    os.chown(tf.name, dropped_uid, orig_euid)
  except:
    print "We were unable to make a temporary file"
    sys.exit(1)
  
  fork_pid = os.fork()
  
  # open the suspect config after we drop privs
  # we assume the dropped privs are still enough to write to the tf
  if (fork_pid == 0):
    signal.signal(signal.SIGCHLD, signal.SIG_IGN)
    
    # Drop privs forever in the child process
    # I believe this drops os.setfsuid os.setfsgid stuff
    # Clear all other supplemental groups for dropped_uid
    os.setgroups([dropped_gid])
    
    if canSetSavedUid:
      # only usable in python 2.7 or later
      os.setresgid(dropped_gid, dropped_egid, dropped_gid)
      os.setresuid(dropped_uid, dropped_euid, dropped_uid)
    else:
      os.setregid(dropped_gid, dropped_egid)
      os.setreuid(dropped_uid, dropped_euid)
    
    os.setgid(dropped_gid)
    os.setegid(dropped_egid)
    os.setuid(dropped_uid)
    os.seteuid(dropped_euid)
    
    try:
      af = open(ARM_CONFIG_FILE) # this is totally unpriv'ed
      
      # ensure that the fd we opened has the properties we requrie
      configStat = os.fstat(af.fileno()) # this happens on the unpriv'ed FD
      if configStat.st_gid != dropped_gid:
        print "Arm's configuration file (%s) must be owned by the group %s" % (ARM_CONFIG_FILE, GROUP)
        sys.exit(1)
      
      # if everything checks out, we're as safe as we're going to get
      armConfig = af.read(1024 * 1024) # limited read but not too limited
      af.close()
      tf.file.write(armConfig)
      tf.flush()
    except:
      print "Unable to open the arm config as unpriv'ed user"
      sys.exit(1)
    finally:
      tf.close()
      sys.exit(0)
  else:
    # If we're here, we're in the parent waiting for the child's death
    # man, unix is really weird...
    _, status = os.waitpid(fork_pid, 0)
  
  if status != 0 or not os.path.exists(tf.name):
    print "The child seems to have failed; exiting!"
    tf.close()
    sys.exit(1)
  
  # attempt to verify that the config is OK
  if RUN_VERIFY:
    # raise privilages to drop them with 'su'
    os.setuid(0)
    os.seteuid(0)
    os.setgid(0)
    os.setegid(0)
    
    # drop privilages and exec tor to verify it as the dropped_uid 
    print "Using Tor to verify that arm will not break Tor's config:"
    verifyCmd = "su -c 'tor --verify-config -f %s' %s" % (tf.name, USER)
    success = os.system(verifyCmd)
    
    if success != 0:
      print "Tor says the new configuration file is invalid: %s (%s)" % (ARM_CONFIG_FILE, tf.name)
      sys.exit(1)
  
  # validates that the torrc matches what the wizard could produce
  torrcFile = open(tf.name)
  torrcContents = torrcFile.readlines()
  torrcFile.close()
  
  if not isWizardGenerated(torrcContents):
    print "torrc doesn't match what we'd expect from the setup wizard"
    sys.exit(1)
  
  # backup the previous tor config
  if os.path.exists(TOR_CONFIG_FILE):
    try:
      backupFilename = "%s_backup_%i" % (TOR_CONFIG_FILE, int(time.time()))
      shutil.copy(TOR_CONFIG_FILE, backupFilename)
    except IOError, exc:
      print "Unable to backup %s (%s)" % (TOR_CONFIG_FILE, exc)
      sys.exit(1)
  
  # overwrites TOR_CONFIG_FILE with ARM_CONFIG_FILE as loaded into tf.name
  try:
    shutil.copy(tf.name, TOR_CONFIG_FILE)
    print "Successfully reconfigured Tor"
  except IOError, exc:
    print "Unable to copy %s to %s (%s)" % (tf.name, TOR_CONFIG_FILE, exc)
    sys.exit(1)
  
  # unlink our temp file
  try:
    os.remove(tf.name)
  except:
    print "Unable to close temp file %s" % tf.name
    sys.exit(1)
  
  sys.exit(0)

def isWizardGenerated(torrcLines):
  """
  True if the given torrc contents could be generated by the wizard, false
  otherwise. This just checks the basic format and options used, not the
  validity of most generated values so this could still be rejected by tor.
  """
  
  wizardPatterns = [re.compile(opt) for opt in WIZARD_OPT]
  
  for line in torrcLines:
    commentStart = line.find("#")
    if commentStart != -1: line = line[:commentStart]
    
    line = line.strip()
    if not line: continue # blank line
    
    isRecognized = False
    for pattern in wizardPatterns:
      if pattern.match(line):
        isRecognized = True
        break
    
    if not isRecognized:
      print "Unrecognized torrc contents: %s" % line
      return False
  
  return True

if __name__ == "__main__":
  # sanity check that we're on linux
  if os.name != "posix":
    print "This is a script specifically for configuring Linux"
    sys.exit(1)
  
  if len(sys.argv) == 3 and sys.argv[1] == "--validate":
    torrcFile = open(sys.argv[2])
    torrcContents = torrcFile.readlines()
    torrcFile.close()
    
    isValid = isWizardGenerated(torrcContents)
    if isValid: print "torrc validated"
    else: print "torrc invalid"
    
    sys.exit(0)
  
  # check that we're running effectively as root
  if os.geteuid() != 0:
    print "This script needs to be run as root"
    sys.exit(1)
  
  if len(sys.argv) < 2:
    replaceTorrc()
  elif len(sys.argv) == 2 and sys.argv[1] == "--init":
    init()
  elif len(sys.argv) == 2 and sys.argv[1] == "--remove":
    remove()
  else:
    print HELP_MSG
    sys.exit(1)