prey/prey-node-client

View on GitHub
lib/agent/actions/lock/linux/prey-lock

Summary

Maintainability
Test Coverage
#!/bin/sh
''':'
':'; python=$(command -v python)
':'; [ -z "$python" ] || [ -n "${python##*usr*}" ] && python="/usr/bin/python"
':'; exec "$python" "$0" "$@"
'''

#############################################
# Prey Linux Lock
# By Tomas Pollak - (c) 2010 Fork Ltd.
# http://preyproject.com
# GPLv3 License
#############################################

import os, sys, gtk, pango, base64
from gobject import timeout_add
from hashlib import md5

image_file = "/../lib/bg-lock.png"

def write(text):
    print text
    sys.stdout.flush()

class Lock:
    def get_md5(self, string):
        hashb64 = base64.b64encode(string.encode("utf-8"))
        return md5(hashb64).hexdigest()

    def hide_error(self):
        self.label.hide()
        return False # stops timeout from repeating

    def enter_callback(self, widget, entry):
        hashed_text = self.get_md5(entry.get_text())
        # print hashed_text

        if hashed_text != passwd:
            write("Invalid password")
            self.label.show()
            timeout_add(3000, self.hide_error)
            return True
        else:
            enable_XF86Switch()
            print 'Correctomondo. PC Unlocked.'
            # self.label.set_markup('<span foreground="green">Very good. Access granted.</span>');
            # self.label.show()
            os._exit(66)

    def on_delete_event(self, widget, event):
        return True
        # self.window.set_keep_above(True)

    def on_focus_change(self, widget, event):
        print "Focus changed."
        return True

    def on_window_state_change(self, widget, event):
        self.window.activate_focus()
        print "Something happened."
        return False

    def on_key_press(self, widget, event):
        keyname = gtk.gdk.keyval_name(event.keyval)
        # print "Key %s (%d) was pressed" % (keyname, event.keyval)
        if event.keyval > 65470 and event.keyval < 65481: # F1 through F12
            print "Key %s (%d) was pressed" % (keyname, event.keyval)
            # return True
        if event.state & gtk.gdk.CONTROL_MASK:
            print "Control was being held down"
            # return True
        if event.state & gtk.gdk.MOD1_MASK:
            print "Alt was being held down"
            # return True
        if event.state & gtk.gdk.SHIFT_MASK:
            print "Shift was being held down"

    def scale_image(self, pixbuf, scale):
        new_width = int(scale*pixbuf.get_width())
        new_height = int(scale*pixbuf.get_height())
        # print "Scaled image to: %s x %s" % (new_width, new_height)
        return pixbuf.scale_simple(new_width, new_height, gtk.gdk.INTERP_BILINEAR)

    def __init__(self):

        # calculate number of screens
        width = gtk.gdk.screen_width()
        height = gtk.gdk.screen_height()

        black = gtk.gdk.color_parse("black")

        ###################################
        # black bg
        ###################################

        self.bg_window = gtk.Window(gtk.WINDOW_POPUP)
        self.bg_window.modify_bg(gtk.STATE_NORMAL, black)
        self.bg_window.resize(width, height)
        self.bg_window.set_deletable(False)
        self.bg_window.show()

        # monitors = self.bg_window.get_screen().get_n_monitors()

        ###################################
        # window
        ###################################

        self.window = gtk.Window(gtk.WINDOW_POPUP)
        self.window.set_title("Prey Lock")
        self.window.modify_bg(gtk.STATE_NORMAL, black)

        # prevents window from being closed
        self.window.connect("delete_event", self.on_delete_event)
        # capture keypresses
        self.window.connect("key_press_event", self.on_key_press)

        self.window.stick()
        self.window.set_deletable(False)
        # self.window.set_focus_on_map(True)
        self.window.set_decorated(False)
        self.window.set_border_width(0)
        self.window.set_keep_above(True)
        self.window.set_resizable(False)
        # self.window.fullscreen()

        main_screen_width = self.window.get_screen().get_monitor_geometry(0).width
        main_screen_height = self.window.get_screen().get_monitor_geometry(0).height
        main_screen_middle = main_screen_width/2

        # print "Main screen size: %s x %s" % (main_screen_width, main_screen_height)
        # print "Main screen middle: %s" % main_screen_middle

        vbox = gtk.VBox(False, 0)
        vbox.set_size_request(main_screen_width, main_screen_height)
        self.window.add(vbox)
        # vbox.show()

        ###################################
        # background color and image
        ###################################

        image = gtk.Image()
        script_path = sys.path[0]
        bg_path = script_path + image_file
        pixbuf = gtk.gdk.pixbuf_new_from_file(bg_path)
        image_width = pixbuf.get_width()
        image_height = pixbuf.get_height()

        # print 'Image size: %s x %s' % (image_width, image_height)
        scale = 1
        input_font = 16
        error_font = 11

        if image_width > main_screen_width or image_height > main_screen_height:
            scale = min(float(main_screen_width)/image_width, float(main_screen_height)/image_height)
            pixbuf = scale_image(pixbuf, scale)

        elif main_screen_width > 2000:
            scale = 2
            pixbuf = self.scale_image(pixbuf, scale)
            input_font -= 2
            error_font -= 1

        image.set_from_pixbuf(pixbuf)
        image.set_size_request(main_screen_width, main_screen_height)
        image.show()
        vbox.add(image)

        ###################################
        # label
        ###################################

        self.entry = gtk.Entry(max=0)
        self.entry.set_max_length(40)

        self.entry.set_inner_border(None)
        self.entry.set_width_chars(20)
        self.entry.set_visibility(False)
        self.entry.set_has_frame(False)
        self.entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#FFFFFF"))
        self.entry.modify_font(pango.FontDescription("sans " + str(input_font)))
        self.entry.set_flags(gtk.CAN_FOCUS | gtk.HAS_FOCUS | gtk.HAS_GRAB | gtk.CAN_DEFAULT | gtk.SENSITIVE)
        self.entry.connect("activate", self.enter_callback, self.entry)
        # self.entry.show()

        input_size = 140
        padding_left = int(input_size*scale)
        padding_top = 5*scale
        # print 'Padding: left %s, top %s' % (padding_left, padding_top)

        # if image was scaled down, calculate offset for positioning elements
        if image_height > main_screen_height:
            padding_top = 18 - int((image_height - main_screen_height)/8)

        x_position = main_screen_middle-padding_left
        y_position = -(main_screen_height/2)+padding_top
        # print "Fixed position: %s, %s" % (x_position, y_position)

        fixed = gtk.Fixed()
        fixed.put(self.entry, x_position, y_position)
        fixed.show()
        vbox.pack_start(fixed, False, False, 0)

        text = 'Invalid password. Access denied.'
        self.label = gtk.Label()
        self.label.set_markup('<span foreground="#b22222">'+text+'</span>');
        self.label.modify_font(pango.FontDescription("sans " + str(error_font)))
        # self.label.set_size_request(main_screen_width, 30)

        fixed2 = gtk.Fixed()
        # push label down a bit, and left a bit so it remains centered
        fixed2.put(self.label, x_position + 20, y_position + (46 * scale))
        vbox.pack_start(fixed2, False, False, 0)

        ###################################
        # optional message
        ###################################

        if msgtext != "":
            self.label2 = gtk.Label()
            self.label2.set_line_wrap(True)
            self.label2.set_justify(gtk.JUSTIFY_CENTER)
            self.label2.set_markup('<span foreground="#FFFFFF">'+msgtext+'</span>');
            self.label2.modify_font(pango.FontDescription("sans " + str(error_font)))
            label_width = self.label2.size_request()[0]

            fixed3 = gtk.Fixed()
            fixed3.put(self.label2, main_screen_width/2 - label_width/2, y_position + (100 * scale))
            fixed3.show()
            vbox.pack_start(fixed3, False, False, 0)

        ##############################################

        disable_XF86Switch()

        self.window.show_all()
        # self.window.set_focus(self.entry)
        gtk.gdk.keyboard_grab(fixed.window, True)
        self.label.hide()

original_keycodes = []

def enable_XF86Switch():
    import subprocess # need at least python 2.4
    try:
        for x,y,z in original_keycodes:
            cmd = ['xmodmap', '-e', 'keycode %s=%s %s' % (x,y,z)]
            subprocess.Popen(cmd)
    except OSError, subprocess.CalledProcessError:
        return

def disable_XF86Switch():
    import subprocess # need at least python 2.4
    import re

    try:
        cmd = ['xmodmap', '-pk']
        # find all keys that allows switching to a VT
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        output, serror = p.communicate()
        matched = []

        # retrieve the keycode and its value from xmodmap output
        for line in output.split('\n'):
            if re.search('XF86Switch_VT', line):
                items = line.split()
                keycode = items[0]
                keysym  = items[1]
                keysym2 = items[7]
                keyname = items[2][1:-1]
                matched.append((keycode, keyname))
                original_keycodes.append((keycode, keysym, keysym2))

        # disable all key that allow to switch to a VT
        for k,v in matched:
            cmd = ['xmodmap', '-e', 'keycode %s=%s' % (k,v)]
            subprocess.Popen(cmd)

    except IndexError:
        print "Couldn't parse Xmodmap list."

    except OSError, subprocess.CalledProcessError:
        # print "Xmodmap not found"
        # do nothing if xmodmap is not found or return an error
        return

if __name__ == "__main__":

    msgtext = ""
    if len(sys.argv) < 2 or len(sys.argv[1]) != 32:
        print "You need to pass an MD5 hexdigested string."
        sys.exit(67)
    else:
        passwd = sys.argv[1]
        if len(sys.argv) > 2:
            msgtext = sys.argv[2]

    write("Process PID: %d" % os.getpid())
    lock = Lock()
    gtk.main()