tails-screen-locker 6.15 KB
Newer Older
segfault's avatar
segfault committed
1 2
#!/usr/bin/env python3

3 4 5 6
import logging
import socket
import sys
import gettext
segfault's avatar
segfault committed
7
import subprocess
8 9
from pydbus import SessionBus, SystemBus
import os
10 11 12
from pam import pam
import time
import pwd
segfault's avatar
segfault committed
13

14 15 16 17 18
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
segfault's avatar
segfault committed
19

20
from tailslib.adminpassword import is_password_set
21 22

_ = gettext.gettext
23
gettext.textdomain("tails")
24

25
logging.basicConfig(level=logging.DEBUG)
26

27
mainloop = GLib.MainLoop()
28

29 30 31 32 33

def lock_screen():
    # org.gnome.ScreenSaver.Lock() sometimes does not return, so we set a timeout of 5 seconds
    try:
        SessionBus().get("org.gnome.ScreenSaver").Lock(timeout=5)
34 35 36 37
    except Exception as e:
        logging.exception(e)
    finally:
        sys.exit()
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54


class PasswordDialog(object):

    def on_cancel_clicked(self, button, data=None):
        sys.exit(1)

    def on_entry_changed(self, entry, data=None):
        if not self.entry1.get_text() or not self.entry2.get_text():
            self.ok_button.set_sensitive(False)
        elif self.entry1.get_text() == self.entry2.get_text():
            # Passwords match
            self.ok_button.set_sensitive(True)
            self.entry2.set_icon_from_icon_name(1, None)
        else:
            # Passwords don't match
            self.ok_button.set_sensitive(False)
segfault's avatar
segfault committed
55
            self.entry2.set_icon_from_stock(1, "gtk-dialog-warning")
56 57 58 59 60 61 62

    def on_ok_clicked(self, button, data=None):
        pw1 = self.entry1.get_text()
        pw2 = self.entry2.get_text()
        if not pw1 == pw2:
            return

63
        self.pw = pw1.encode('utf8')
64 65 66 67

        bus = SystemBus()
        object_path = bus.get("org.freedesktop.Accounts").FindUserById(os.getuid())
        user_object = bus.get("org.freedesktop.Accounts", object_path)
68
        # lock the screen once the 'Changed' signal was received
69
        user_object.Changed.connect(self.wait_until_password_set_and_lock_screen)
70 71 72 73 74 75 76

        p = subprocess.Popen("passwd", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        p.stdin.write(self.pw + b"\n")
        p.stdin.write(self.pw)
        p.stdin.flush()
        out, err = p.communicate()
        if p.returncode != 0:
77 78 79
            print("passwd stdout: %s" % out)
            print("passwd stderr: %s" % err)
            raise RuntimeError("passwd returned %r" % p.returncode)
80 81 82 83

        # We close the window here for the case that lock_screen does not return immediately,
        # otherwise it would look like the app is unresponsive
        self.window.close()
84 85 86 87 88 89 90 91

    def on_key_pressed(self, widget, event):
        if Gdk.keyval_name(event.keyval) == "Escape":
            sys.exit(1)

        if self.ok_button.get_sensitive() and Gdk.keyval_name(event.keyval) == "Return":
            self.ok_button.clicked()

92 93 94 95
    def wait_until_password_set_and_lock_screen(self):
        # TODO: Remove this once this is fixed: https://bugzilla.gnome.org/show_bug.cgi?id=761969
        p = pam()
        while not p.authenticate(pwd.getpwuid(os.getuid()).pw_name, self.pw):
96
            logging.debug("PAM not updated yet...")
97 98 99 100 101 102
            time.sleep(0.01)
        # We close the window here for the case that lock_screen does not return immediately,
        # otherwise it would look like the app is unresponsive
        self.window.close()
        GLib.idle_add(lock_screen)

103 104 105 106 107
    def run(self):
        self.window.show()

    def __init__(self):
        self.pw = None
segfault's avatar
segfault committed
108

109
        self.ok_button = Gtk.Button(
sajolida's avatar
sajolida committed
110
            label=_("Lock Screen"),
111 112 113
            receives_default=True,
            sensitive=False,
            width_request=86
segfault's avatar
segfault committed
114
        )
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
        self.ok_button.connect("clicked", self.on_ok_clicked)
        self.ok_button.get_style_context().add_class('suggested-action')

        cancel_button = Gtk.Button(
            label=_("Cancel"),
            width_request=86
        )
        cancel_button.connect("clicked", self.on_cancel_clicked)

        headerbar = Gtk.HeaderBar(
            title=_("Screen Locker"),
        )
        headerbar.pack_start(cancel_button)
        headerbar.pack_end(self.ok_button)

        label_subtitle = Gtk.Label(
sajolida's avatar
sajolida committed
131
            label=_("Set up a password to unlock the screen."),
132
        )
segfault's avatar
segfault committed
133 134 135

        self.entry1 = Gtk.Entry(
            can_focus=True,
136 137
            visibility=False,
            width_request=200
segfault's avatar
segfault committed
138 139 140 141 142
        )
        self.entry1.connect("changed", self.on_entry_changed)

        self.entry2 = Gtk.Entry(
            can_focus=True,
143 144
            visibility=False,
            width_request=200
segfault's avatar
segfault committed
145 146 147 148
        )
        self.entry2.connect("changed", self.on_entry_changed)

        grid = Gtk.Grid(row_spacing=2, column_spacing=10)
149 150
        grid.attach(Gtk.Label(label=_("Password"), xalign=1), 0, 0, 1, 1)
        grid.attach(Gtk.Label(label=_("Confirm"), xalign=1), 0, 1, 1, 1)
segfault's avatar
segfault committed
151 152 153
        grid.attach(self.entry1, 1, 0, 1, 1)
        grid.attach(self.entry2, 1, 1, 1, 1)

154 155 156 157
        content_box = Gtk.Box(Gtk.Orientation.HORIZONTAL)
        content_box.pack_start(Gtk.Box(hexpand=True), False, False, 0)
        content_box.pack_end(Gtk.Box(hexpand=True), False, False, 0)
        content_box.add(grid)
segfault's avatar
segfault committed
158 159 160

        box = Gtk.Box(
            orientation=Gtk.Orientation.VERTICAL,
161 162 163 164 165
            margin_top=18,
            margin_bottom=18,
            margin_left=18,
            margin_right=18,
            spacing=18
segfault's avatar
segfault committed
166
        )
167 168 169 170 171 172 173 174
        box.add(label_subtitle)
        box.add(content_box)

        self.window = Gtk.Window(
            type_hint=Gdk.WindowTypeHint.DIALOG,
        )
        self.window.connect("key-press-event", self.on_key_pressed)
        self.window.set_titlebar(headerbar)
segfault's avatar
segfault committed
175 176
        self.window.add(box)
        self.window.show_all()
segfault's avatar
segfault committed
177 178


179 180 181 182 183 184 185 186 187
def get_lock():
    # Source: https://stackoverflow.com/a/7758075
    # Original author: https://stackoverflow.com/users/639295/aychedee
    # Without holding a reference to our socket somewhere it gets garbage
    # collected when the function exits
    get_lock._lock_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    get_lock._lock_socket.bind('\0' + 'tails-screen-locker')


188 189
def main():
    get_lock()
190
    if is_password_set():
191 192
        lock_screen()
        return
segfault's avatar
segfault committed
193

194 195 196
    dialog = PasswordDialog()
    GLib.idle_add(dialog.run)
    mainloop.run()
segfault's avatar
segfault committed
197 198 199 200


if __name__ == "__main__":
    main()