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:
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()