summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Idle.py47
-rw-r--r--LockWindow.py69
-rw-r--r--Locker.py38
-rw-r--r--pam.py170
-rw-r--r--pylock.py51
-rw-r--r--unlock.ui176
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__
diff --git a/Idle.py b/Idle.py
new file mode 100644
index 0000000..38137ea
--- /dev/null
+++ b/Idle.py
@@ -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)
diff --git a/pam.py b/pam.py
new file mode 100644
index 0000000..d72489d
--- /dev/null
+++ b/pam.py
@@ -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>