summaryrefslogtreecommitdiffstats
path: root/pylock
diff options
context:
space:
mode:
authorMatthias Schiffer <mschiffer@universe-factory.net>2013-04-12 18:44:55 +0200
committerMatthias Schiffer <mschiffer@universe-factory.net>2013-04-12 18:44:55 +0200
commit6636cbff81061bc270e4d21659fa3fba8eb3c87b (patch)
tree394c6de2cc718d320f92c2d3cd863f88c40bcfd7 /pylock
parent4659883ba00bd499f41f4cc1a5f96e4bdd5ac9de (diff)
downloadpylock-6636cbff81061bc270e4d21659fa3fba8eb3c87b.tar
pylock-6636cbff81061bc270e4d21659fa3fba8eb3c87b.zip
Create installation script using distutils
Diffstat (limited to 'pylock')
-rw-r--r--pylock/LockWindow.py172
-rw-r--r--pylock/Locker.py73
-rw-r--r--pylock/Main.py126
-rw-r--r--pylock/Selection.py66
-rw-r--r--pylock/__init__.py0
-rw-r--r--pylock/data/bg.svg208
-rw-r--r--pylock/data/unlock.ui166
-rw-r--r--pylock/pam.py170
8 files changed, 981 insertions, 0 deletions
diff --git a/pylock/LockWindow.py b/pylock/LockWindow.py
new file mode 100644
index 0000000..bb4d0e4
--- /dev/null
+++ b/pylock/LockWindow.py
@@ -0,0 +1,172 @@
+# Copyright (c) 2012, Matthias Schiffer <mschiffer@universe-factory.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+import sys
+import os
+import locale
+
+from gi.repository import Gtk, Gdk, GObject, GLib
+
+
+_ = locale.gettext
+
+
+PACKAGE_DIR = os.path.dirname(__file__)
+
+
+class LockWindow(Gtk.Window):
+ __gsignals__ = {
+ 'logout': (GObject.SIGNAL_RUN_FIRST, None, ()),
+ 'tryUnlock': (GObject.SIGNAL_RUN_FIRST, None, (str,))
+ }
+
+ def __init__(self):
+ Gtk.Window.__init__(self)
+
+ self.display = Gdk.Display.get_default()
+ self.screen = self.display.get_default_screen()
+ self.devicesGrabbed = False
+
+ self.overlay = Gtk.Overlay()
+ self.add(self.overlay)
+
+ self.bg = Gtk.Image.new_from_file(os.path.join(PACKAGE_DIR, 'data/bg.svg'))
+ self.overlay.add(self.bg)
+
+ self.realize()
+ self.get_window().set_override_redirect(True)
+
+ self.set_default_size(self.screen.get_width(), self.screen.get_height())
+
+ self.set_has_resize_grip(False)
+
+ builder = Gtk.Builder()
+
+ if not builder.add_from_file(os.path.join(PACKAGE_DIR, 'data/unlock.ui')):
+ sys.exit(1)
+
+ self.unlockWindow = builder.get_object('unlock_window')
+ self.lockLabel = builder.get_object('lock_label')
+ 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.overlay.add_overlay(self.unlockWindow)
+
+ self.connect('delete-event', lambda w, e: True)
+
+ self.promptEntry.connect('activate', lambda w: self._tryUnlock())
+ self.logoutButton.connect('clicked', lambda w: self._logout())
+ self.unlockButton.connect('clicked', lambda w: self._tryUnlock())
+
+ self.reset(False, True)
+
+ self.deviceManager = self.display.get_device_manager()
+
+ self.connect('map-event', self._grabDevices)
+ self.connect('configure-event', lambda w, e: self.get_window().move_resize(0, 0, self.screen.get_width(), self.screen.get_height()) and False)
+
+ def _logout(self):
+ self.promptEntry.set_sensitive(False)
+ self.unlockButton.set_sensitive(False)
+ self.logoutButton.set_sensitive(False)
+ GLib.idle_add(lambda: self.emit('logout'))
+
+ def _tryUnlock(self):
+ self.promptEntry.set_sensitive(False)
+ self.unlockButton.set_sensitive(False)
+ self.logoutButton.set_sensitive(False)
+ GLib.idle_add(lambda: self.emit('tryUnlock', self.promptEntry.get_text()))
+
+ def _update(self):
+ self.promptEntry.set_sensitive(self._enablePromptEntry)
+ self.logoutButton.set_sensitive(self._enableLogoutButton)
+ self.unlockButton.set_sensitive(self._enableUnlockButton)
+
+ def reset(self, regrab = True, resetButtons = False):
+ if resetButtons:
+ self._enablePromptEntry = True
+ self._enableLogoutButton = False
+ self._enableUnlockButton = True
+
+ self.promptEntry.set_text('')
+ self.messageLabel.set_label('')
+ self._update()
+
+ if regrab:
+ self._grabDevices(None, None)
+
+ def updateLockMessage(self, username, logoutTime = None):
+ if logoutTime is None:
+ self.lockLabel.set_markup(_('This computer is currently locked by the user <i>{username}</i>.').format(username=GLib.markup_escape_text(username)))
+ self._enableLogoutButton = False
+ elif logoutTime > 0:
+ self.lockLabel.set_markup(
+ _('This computer is currently locked by the user <i>{username}</i>.\nThe user can be logged out in {minutes:02}:{seconds:02} minutes.').format(
+ username=GLib.markup_escape_text(username), minutes=logoutTime//60, seconds=logoutTime%60))
+ self._enableLogoutButton = False
+ else:
+ self.lockLabel.set_markup(
+ _('This computer is currently locked by the user <i>{username}</i>.\nThe user can be logged out now.').format(
+ username=GLib.markup_escape_text(username)))
+ self._enableLogoutButton = True
+ self._update()
+
+ def setAuthMessage(self, message):
+ self.messageLabel.set_label(message)
+
+ def _grabDevices(self, w, e):
+ self.promptEntry.grab_focus()
+
+ if not self.devicesGrabbed:
+ for device in self.deviceManager.list_devices(Gdk.DeviceType.MASTER):
+ if device.grab(self.get_window(), Gdk.GrabOwnership.APPLICATION, True, Gdk.EventMask.ALL_EVENTS_MASK, None, Gdk.CURRENT_TIME) != Gdk.GrabStatus.SUCCESS:
+ self._ungrabDevices()
+ self.get_window().lower()
+ return False
+
+ self.present()
+
+ self.devicesGrabbed = True
+
+ return False
+
+ def _ungrabDevices(self):
+ for device in self.deviceManager.list_devices(Gdk.DeviceType.MASTER):
+ device.ungrab(Gdk.CURRENT_TIME)
+ self.devicesGrabbed = False
+
+ def lock(self):
+ self.show_all()
+ self.display.sync()
+ self._grabDevices(self, None)
+ return self.devicesGrabbed
+
+ def unlock(self):
+ self._ungrabDevices()
+
+ self.hide()
+
+ self.reset(False, True)
diff --git a/pylock/Locker.py b/pylock/Locker.py
new file mode 100644
index 0000000..5243609
--- /dev/null
+++ b/pylock/Locker.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2012, Matthias Schiffer <mschiffer@universe-factory.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+from gi.repository import Gtk, GLib
+
+
+class Locker(object):
+ def __init__(self, doLock, doUnlock, logoutTimeout = None, doLogout = None, updateLogoutTimeout = None):
+ self.locked = False
+ self.logoutTimeout = logoutTimeout
+ self.doLock = doLock
+ self.doUnlock = doUnlock
+ self.doLogout = doLogout
+ self.updateLogoutTimeout = updateLogoutTimeout
+
+ def _checkLogout(self):
+ if not self.locked:
+ return False
+
+ self.currentLogoutTimeout = self.currentLogoutTimeout - 1
+ if self.updateLogoutTimeout is not None:
+ self.updateLogoutTimeout(self.currentLogoutTimeout)
+
+ return self.currentLogoutTimeout > 0
+
+ def lock(self):
+ if not self.locked:
+ self.locked = True
+ if not self.doLock(self.logoutTimeout):
+ self.locked = False
+ GLib.timeout_add_seconds(1, self.lock)
+ return False
+
+ if self.doLogout is not None:
+ self.currentLogoutTimeout = self.logoutTimeout
+
+ if self.currentLogoutTimeout > 0:
+ GLib.timeout_add_seconds(1, self._checkLogout)
+
+ return True
+
+ def unlock(self):
+ if self.locked:
+ self.doUnlock()
+ self.locked = False
+
+ def _canLogout(self):
+ return (self.locked and self.doLogout is not None and self.currentLogoutTimeout <= 0)
+
+ def logout(self):
+ if self._canLogout():
+ self.doLogout()
diff --git a/pylock/Main.py b/pylock/Main.py
new file mode 100644
index 0000000..7a2cbb9
--- /dev/null
+++ b/pylock/Main.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+
+
+# Copyright (c) 2012, Matthias Schiffer <mschiffer@universe-factory.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+import signal
+import sys
+import os
+import pwd
+import locale
+import configparser
+
+from gi.repository import Gtk, GLib
+
+from .Locker import Locker
+from .LockWindow import LockWindow
+from . import Selection
+from . import pam
+
+
+CONFIG_FILE = '/etc/pylock.conf'
+
+
+_ = locale.gettext
+
+
+def main():
+ configParser = configparser.ConfigParser()
+ configParser.read_file(open(CONFIG_FILE))
+
+ config = configParser['pylock']
+ username = pwd.getpwuid(os.getuid())[0]
+
+ def signalHandler(signum, frame):
+ Gtk.main_quit()
+
+ signal.signal(signal.SIGINT, signalHandler)
+ signal.signal(signal.SIGTERM, signalHandler)
+ signal.signal(signal.SIGQUIT, signalHandler)
+
+ locale.setlocale(locale.LC_ALL, '')
+ locale.textdomain('pylock')
+
+ if 'theme' in config:
+ Gtk.Settings.get_defalt().set_property('gtk-theme-name', config['theme'])
+
+ def waitForSelection():
+ if Selection.get() != 0:
+ return True
+
+ Gtk.main_quit()
+ return False
+
+
+ if Selection.get() != 0:
+ GLib.timeout_add_seconds(1, waitForSelection)
+ Gtk.main()
+ sys.exit(0)
+
+ window = LockWindow()
+
+ if not Selection.acquire(window):
+ print('Unable to register pylock at X server', file=sys.stderr)
+ sys.exit(1)
+
+
+ def lock(timeLeft):
+ window.updateLockMessage(username, timeLeft)
+ return window.lock()
+
+ def logout():
+ try:
+ os.system(config['logout_command'])
+ except:
+ pass
+
+ window.reset(False)
+
+ def updateTimeout(timeLeft):
+ window.updateLockMessage(username, timeLeft)
+
+ if not ('logout_timeout' in config and 'logout_command' in config):
+ locker = Locker(lock, window.unlock)
+ else:
+ locker = Locker(lock, window.unlock, int(config['logout_timeout']), logout, updateTimeout)
+
+ pamAuth = pam.pam()
+
+ def tryUnlock(w, password):
+ if pamAuth.authenticate(username, password):
+ locker.unlock()
+ Gtk.main_quit()
+ else:
+ window.reset()
+ window.setAuthMessage(pamAuth.reason)
+
+ return True
+
+ window.connect('logout', lambda w: locker.logout())
+ window.connect('tryUnlock', tryUnlock)
+
+ GLib.idle_add(lambda: locker.lock())
+
+ Gtk.main()
diff --git a/pylock/Selection.py b/pylock/Selection.py
new file mode 100644
index 0000000..11a7285
--- /dev/null
+++ b/pylock/Selection.py
@@ -0,0 +1,66 @@
+# Copyright (c) 2012-2013, Matthias Schiffer <mschiffer@universe-factory.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+import ctypes
+import ctypes.util
+
+from gi.repository import Gdk, GdkX11
+
+
+XEvent_p = ctypes.c_void_p
+display_p = ctypes.c_void_p
+xid = ctypes.c_ulong
+x11_bool = ctypes.c_int
+x11_time = ctypes.c_ulong
+status = ctypes.c_int
+
+
+libX11 = ctypes.util.find_library('X11')
+if libX11 == None:
+ raise OSError('libX11 could not be found.')
+libX11 = ctypes.cdll.LoadLibrary(libX11)
+
+libX11.XSetSelectionOwner.argtypes = display_p, xid, xid, x11_time
+libX11.XSetSelectionOwner.restype = ctypes.c_int
+libX11.XGetSelectionOwner.argtypes = display_p, xid
+libX11.XGetSelectionOwner.restype = xid
+
+
+display = Gdk.Display.get_default()
+dpy_p = display_p(hash(GdkX11.X11Display.get_xdisplay(display)))
+atomPylockWindow = GdkX11.x11_get_xatom_by_name("PYLOCK_WINDOW")
+
+
+def get():
+ return libX11.XGetSelectionOwner(dpy_p, atomPylockWindow)
+
+def acquire(window):
+ if get() != 0:
+ return False
+
+ window.realize()
+ xid = GdkX11.X11Window.get_xid(window.get_window())
+ libX11.XSetSelectionOwner(dpy_p, atomPylockWindow, xid, 0)
+
+ return (get() == xid)
diff --git a/pylock/__init__.py b/pylock/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pylock/__init__.py
diff --git a/pylock/data/bg.svg b/pylock/data/bg.svg
new file mode 100644
index 0000000..78e3972
--- /dev/null
+++ b/pylock/data/bg.svg
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="1680"
+ height="1050"
+ viewBox="0 0 1680 1050"
+ id="svg2"
+ xml:space="preserve"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="bg.svg"><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1280"
+ inkscape:window-height="776"
+ id="namedview3016"
+ showgrid="false"
+ inkscape:zoom="0.23166667"
+ inkscape:cx="349.5177"
+ inkscape:cy="787.53453"
+ inkscape:window-x="0"
+ inkscape:window-y="24"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer1" /><metadata
+ id="metadata129"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+ id="defs127">
+
+
+
+<linearGradient
+ x1="960.00049"
+ y1="878.5"
+ x2="960.00049"
+ y2="291.50049"
+ id="linearGradient3223"
+ xlink:href="#SVGID_1_"
+ gradientUnits="userSpaceOnUse" />
+<linearGradient
+ inkscape:collect="always"
+ xlink:href="#SVGID_1_"
+ id="linearGradient3811"
+ gradientUnits="userSpaceOnUse"
+ x1="960.00049"
+ y1="878.5"
+ x2="960.00049"
+ y2="291.50049" />
+ <linearGradient
+ x1="960.00049"
+ y1="878.5"
+ x2="960.00049"
+ y2="291.50049"
+ id="SVGID_1_"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ id="stop20"
+ style="stop-color:#50acc5;stop-opacity:0.80000001"
+ offset="0" />
+ <stop
+ id="stop22"
+ style="stop-color:#388da2;stop-opacity:0.81980002"
+ offset="0.0148" />
+ <stop
+ id="stop24"
+ style="stop-color:#247385;stop-opacity:0.84420002"
+ offset="0.0331" />
+ <stop
+ id="stop26"
+ style="stop-color:#166071;stop-opacity:0.87110001"
+ offset="0.0533" />
+ <stop
+ id="stop28"
+ style="stop-color:#0b5363;stop-opacity:0.9016"
+ offset="0.0762" />
+ <stop
+ id="stop30"
+ style="stop-color:#044d5c;stop-opacity:0.93849999"
+ offset="0.1039" />
+ <stop
+ id="stop32"
+ style="stop-color:#104b5a;stop-opacity:1;"
+ offset="0.15000001" />
+ <stop
+ id="stop34"
+ style="stop-color:#104b5a;stop-opacity:1;"
+ offset="0.85000002" />
+ <stop
+ id="stop36"
+ style="stop-color:#044d5c;stop-opacity:0.93529999"
+ offset="0.89859998" />
+ <stop
+ id="stop38"
+ style="stop-color:#0b5363;stop-opacity:0.8987"
+ offset="0.926" />
+ <stop
+ id="stop40"
+ style="stop-color:#165f70;stop-opacity:0.86919999"
+ offset="0.94809997" />
+ <stop
+ id="stop42"
+ style="stop-color:#237184;stop-opacity:0.84350002"
+ offset="0.96740001" />
+ <stop
+ id="stop44"
+ style="stop-color:#368ba0;stop-opacity:0.8204"
+ offset="0.98470002" />
+ <stop
+ id="stop46"
+ style="stop-color:#50acc5;stop-opacity:0.80000001"
+ offset="1" />
+ </linearGradient>
+
+
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#SVGID_1_"
+ id="linearGradient3830"
+ gradientUnits="userSpaceOnUse"
+ x1="960.00049"
+ y1="878.5"
+ x2="960.00049"
+ y2="291.50049"
+ gradientTransform="matrix(1.1474185,0,0,0.85490494,-261.52174,174.07496)" /></defs>
+
+
+
+
+<g
+ id="Hilfslinien_Center"
+ style="display:none"
+ transform="translate(0,-150)">
+</g>
+<g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer"
+ transform="translate(0,-150)"><rect
+ style="fill:#104b5a;fill-opacity:1"
+ id="rect5"
+ y="150"
+ x="0"
+ height="1050"
+ width="1680" /><path
+ d="m 1326.2174,920.83442 c 0,2.35099 -2.5816,4.27453 -5.7371,4.27453 H 359.51967 c -3.1554,0 -5.73709,-1.92354 -5.73709,-4.27453 V 427.55427 c 0,-2.35098 2.58169,-4.27452 5.73709,-4.27452 h 960.96063 c 3.1555,0 5.7371,1.92354 5.7371,4.27452 v 493.28015 z"
+ id="path48"
+ style="opacity:0.5;fill:url(#linearGradient3830)"
+ inkscape:connector-curvature="0" /><path
+ d="m 1326.2174,920.83442 c 0,2.35099 -2.5816,4.27453 -5.7371,4.27453 H 359.51967 c -3.1554,0 -5.73709,-1.92354 -5.73709,-4.27453 V 427.55427 c 0,-2.35098 2.58169,-4.27452 5.73709,-4.27452 h 960.96063 c 3.1555,0 5.7371,1.92354 5.7371,4.27452 v 493.28015 z"
+ id="path50"
+ style="fill:none;stroke:#50acc5;stroke-width:0.4952105;stroke-linejoin:round;stroke-miterlimit:10"
+ inkscape:connector-curvature="0" /><g
+ id="Hilfslinien_Login"
+ transform="translate(-120,75)">
+</g><text
+ xml:space="preserve"
+ style="font-size:14px;font-style:normal;font-weight:normal;line-height:129.99999523%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"
+ x="839.86035"
+ y="1006.295"
+ id="text3011"
+ sodipodi:linespacing="130%"><tspan
+ sodipodi:role="line"
+ id="tspan3013"
+ x="839.86035"
+ y="1006.295"
+ style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:129.99999523%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans Bold">Bei Problemen melden Sie sich bitte per E-Mail an</tspan><tspan
+ sodipodi:role="line"
+ x="839.86035"
+ y="1034.895"
+ id="tspan3015"
+ style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:129.99999523%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans Bold" /><tspan
+ sodipodi:role="line"
+ x="839.86035"
+ y="1063.495"
+ id="tspan3017"
+ style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:129.99999523%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans Bold">pool-hotline@itsc.uni-luebeck.de</tspan><tspan
+ sodipodi:role="line"
+ x="839.86035"
+ y="1092.095"
+ id="tspan3019"
+ style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:129.99999523%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans Bold" /><tspan
+ sodipodi:role="line"
+ x="839.86035"
+ y="1120.6949"
+ id="tspan3021"
+ style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:129.99999523%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans Bold">oder telefonisch unter der</tspan><tspan
+ sodipodi:role="line"
+ x="839.86035"
+ y="1149.2949"
+ id="tspan3023"
+ style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:129.99999523%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans Bold">internen Nummer 5004.</tspan></text>
+
+</g></svg> \ No newline at end of file
diff --git a/pylock/data/unlock.ui b/pylock/data/unlock.ui
new file mode 100644
index 0000000..89bc3b8
--- /dev/null
+++ b/pylock/data/unlock.ui
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="2.24"/>
+ <object class="GtkNotebook" id="unlock_window">
+ <property name="width_request">700</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</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="lock_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">[lock]</property>
+ </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="invisible_char_set">True</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="yalign">0</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">True</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>
+ </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>
+ </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>
+</interface>
diff --git a/pylock/pam.py b/pylock/pam.py
new file mode 100644
index 0000000..d72489d
--- /dev/null
+++ b/pylock/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