diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Idle.py | 47 | ||||
-rw-r--r-- | LockWindow.py | 69 | ||||
-rw-r--r-- | Locker.py | 38 | ||||
-rw-r--r-- | pam.py | 170 | ||||
-rw-r--r-- | pylock.py | 51 | ||||
-rw-r--r-- | unlock.ui | 176 |
7 files changed, 553 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b5e2e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +__pycache__ @@ -0,0 +1,47 @@ +import ctypes +import ctypes.util + +from gi.repository import GdkX11 + + +class XScreenSaverInfo(ctypes.Structure): + _fields_ = [ + ('window', ctypes.c_ulong), + ('state', ctypes.c_int), + ('kind', ctypes.c_int), + ('til_or_since', ctypes.c_ulong), + ('idle', ctypes.c_ulong), + ('eventMask', ctypes.c_ulong) + ] +XScreenSaverInfo_p = ctypes.POINTER(XScreenSaverInfo) + +display_p = ctypes.c_void_p +xid = ctypes.c_ulong +c_int_p = ctypes.POINTER(ctypes.c_int) + +libXsspath = ctypes.util.find_library('Xss') +if libXsspath == None: + raise OSError('libXss could not be found.') +libXss = ctypes.cdll.LoadLibrary(libXsspath) +libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p +libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p +libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p) + +dpy_p = hash(GdkX11.x11_get_default_xdisplay()) +rootwindow = GdkX11.x11_get_default_root_xwindow() + +_event_basep = ctypes.c_int() +_error_basep = ctypes.c_int() +if libXss.XScreenSaverQueryExtension(ctypes.c_void_p(dpy_p), ctypes.byref(_event_basep), + ctypes.byref(_error_basep)) == 0: + raise OSError('XScreenSaver Extension not available on display.') + +xss_info_p = libXss.XScreenSaverAllocInfo() +if xss_info_p == None: + raise OSError('XScreenSaverAllocInfo: Out of Memory.') + +def getIdleSec(): + if libXss.XScreenSaverQueryInfo(dpy_p, rootwindow, xss_info_p) == 0: + return 0 + else: + return int(xss_info_p.contents.idle) / 1000 diff --git a/LockWindow.py b/LockWindow.py new file mode 100644 index 0000000..98b8e86 --- /dev/null +++ b/LockWindow.py @@ -0,0 +1,69 @@ +import sys + +from gi.repository import Gtk, Gdk, GObject, GLib + + +class LockWindow(Gtk.Window): + __gsignals__ = { + 'tryUnlock': (GObject.SIGNAL_RUN_FIRST, None, (str,)) + } + + def __init__(self): + Gtk.Window.__init__(self) + + bg = Gtk.Image.new_from_file("bg.svg") + self.add(bg) + + self.set_decorated(False) + self.set_skip_taskbar_hint(True) + self.set_skip_pager_hint(True) + self.set_keep_above(True) + self.fullscreen() + + builder = Gtk.Builder() + + if not builder.add_from_file("unlock.ui"): + sys.exit(1) + + self.unlockWindow = builder.get_object('unlock_window') + self.promptEntry = builder.get_object('prompt_entry') + self.messageLabel = builder.get_object('message_label') + self.logoutButton = builder.get_object('logout_button') + self.unlockButton = builder.get_object('unlock_button') + + self.unlockWindow.set_position(Gtk.WindowPosition.CENTER_ALWAYS) + self.unlockWindow.set_transient_for(self) + self.unlockWindow.set_modal(True) + + self.connect('delete-event', lambda w, e: True) + self.unlockWindow.connect('delete-event', lambda w, e: True) + + self.promptEntry.connect('activate', lambda w: self._tryUnlock()) + self.unlockButton.connect('clicked', lambda w: self._tryUnlock()) + + self.reset() + + def _tryUnlock(self): + self.promptEntry.set_sensitive(False) + GLib.idle_add(lambda: self.emit('tryUnlock', self.promptEntry.get_text())) + + def reset(self): + self.promptEntry.set_text('') + self.promptEntry.set_sensitive(True) + self.promptEntry.grab_focus() + self.messageLabel.set_label('') + + def setMessage(self, message): + self.messageLabel.set_label(message) + + def lock(self): + self.show_all() + self.unlockWindow.show_all() + + self.promptEntry.grab_focus() + + def unlock(self): + self.unlockWindow.hide() + self.hide() + + self.reset() diff --git a/Locker.py b/Locker.py new file mode 100644 index 0000000..7871307 --- /dev/null +++ b/Locker.py @@ -0,0 +1,38 @@ +from gi.repository import Gtk, GLib + +import Idle + + +class Locker(object): + def __init__(self, lockTimeout, doLock, doUnlock, logoutTimeout = 0, doLogout = None): + self.locked = False + self.lockTimeout = lockTimeout + self.logoutTimeout = logoutTimeout + self.doLock = doLock + self.doUnlock = doUnlock + self.doLogout = doLogout + + GLib.timeout_add_seconds(1, self.checkLock) + + def checkLock(self): + if self.locked: + return False + + idle = Idle.getIdleSec() + if (idle >= self.lockTimeout): + GLib.idle_add(self.lock) + return False + else: + return True + + def lock(self): + if not self.locked: + self.doLock() + self.locked = True + + def unlock(self): + if self.locked: + self.doUnlock() + self.locked = False + + GLib.timeout_add(100, self.checkLock) @@ -0,0 +1,170 @@ +# (c) 2007 Chris AtLee <chris@atlee.ca> +# Licensed under the MIT license: +# http://www.opensource.org/licenses/mit-license.php +""" +PAM module for python + +Provides an authenticate function that will allow the caller to authenticate +a user against the Pluggable Authentication Modules (PAM) on the system. + +Implemented using ctypes, so no compilation is necessary. + +--- +modified to work [better] with python3.2, 2011-12-6, david ford, <david@blue-labs.org> +i haven't paid any attention to making sure things work in python2. there may be +problems in my_conv() +--- +Just some encoding fixes for python3, 2012-01-27 Matthias Schiffer <mschiffer@universe-factory.net> +""" + +import sys +import locale +if sys.version_info >= (3,): + py3k = True +else: + py3k = False + +from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof +from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int +from ctypes import memmove +from ctypes.util import find_library + +class PamHandle(Structure): + """wrapper class for pam_handle_t""" + _fields_ = [ + ("handle", c_void_p) + ] + + def __init__(self): + Structure.__init__(self) + self.handle = 0 + +class PamMessage(Structure): + """wrapper class for pam_message structure""" + _fields_ = [ + ("msg_style", c_int), + ("msg", c_char_p), + ] + + def __repr__(self): + return "<PamMessage %i '%s'>" % (self.msg_style, self.msg) + +class PamResponse(Structure): + """wrapper class for pam_response structure""" + _fields_ = [ + ("resp", c_char_p), + ("resp_retcode", c_int), + ] + + def __repr__(self): + return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp) + +CONV_FUNC = CFUNCTYPE( + c_int, + c_int, + POINTER(POINTER(PamMessage)), + POINTER(POINTER(PamResponse)), + c_void_p) + +class PamConv(Structure): + """wrapper class for pam_conv structure""" + _fields_ = [ + ("conv", CONV_FUNC), + ("appdata_ptr", c_void_p) + ] + +# Various constants +PAM_PROMPT_ECHO_OFF = 1 +PAM_PROMPT_ECHO_ON = 2 +PAM_ERROR_MSG = 3 +PAM_TEXT_INFO = 4 + +LIBC = CDLL(find_library("c")) +LIBPAM = CDLL(find_library("pam")) + +CALLOC = LIBC.calloc +CALLOC.restype = c_void_p +CALLOC.argtypes = [c_uint, c_uint] + +PAM_START = LIBPAM.pam_start +PAM_START.restype = c_int +PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv), POINTER(PamHandle)] + +PAM_STRERROR = LIBPAM.pam_strerror +PAM_STRERROR.restype = c_char_p +PAM_STRERROR.argtypes = [POINTER(PamHandle), c_int] + +PAM_AUTHENTICATE = LIBPAM.pam_authenticate +PAM_AUTHENTICATE.restype = c_int +PAM_AUTHENTICATE.argtypes = [PamHandle, c_int] + +class pam(): + code = 0 + reason = None + + def __init__(self): + pass + + def authenticate(self, username, password, service='login'): + """username and password authenticate for the given service. + + Returns True for success, or False. self.code is the integer + value representing the numerice failure reason, or 0 on success. + self.reason is the textual reason. + + Python3 expects bytes() for ctypes inputs. This function will make + necessary conversions using the latin-1 coding. + + username: the username to authenticate + password: the password in plain text + service: the PAM service to authenticate against. + Defaults to 'login' """ + + @CONV_FUNC + def my_conv(n_messages, messages, p_response, app_data): + """Simple conversation function that responds to any + prompt where the echo is off with the supplied password""" + # Create an array of n_messages response objects + addr = CALLOC(n_messages, sizeof(PamResponse)) + p_response[0] = cast(addr, POINTER(PamResponse)) + for i in range(n_messages): + if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF: + cs = c_char_p(password) + dst = CALLOC(sizeof(c_char_p), len(password)+1) + memmove(dst , cs, len(password)) + p_response.contents[i].resp = dst + p_response.contents[i].resp_retcode = 0 + return 0 + + # python3 ctypes prefers bytes, pretend everyone will be happy using latin-1 + if py3k: + if isinstance(username, str): + username = bytes(username, locale.getpreferredencoding()) + if isinstance(password, str): + password = bytes(password, locale.getpreferredencoding()) + if isinstance(service, str): + service = bytes(service, locale.getpreferredencoding()) + + handle = PamHandle() + conv = PamConv(my_conv, 0) + retval = PAM_START(service, username, pointer(conv), pointer(handle)) + + if retval != 0: + # This is not an authentication error, something has gone wrong starting up PAM + self.code = retval + self.reason = str(PAM_STRERROR(pointer(handle), retval), locale.getpreferredencoding()) + return False + + retval = PAM_AUTHENTICATE(handle, 0) + + if retval == 0: + # success + logic = True + else: + logic = False + + # store information to inform the caller why we failed + self.code = retval + self.reason = str(PAM_STRERROR(pointer(handle), retval), locale.getpreferredencoding()) + + return logic diff --git a/pylock.py b/pylock.py new file mode 100644 index 0000000..d42aa46 --- /dev/null +++ b/pylock.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +import signal +import sys +import os +import pwd + +from gi.repository import Gtk, Gdk + +from Locker import Locker +from LockWindow import LockWindow +import pam + + +def get_username(): + return pwd.getpwuid(os.getuid())[0] + + +theme = 'UzL-login' +timeout = 5 +username = get_username() + + +def handler(signum, frame): + Gtk.main_quit() + + +signal.signal(signal.SIGINT, handler) +signal.signal(signal.SIGTERM, handler) +signal.signal(signal.SIGQUIT, handler) + + +Gtk.Settings.get_default().set_property('gtk-theme-name', theme) + +window = LockWindow() +locker = Locker(timeout, lambda: window.lock(), lambda: window.unlock()) +pamAuth = pam.pam() + + +def tryUnlock(w, password): + if pamAuth.authenticate(username, password): + locker.unlock() + else: + window.reset() + window.setMessage(pamAuth.reason) + + return True + +window.connect('tryUnlock', tryUnlock) + +Gtk.main() diff --git a/unlock.ui b/unlock.ui new file mode 100644 index 0000000..d958485 --- /dev/null +++ b/unlock.ui @@ -0,0 +1,176 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="2.24"/> + <object class="GtkWindow" id="unlock_window"> + <property name="can_focus">False</property> + <property name="resizable">False</property> + <property name="decorated">False</property> + <property name="has_resize_grip">False</property> + <signal name="size-allocate" handler="login_window_size_allocate_cb" swapped="no"/> + <child> + <object class="GtkNotebook" id="login_notebook"> + <property name="width_request">700</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="show_tabs">False</property> + <child> + <object class="GtkHBox" id="box1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">40</property> + <child> + <object class="GtkImage" id="image1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="pixbuf">siegel.png</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">12</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="hostname_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" comments="This is a placeholder string and will be replaced with the hostname of the system">[hostname]</property> + <attributes> + <attribute name="font-desc" value="Sans 18"/> + </attributes> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="login_box"> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkHBox" id="prompt_box"> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="prompt_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Password:</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="prompt_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="visibility">False</property> + <property name="invisible_char">•</property> + <property name="primary_icon_activatable">False</property> + <property name="secondary_icon_activatable">False</property> + <signal name="activate" handler="login_cb" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="message_label"> + <property name="can_focus">False</property> + <property name="label" comments="This is a placeholder string and will be replaced with a message from PAM">[message]</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">20</property> + <child> + <object class="GtkButton" id="unlock_button"> + <property name="label" translatable="yes">Unlock</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <signal name="clicked" handler="login_cb" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="logout_button"> + <property name="label" translatable="yes">Logout</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <signal name="clicked" handler="cancel_cb" swapped="no"/> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <child type="tab"> + <placeholder/> + </child> + </object> + </child> + </object> +</interface> |