Added keyhook code, reworked things to use it.
authorRyan C. Gordon <icculus@icculus.org>
Mon, 23 Dec 2013 23:49:11 -0500
changeset 17 e884dbb403cc
parent 16 f5a7ede6c1f3
child 18 e7821efc78f6
Added keyhook code, reworked things to use it.
1pass.c
1pass.lua
CMakeLists.txt
keyhook.c
keyhook.h
--- a/1pass.c	Mon Dec 23 23:48:35 2013 -0500
+++ b/1pass.c	Mon Dec 23 23:49:11 2013 -0500
@@ -9,6 +9,7 @@
 #include "aes.h"
 #include "base64.h"
 #include "md5.h"
+#include "keyhook.h"
 #include <gtk/gtk.h>
 
 #define STATICARRAYLEN(x) ( (sizeof ((x))) / (sizeof ((x)[0])) )
@@ -287,8 +288,29 @@
 } // popupGuiMenu
 
 
+static void keyhookPressed(void)
+{
+    lua_getglobal(luaState, "keyhookPressed");
+    lua_call(luaState, 0, 0);
+} // keyhookPressed
+
+
+static gboolean keyhookPumper(void *arg)
+{
+    if (pumpKeyHook())
+        keyhookPressed();
+    return TRUE;  // keep firing timer
+} // keyhookPumper
+
+
 static int giveControlToGui(lua_State *L)
 {
+    if (initKeyHook())
+    {
+        atexit(deinitKeyHook);
+        g_timeout_add(200, (GSourceFunc) keyhookPumper, (gpointer) NULL);
+    } // if
+
     gtk_main();
     return 0;
 } // giveControlToGui
--- a/1pass.lua	Mon Dec 23 23:48:35 2013 -0500
+++ b/1pass.lua	Mon Dec 23 23:49:11 2013 -0500
@@ -1,6 +1,11 @@
 JSON = (loadfile "JSON.lua")()
 dofile("dumptable.lua")
 
+local basedir = "1Password/1Password.agilekeychain/data/default"  -- !!! FIXME
+local password = argv[2]
+local items = nil
+local keyhookRunning = false
+
 local passwordTypeNameMap = {
     ["webforms.WebForm"] = "Logins",
     ["wallet.financial.CreditCard"] = "Credit cards",
@@ -42,7 +47,7 @@
 
 
 local keys = {}
-function loadKey(basedir, level, password)
+local function loadKey(level, password)
     if keys[level] ~= nil then
         return keys[level]
     end
@@ -78,7 +83,7 @@
     return nil
 end
 
-local function getHint(basedir)
+local function getHint()
     local f = io.open(basedir .. "/.password.hint", "r")
     if (f == nil) then
         return
@@ -91,7 +96,7 @@
 end
 
 
-function loadContents(basedir)
+local function loadContents()
     return load_json(basedir .. "/contents.js")
 end
 
@@ -109,6 +114,7 @@
     local callback = function()
         copyToClipboard(str)
         --print("Copied data [" .. str .. "] to clipboard.")
+        keyhookRunning = false
     end
     return appendGuiMenuItem(menu, text, callback)
 end
@@ -208,13 +214,13 @@
 secret_menuitem_builders["wallet.financial.CreditCard"] = build_secret_menuitem_creditcard
 
 
-local function build_secret_menuitems(basedir, info, menu, password)
+local function build_secret_menuitems(info, menu)
     local metadata = load_json(basedir .. "/" .. info.uuid .. ".1password")
     if metadata == nil then
         return
     end
 
-    local plaintext = decryptBase64UsingKey(metadata.encrypted, loadKey(basedir, metadata.securityLevel, password))
+    local plaintext = decryptBase64UsingKey(metadata.encrypted, loadKey(metadata.securityLevel, password))
     if plaintext == nil then
         return
     end
@@ -238,6 +244,63 @@
     setGuiMenuItemSubmenu(menuitem, submenu)
 end
 
+local function prepItems()
+    items = {}
+    local contents = loadContents()
+    for i,v in ipairs(contents) do
+        local t = v[2]
+        if items[t] == nil then
+            items[t] = {}
+        end
+        local bucket = items[t]
+        bucket[#bucket+1] = { uuid=v[1], type=t, name=v[3], url=v[4] }  -- !!! FIXME: there are more fields, don't know what they mean yet.
+    end
+end
+
+function keyhookPressed()  -- not local! Called from C!
+    if keyhookRunning then
+        return
+    end
+
+    keyhookRunning = true
+
+    while password == nil do
+        password = runGuiPasswordPrompt(getHint())
+        if password == nil then
+            keyhookRunning = false
+            return
+        end
+        if loadKey("SL5", password) == nil then
+            password = nil  -- wrong password
+            local start = os.time()  -- cook the CPU for three seconds.
+            local now = start
+            while os.difftime(now, start) < 3 do
+                now = os.time()
+            end
+        end
+    end
+
+    prepItems()
+
+    local topmenu = makeGuiMenu()
+    for orderi,type in ipairs(passwordTypeOrdering) do
+        local bucket = items[type]
+        local realname = passwordTypeNameMap[type]
+        if realname == nil then
+            realname = type
+        end
+        local menuitem = appendGuiMenuItem(topmenu, realname)
+        local submenu = makeGuiMenu()
+        table.sort(bucket, function(a, b) return a.name < b.name end)
+        for i,v in pairs(bucket) do
+            build_secret_menuitems(v, submenu)
+        end
+        setGuiMenuItemSubmenu(menuitem, submenu)
+    end
+
+    popupGuiMenu(topmenu)
+end
+
 
 -- Mainline!
 
@@ -245,53 +308,9 @@
 --    print("argv[" .. i .. "] = " .. v)
 --end
 
-local basedir = "1Password/1Password.agilekeychain/data/default"  -- !!! FIXME
-
-local password = argv[2]
-while password == nil do
-    password = runGuiPasswordPrompt(getHint(basedir))
-    if password == nil then
-        os.exit(1)
-    end
-    if loadKey(basedir, "SL5", password) == nil then
-        password = nil  -- wrong password
-        local start = os.time()  -- cook the CPU for three seconds.
-        local now = start
-        while os.difftime(now, start) < 3 do
-            now = os.time()
-        end
-    end
-end
-
-local contents = loadContents(basedir)
-local items = {}
-for i,v in ipairs(contents) do
-    local t = v[2]
-    if items[t] == nil then
-        items[t] = {}
-    end
-    local bucket = items[t]
-    bucket[#bucket+1] = { uuid=v[1], type=t, name=v[3], url=v[4] }  -- !!! FIXME: there are more fields, don't know what they mean yet.
-end
-contents = nil
-
-local topmenu = makeGuiMenu()
-for orderi,type in ipairs(passwordTypeOrdering) do
-    local bucket = items[type]
-    local realname = passwordTypeNameMap[type]
-    if realname == nil then
-        realname = type
-    end
-    local menuitem = appendGuiMenuItem(topmenu, realname)
-    local submenu = makeGuiMenu()
-    table.sort(bucket, function(a, b) return a.name < b.name end)
-    for i,v in pairs(bucket) do
-        build_secret_menuitems(basedir, v, submenu, password)
-    end
-    setGuiMenuItemSubmenu(menuitem, submenu)
-end
-
-popupGuiMenu(topmenu)
+-- !!! FIXME: message box, exit if basedir is wack.
+-- !!! FIXME: this can probably happen in C now (the Lua mainline is basically gone now).
+--print("Now waiting for keyhook.")
 giveControlToGui()
 
 -- end of 1pass.lua ...
--- a/CMakeLists.txt	Mon Dec 23 23:48:35 2013 -0500
+++ b/CMakeLists.txt	Mon Dec 23 23:49:11 2013 -0500
@@ -75,6 +75,7 @@
 
 add_executable(1pass
     1pass.c
+    keyhook.c
     pkcs5_pbkdf2.c
     aes.c
     md5.c
@@ -121,6 +122,7 @@
 if(LINUX)
     set_target_properties(1pass PROPERTIES LINK_FLAGS "-Wl,-rpath,$ORIGIN")
     target_link_libraries(1pass "m")
+    target_link_libraries(1pass "Xtst")
 endif()
 
 # end of CMakeLists.txt ...
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/keyhook.c	Mon Dec 23 23:49:11 2013 -0500
@@ -0,0 +1,134 @@
+// !!! FIXME: this is X11 specific.  :(
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xlibint.h>
+#include <X11/extensions/record.h>
+
+#include "keyhook.h"
+
+static volatile int keyPressFlags = 0;
+static volatile int sawKeyCombo = 0;
+static void keyhookCallback(XPointer priv, XRecordInterceptData *data)
+{
+    const xEvent *xev = (const xEvent *) data->data;
+    if (data->category == XRecordFromServer)
+    {
+        const BYTE keycode = xev->u.u.detail;
+        if (xev->u.u.type == KeyPress)
+        {
+            // !!! FIXME: don't hardcode these keycodes.
+            if ((keycode == 64) && (keyPressFlags == 0))
+                keyPressFlags++;
+            else if ((keycode == 133) && (keyPressFlags == 1))
+                keyPressFlags++;
+            else if ((keycode == 51) && (keyPressFlags == 2))
+            {
+                sawKeyCombo = 1;
+                keyPressFlags = 0;
+            } // else if
+            else
+                keyPressFlags = 0;
+        } // if
+        else if (xev->u.u.type == KeyRelease)
+        {
+            keyPressFlags = 0;
+        } // else if
+    } // if
+
+    XRecordFreeData(data);
+} // keyhookCallback
+
+
+// every example I've seen needs two Display connections...one for the
+//  keyhook, and one to control it.
+static Display *ctrldpy = NULL;
+static Display *datadpy = NULL;
+static XRecordContext xrc = 0;
+
+int initKeyHook(void)
+{
+    int major = 0;
+    int minor = 0;
+    XRecordRange *xrr = NULL;
+    XRecordClientSpec xrcs = XRecordAllClients;
+
+    if (ctrldpy)
+        return 0;  // already initialized.
+
+    ctrldpy = XOpenDisplay(NULL);
+    if (!ctrldpy)
+        goto failed;
+
+    XSynchronize(ctrldpy, True);
+
+    datadpy = XOpenDisplay(NULL);
+    if (!datadpy)
+        goto failed;
+    else if (!XRecordQueryVersion(ctrldpy, &major, &minor))
+        goto failed;
+    else if ((xrr = XRecordAllocRange()) == NULL)
+        goto failed;
+
+    memset(xrr, '\0', sizeof (*xrr));
+    xrr->device_events.first = KeyPress;
+    xrr->device_events.last = KeyPress;
+
+    if ((xrc = XRecordCreateContext(ctrldpy, 0, &xrcs, 1, &xrr, 1)) == 0)
+        goto failed;
+    else if (!XRecordEnableContextAsync(datadpy, xrc, keyhookCallback, NULL))
+        goto failed;
+
+    XFree(xrr);
+    xrr = NULL;
+
+    return 1;
+
+failed:
+    deinitKeyHook();
+    if (xrr) XFree(xrr);
+
+    return 0;
+} // initKeyHook
+
+
+void deinitKeyHook(void)
+{
+    if (ctrldpy)
+    {
+        if (xrc)
+        {
+            XRecordDisableContext(ctrldpy, xrc);
+            XRecordFreeContext(ctrldpy, xrc);
+        } // if
+        XCloseDisplay(ctrldpy);
+    } // if
+
+    if (datadpy)
+        XCloseDisplay(datadpy);
+
+    ctrldpy = NULL;
+    datadpy = NULL;
+    xrc = 0;
+    sawKeyCombo = 0;
+    keyPressFlags = 0;
+} // deinitKeyHook
+
+
+int pumpKeyHook(void)
+{
+    if (!datadpy)
+        return 0;
+
+    sawKeyCombo = 0;
+    XRecordProcessReplies(datadpy);
+    return sawKeyCombo;
+} // pumpKeyHook
+
+// end of keyhook.c ...
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/keyhook.h	Mon Dec 23 23:49:11 2013 -0500
@@ -0,0 +1,11 @@
+#ifndef _INCL_KEYHOOK_H_
+#define _INCL_KEYHOOK_H_
+
+int pumpKeyHook(void);
+void deinitKeyHook(void);
+int initKeyHook(void);
+
+#endif
+
+// end of keyhook.h ...
+