mrrl-logincaps

MRRL version of logincaps
git clone https://ccx.te2000.cz/git/mrrl-logincaps
Log | Files | Refs

commit 17ebdc367951bc73e4a1b51d70364844645a9d38
parent f55b18f88d908881b4bd6c3d962577d04ec0d2e0
Author: Jan Pobrislo <ccx@webprojekty.cz>
Date:   Thu, 17 Dec 2020 03:26:29 +0100

split su-term.py into check-root-password.py and spawn-pty.py to reduce redundancy, drop argparse
Diffstat:
Mbin/handle-nsx11-message | 22++++++++++++++++++----
Mbin/spawn-pty.py | 27+++++++++++++++++----------
Asbin/check-root-password.py | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msbin/logincaps | 4++--
Dsbin/su-term.py | 105-------------------------------------------------------------------------------
5 files changed, 98 insertions(+), 121 deletions(-)

diff --git a/bin/handle-nsx11-message b/bin/handle-nsx11-message @@ -1,13 +1,27 @@ #!/bin/zsh setopt no_unset warn_create_global -typeset -g plumber_fifo terminal_exec_socket terminal_el x_container_service x_container_tmpfs +typeset -g data terminal_exec_socket terminal_el x_container_service x_container_tmpfs +typeset -ga terminal_cmd x_container_tmpfs=/run/containers/xsession.$X.$USER x_container_service=/run/service/container.xsession.$X.$USER -plumber_fifo=$x_container_tmpfs/run/plumber_fifo terminal_exec_socket=$x_container_tmpfs/run/exec/urxvt terminal_el="redirfd -w 1 /dev/null redirfd -w 2 /dev/null s6-sudo ${(qqq)terminal_exec_socket:A}" +terminal_cmd=( redirfd -w 1 /dev/null redirfd -w 2 /dev/null s6-sudo $terminal_exec_socket ) terminal_env=rxvt-unicode # the TERM variable used -exec s6-sudo $x_container_tmpfs/run/exec/exec \ - i3-nagbar -t warning -f fixed -m "Powerbox got message: ${(qqq)plumb_data}" +in_terminal() { + spawn-pty.py -- "$terminal_env" " $^@" "" "$terminal_cmd[@]" +} + +# read full data from stdin, will handle binary payloads correctly unlike env +IFS= read -ru 0 -k $plumb_ndata data || return $? + +case $data in + (show-inbox) + in_terminal watch -n 1 'tree /run/containers/*.ccx/inbox' + ;; + + (*) s6-sudo $x_container_tmpfs/run/exec/exec \ + i3-nagbar -t warning -f fixed -m "Powerbox got message: ${(qqq)plumb_data}" +esac diff --git a/bin/spawn-pty.py b/bin/spawn-pty.py @@ -6,13 +6,13 @@ from __future__ import ( import sys import os import os.path +from fcntl import ioctl +from termios import TIOCSCTTY import pwd -import argparse -import re -parser = argparse.ArgumentParser(description="Runs one program inside pty and another with changed privileges with master pty as fd 0") -parser.add_argument('term_env', help='The TERM variable used by the slave') -parser.add_argument('exe', nargs='+', help='Execline block defining program ran on slave end, then terminal program') +# parser = argparse.ArgumentParser(description="Runs one program inside pty and another with changed privileges with master pty as fd 0") +# parser.add_argument('term_env', help='The TERM variable used by the slave') +# parser.add_argument('exe', nargs='+', help='Execline block defining program ran on slave end, then terminal program') def execve(argv, env): @@ -49,7 +49,8 @@ def exec_terminal(terminal, term_env, slave_exe): os.dup2(slave, 1) os.dup2(slave, 2) os.close(slave) - os.setsid() + os.setsid() # create new session for this terminal + ioctl(0, TIOCSCTTY, 0) # set controlling terminal env = dict(os.environ) env['TERM'] = term_env env.pop('LOGNAME', None) @@ -57,9 +58,15 @@ def exec_terminal(terminal, term_env, slave_exe): def main(): - args = parser.parse_args() + argv = sys.argv[1:] + if len(argv) < 2: + return 1 + + term_env = argv.pop(0) + # compat with argparse way + if term_env in ('-', '--'): + term_env = argv.pop(0) - argv = list(args.exe) slave_argv = [] while argv: @@ -78,9 +85,9 @@ def main(): exec_terminal( terminal=argv, slave_exe=slave_argv, - term_env=args.term_env, + term_env=term_env, ) if __name__ == '__main__': - main() + sys.exit(main()) diff --git a/sbin/check-root-password.py b/sbin/check-root-password.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +from __future__ import ( + generators, division, absolute_import, with_statement, print_function +) +import sys +import os +import os.path +import pwd +import spwd +import crypt +import getpass + + +def execve(argv, env=None): + if env is None: + env = os.environ + if '/' in argv[0]: + os.execve(argv[0], argv, env) + else: + for p in os.environ['PATH'].split(os.path.pathsep): + try: + os.execve(os.path.join(p, argv[0]), argv, env) + except OSError: + continue + raise SystemExit(1) + + +def constant_time_compare(val1, val2): + """ + Returns True if the two strings are equal, False otherwise. + + The time taken is independent of the number of characters that match. + + For the sake of simplicity, this function executes in constant time only + when the two strings have the same length. It short-circuits when they + have different lengths. + """ + if len(val1) != len(val2): + return False + result = 0 + for x, y in zip(val1, val2): + result |= ord(x) ^ ord(y) + return result == 0 + + +def main(): + root_hash = spwd.getspnam('root').sp_pwd + root_pwent = pwd.getpwnam('root') + p = getpass.getpass() + if constant_time_compare(root_hash, crypt.crypt(p, root_hash)): + # OK + execve(sys.argv[1:]) + else: + sys.stderr.write("Incorrect password\n") + sys.exit(2) + + +if __name__ == '__main__': + sys.exit(main()) + diff --git a/sbin/logincaps b/sbin/logincaps @@ -82,11 +82,11 @@ terminal_spawn_common() { return 1 fi term_cmd="${term_cmd#* }" - cap_cmd execlineb -c "$1 -- ${(qqq)term_env} $2 cd / s6-setuidgid ${(qqq)USER} $term_cmd" + cap_cmd execlineb -c "$1 ${(qqq)term_env} $2 cd / s6-setuidgid ${(qqq)USER} $term_cmd" } terminal_spawn_password() { - terminal_spawn_common /command/su-term.py '' "$1" + terminal_spawn_common "/command/check-root-password.py /command/su-term.py" '{ login -f root }' "$1" } terminal_spawn() { diff --git a/sbin/su-term.py b/sbin/su-term.py @@ -1,105 +0,0 @@ -#!/usr/bin/env python - -from __future__ import ( - generators, division, absolute_import, with_statement, print_function -) -import sys -import os -import os.path -import pwd -import spwd -import crypt -import getpass -import argparse -import re - -parser = argparse.ArgumentParser(description="Check root password and run terminal with root shell") -parser.add_argument('-d', '--notify-fd', type=int, help="Write newline to this filedescriptor after reading password") -parser.add_argument('term_env', help='The TERM variable used by the shell') -parser.add_argument('exe', nargs='+', help='Terminal application to execute') - - -def execve(argv, env): - if '/' in argv[0]: - os.execve(argv[0], argv, env) - else: - for p in os.environ['PATH'].split(os.path.pathsep): - try: - os.execve(os.path.join(p, argv[0]), argv, env) - except OSError: - continue - raise SystemExit(1) - - -def exec_terminal(terminal, term_env, shell, shell_workdir): - assert isinstance(terminal, (list, tuple)) - assert all(isinstance(s, str) for s in terminal) - assert len(terminal) - assert isinstance(shell, str) - master, slave = os.openpty() - if os.fork(): - # parent - os.close(slave) - os.dup2(master, 0) - os.close(master) - env = dict(os.environ) - env['PTY_FD'] = "0" - execve(terminal, env) - else: - # child - os.close(master) - os.dup2(slave, 0) - os.dup2(slave, 1) - os.dup2(slave, 2) - os.close(slave) - os.setsid() - os.chdir(shell_workdir) - env = dict(os.environ) - env['USER'] = 'root' - env['TERM'] = term_env - env['HOME'] = shell_workdir - env['SHELL'] = shell - env.pop('LOGNAME', None) - os.execve(shell, ['-' + os.path.basename(shell)], env) - - -def constant_time_compare(val1, val2): - """ - Returns True if the two strings are equal, False otherwise. - - The time taken is independent of the number of characters that match. - - For the sake of simplicity, this function executes in constant time only - when the two strings have the same length. It short-circuits when they - have different lengths. - """ - if len(val1) != len(val2): - return False - result = 0 - for x, y in zip(val1, val2): - result |= ord(x) ^ ord(y) - return result == 0 - - -def main(): - args = parser.parse_args() - root_hash = spwd.getspnam('root').sp_pwd - root_pwent = pwd.getpwnam('root') - p = getpass.getpass() - if constant_time_compare(root_hash, crypt.crypt(p, root_hash)): - # OK - if args.notify_fd is not None: - os.write(args.notify_fd, '\n') - exec_terminal( - terminal=args.exe, - term_env=args.term_env, - shell=root_pwent.pw_shell, - shell_workdir=root_pwent.pw_dir, - ) - else: - sys.stderr.write("Incorrect password\n") - sys.exit(2) - - -if __name__ == '__main__': - main()