Merge pull request #708 from skuhl/doas

Allow use of sudo or doas.
This commit is contained in:
Brian May 2022-01-17 08:04:50 +11:00 committed by GitHub
commit 4e43af758d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -197,55 +197,101 @@ class FirewallClient:
if ssyslog._p: if ssyslog._p:
argvbase += ['--syslog'] argvbase += ['--syslog']
# Determine how to prefix the command in order to elevate privileges. # A list of commands that we can try to run to start the firewall.
if platform.platform().startswith('OpenBSD'): argv_tries = []
elev_prefix = ['doas'] # OpenBSD uses built in `doas`
if os.getuid() == 0: # No need to elevate privileges
argv_tries.append(argvbase)
else: else:
elev_prefix = ['sudo', '-p', '[local sudo] Password: '] # Linux typically uses sudo; OpenBSD uses doas. However, some
# Linux distributions are starting to use doas.
sudo_cmd = ['sudo', '-p', '[local sudo] Password: ']+argvbase
doas_cmd = ['doas']+argvbase
# Look for binary and switch to absolute path if we can find # For clarity, try to replace executable name with the
# it. # full path.
path = which(elev_prefix[0]) doas_path = which("doas")
if path: if doas_path:
elev_prefix[0] = path doas_cmd[0] = doas_path
sudo_path = which("sudo")
if sudo_path:
sudo_cmd[0] = sudo_path
if sudo_pythonpath: # sudo_pythonpath indicates if we should set the
elev_prefix += ['/usr/bin/env', # PYTHONPATH environment variable when elevating
'PYTHONPATH=%s' % # privileges. This can be adjusted with the
os.path.dirname(os.path.dirname(__file__))] # --no-sudo-pythonpath option.
argv_tries = [elev_prefix + argvbase, argvbase] if sudo_pythonpath:
pp_prefix = ['/usr/bin/env',
'PYTHONPATH=%s' %
os.path.dirname(os.path.dirname(__file__))]
sudo_cmd = pp_prefix + sudo_cmd
doas_cmd = pp_prefix + doas_cmd
# we can't use stdin/stdout=subprocess.PIPE here, as we normally would, # If we can find doas and not sudo or if we are on
# because stupid Linux 'su' requires that stdin be attached to a tty. # OpenBSD, try using doas first.
# Instead, attach a *bidirectional* socket to its stdout, and use if (doas_path and not sudo_path) or \
# that for talking in both directions. platform.platform().startswith('OpenBSD'):
(s1, s2) = socket.socketpair() argv_tries = [doas_cmd, sudo_cmd, argvbase]
else:
argv_tries = [sudo_cmd, doas_cmd, argvbase]
def setup(): # Try all commands in argv_tries in order. If a command
# run in the child process # produces an error, try the next one. If command is
s2.close() # successful, set 'success' variable and break.
if os.getuid() == 0: success = False
argv_tries = argv_tries[-1:] # last entry only
for argv in argv_tries: for argv in argv_tries:
# we can't use stdin/stdout=subprocess.PIPE here, as we
# normally would, because stupid Linux 'su' requires that
# stdin be attached to a tty. Instead, attach a
# *bidirectional* socket to its stdout, and use that for
# talking in both directions.
(s1, s2) = socket.socketpair()
def setup():
# run in the child process
s2.close()
try: try:
if argv[0] == 'su': debug1("Starting firewall manager with command: %r" % argv)
sys.stderr.write('[local su] ')
self.p = ssubprocess.Popen(argv, stdout=s1, preexec_fn=setup) self.p = ssubprocess.Popen(argv, stdout=s1, preexec_fn=setup)
# No env: Talking to `FirewallClient.start`, which has no i18n. # No env: Talking to `FirewallClient.start`, which has no i18n.
break
except OSError as e: except OSError as e:
log('Spawning firewall manager: %r' % argv) # This exception will occur if the program isn't
raise Fatal(e) # present or isn't executable.
self.argv = argv debug1('Unable to start firewall manager. Popen failed. '
s1.close() 'Command=%r Exception=%s' % (argv, e))
self.pfile = s2.makefile('rwb') continue
line = self.pfile.readline()
self.check() self.argv = argv
if line[0:5] != b'READY': s1.close()
raise Fatal('%r expected READY, got %r' % (self.argv, line)) self.pfile = s2.makefile('rwb')
method_name = line[6:-1] line = self.pfile.readline()
self.method = get_method(method_name.decode("ASCII"))
self.method.set_firewall(self) rv = self.p.poll() # Check if process is still running
if rv:
# We might get here if program runs and exits before
# outputting anything. For example, someone might have
# entered the wrong password to elevate privileges.
debug1('Unable to start firewall manager. '
'Process exited too early. '
'%r returned %d' % (self.argv, rv))
continue
if line[0:5] != b'READY':
debug1('Unable to start firewall manager. '
'Expected READY, got %r. '
'Command=%r' % (line, self.argv))
continue
method_name = line[6:-1]
self.method = get_method(method_name.decode("ASCII"))
self.method.set_firewall(self)
success = True
break
if not success:
raise Fatal("All attempts to elevate privileges failed.")
def setup(self, subnets_include, subnets_exclude, nslist, def setup(self, subnets_include, subnets_exclude, nslist,
redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp, redirectport_v6, redirectport_v4, dnsport_v6, dnsport_v4, udp,