1pass.lua
author Ryan C. Gordon <icculus@icculus.org>
Wed, 14 Jun 2017 00:27:18 -0400
branchgtkui
changeset 50 7bc981f5da6c
parent 49 2c57a0ad7c8f
child 56 a573346e6f7b
permissions -rw-r--r--
Closing gtkui branch.
icculus@0
     1
JSON = (loadfile "JSON.lua")()
icculus@12
     2
dofile("dumptable.lua")
icculus@12
     3
icculus@17
     4
local basedir = "1Password/1Password.agilekeychain/data/default"  -- !!! FIXME
icculus@17
     5
local password = argv[2]
icculus@17
     6
local items = nil
icculus@35
     7
local faveitems = nil
icculus@17
     8
local keyhookRunning = false
icculus@45
     9
local keyhookGuiMenus = nil
icculus@45
    10
icculus@45
    11
icculus@45
    12
local function runGarbageCollector()
icculus@45
    13
    --local memused = math.floor(collectgarbage("count") * 1024.0)
icculus@45
    14
    --print("Collecting garbage (currently using " .. memused .. " bytes).")
icculus@45
    15
    collectgarbage()
icculus@45
    16
    --local newmemused = math.floor(collectgarbage("count") * 1024.0)
icculus@45
    17
    --print("Now using " .. newmemused .. " bytes (" .. memused - newmemused .. " bytes savings).")
icculus@45
    18
end
icculus@17
    19
icculus@12
    20
local passwordTypeNameMap = {
icculus@12
    21
    ["webforms.WebForm"] = "Logins",
icculus@12
    22
    ["wallet.financial.CreditCard"] = "Credit cards",
icculus@12
    23
    ["passwords.Password"] = "Passwords",
icculus@12
    24
    ["wallet.financial.BankAccountUS"] = "Bank accounts",
icculus@12
    25
    ["wallet.membership.Membership"] = "Memberships",
icculus@12
    26
    ["wallet.government.DriversLicense"] = "Drivers licenses",
icculus@12
    27
    ["system.Tombstone"] = "Dead items",
icculus@25
    28
    ["securenotes.SecureNote"] = "Secure notes",
icculus@49
    29
    ["wallet.government.SsnUS"] = "Social Security Numbers",
icculus@49
    30
    ["wallet.computer.Router"] = "Router passwords",
icculus@12
    31
    -- !!! FIXME: more!
icculus@12
    32
}
icculus@12
    33
icculus@12
    34
local passwordTypeOrdering = {
icculus@12
    35
    "webforms.WebForm",
icculus@12
    36
    "wallet.financial.CreditCard",
icculus@12
    37
    "passwords.Password",
icculus@12
    38
    "wallet.financial.BankAccountUS",
icculus@12
    39
    "wallet.membership.Membership",
icculus@12
    40
    "wallet.government.DriversLicense",
icculus@49
    41
    "wallet.government.SsnUS",
icculus@25
    42
    "securenotes.SecureNote",
icculus@49
    43
    "wallet.computer.Router",
icculus@12
    44
    -- never show "system.Tombstone",
icculus@12
    45
    -- !!! FIXME: more!
icculus@12
    46
}
icculus@0
    47
icculus@6
    48
local function load_json_str(str, desc)
icculus@6
    49
    local retval = JSON:decode(str)
icculus@6
    50
    return retval
icculus@6
    51
end
icculus@6
    52
icculus@0
    53
local function load_json(fname)
icculus@0
    54
    local f = io.open(fname, "rb")
icculus@0
    55
    if (f == nil) then
icculus@0
    56
        return nil
icculus@0
    57
    end
icculus@0
    58
icculus@46
    59
    local str = f:read("*a")
icculus@0
    60
    f:close()
icculus@0
    61
icculus@6
    62
    return load_json_str(str, fname)
icculus@0
    63
end
icculus@0
    64
icculus@0
    65
icculus@5
    66
local keys = {}
icculus@17
    67
local function loadKey(level, password)
icculus@5
    68
    if keys[level] ~= nil then
icculus@5
    69
        return keys[level]
icculus@5
    70
    end
icculus@5
    71
icculus@12
    72
    local keysjson = load_json(basedir .. "/encryptionKeys.js")
icculus@0
    73
    if (keysjson == nil) or (keysjson[level] == nil) then
icculus@0
    74
        return nil
icculus@0
    75
    end
icculus@0
    76
icculus@0
    77
    local identifier = keysjson[level]
icculus@0
    78
    for i,v in ipairs(keysjson.list) do
icculus@0
    79
        if v.identifier == identifier then
icculus@0
    80
			local iterations = v.iterations
icculus@0
    81
            if (iterations == nil) or (iterations < 1000) then
icculus@0
    82
			    iterations = 1000
icculus@0
    83
            end
icculus@0
    84
icculus@0
    85
			local decrypted = decryptUsingPBKDF2(v.data, password, iterations)
icculus@0
    86
			if decrypted == nil then
icculus@0
    87
                return nil
icculus@0
    88
            end
icculus@0
    89
icculus@0
    90
			local validate = decryptBase64UsingKey(v.validation, decrypted)
icculus@0
    91
			if validate ~= decrypted then
icculus@0
    92
                return nil
icculus@0
    93
            end
icculus@0
    94
icculus@5
    95
            keys[level] = decrypted
icculus@0
    96
            return decrypted
icculus@0
    97
        end
icculus@0
    98
    end
icculus@0
    99
icculus@0
   100
    return nil
icculus@0
   101
end
icculus@0
   102
icculus@17
   103
local function getHint()
icculus@0
   104
    local f = io.open(basedir .. "/.password.hint", "r")
icculus@0
   105
    if (f == nil) then
icculus@0
   106
        return
icculus@0
   107
    end
icculus@0
   108
icculus@46
   109
    local str = "(hint is '" .. f:read("*a") .. "')."
icculus@0
   110
    f:close()
icculus@12
   111
    --print(str)
icculus@12
   112
    return str
icculus@0
   113
end
icculus@0
   114
icculus@1
   115
icculus@17
   116
local function loadContents()
icculus@12
   117
    return load_json(basedir .. "/contents.js")
icculus@6
   118
end
icculus@6
   119
icculus@45
   120
local function makeMenu()
icculus@45
   121
    return {}
icculus@45
   122
end
icculus@45
   123
icculus@45
   124
local function appendMenuItem(menu, text, callback)
icculus@45
   125
    local item = {}
icculus@45
   126
    item["text"] = text
icculus@45
   127
    if callback ~= nil then
icculus@45
   128
        item["callback"] = callback
icculus@45
   129
    end
icculus@45
   130
    menu[#menu+1] = item
icculus@45
   131
    return item
icculus@45
   132
end
icculus@45
   133
icculus@45
   134
local function setMenuItemSubmenu(menuitem, submenu)
icculus@45
   135
    menuitem["submenu"] = submenu
icculus@45
   136
end
icculus@45
   137
icculus@46
   138
local function setMenuItemChecked(menuitem, ischecked)
icculus@46
   139
    menuitem["checked"] = ischecked
icculus@46
   140
end
icculus@45
   141
icculus@45
   142
icculus@12
   143
local function build_secret_menuitem(menu, type, str, hidden)
icculus@12
   144
    if str == nil then
icculus@12
   145
        return nil
icculus@8
   146
    end
icculus@12
   147
icculus@12
   148
    local valuestr = str
icculus@12
   149
    if hidden == true then
icculus@12
   150
        valuestr = "*****"
icculus@12
   151
    end
icculus@12
   152
    local text = type .. " " .. valuestr
icculus@12
   153
icculus@12
   154
    local callback = function()
icculus@12
   155
        copyToClipboard(str)
icculus@12
   156
        --print("Copied data [" .. str .. "] to clipboard.")
icculus@45
   157
        guiDestroyMenu(keyhookGuiMenus[1])
icculus@12
   158
    end
icculus@45
   159
    return appendMenuItem(menu, text, callback)
icculus@12
   160
end
icculus@12
   161
icculus@12
   162
icculus@12
   163
local secret_menuitem_builders = {}
icculus@12
   164
icculus@12
   165
local function build_secret_menuitem_webform(menu, info, secure)
icculus@12
   166
    local addthis = false
icculus@12
   167
    local username = nil
icculus@12
   168
    local password = nil
icculus@32
   169
    local designated_password = nil
icculus@32
   170
    local designated_username = nil
icculus@12
   171
    local email = nil
robbie@21
   172
icculus@24
   173
    if secure.fields == nil then
icculus@24
   174
      print("no secure fields, don't know how to handle this item") 
icculus@24
   175
      return
icculus@24
   176
    end
icculus@24
   177
icculus@12
   178
    for i,v in ipairs(secure.fields) do
icculus@12
   179
        --print(info.name .. ": " .. v.type .. ", " .. v.value)
icculus@12
   180
        local ignored = false
icculus@32
   181
        if (v.value == nil) or (v.value == "") then
icculus@32
   182
            ignored = true
icculus@32
   183
        elseif (v.designation ~= nil) and (v.designation == "password") then
icculus@32
   184
            designated_password = v.value
icculus@32
   185
        elseif (v.designation ~= nil) and (v.designation == "username") then
icculus@32
   186
            designated_username = v.value
icculus@32
   187
        elseif (v.type == "P") then
icculus@12
   188
            password = v.value
icculus@32
   189
        elseif (v.type == "T") then
icculus@12
   190
            username = v.value
icculus@32
   191
        elseif (v.type == "E") then
icculus@12
   192
            email = v.value
icculus@12
   193
        else
icculus@12
   194
            ignored = true
icculus@12
   195
        end
icculus@12
   196
icculus@12
   197
        if not ignored then
icculus@12
   198
            addthis = true
icculus@12
   199
        end
icculus@12
   200
    end
icculus@12
   201
icculus@12
   202
    if addthis then
icculus@32
   203
        -- designated fields always win out.
icculus@32
   204
        if (designated_username ~= nil) then
icculus@32
   205
            username = designated_username
icculus@32
   206
        end
icculus@32
   207
icculus@32
   208
        if (designated_password ~= nil) then
icculus@32
   209
            password = designated_password
icculus@32
   210
        end
icculus@32
   211
icculus@12
   212
        if (username ~= nil) and (email ~= nil) and (email == username) then
icculus@12
   213
            email = nil
icculus@12
   214
        end
icculus@12
   215
icculus@12
   216
        build_secret_menuitem(menu, "username", username)
icculus@12
   217
        build_secret_menuitem(menu, "email", email)
icculus@12
   218
        build_secret_menuitem(menu, "password", password, true)
icculus@12
   219
    end
icculus@12
   220
end
icculus@12
   221
secret_menuitem_builders["webforms.WebForm"] = build_secret_menuitem_webform
icculus@12
   222
icculus@12
   223
icculus@12
   224
local function build_secret_menuitem_password(menu, info, secure)
icculus@12
   225
    build_secret_menuitem(menu, "password", secure.password, true)
icculus@12
   226
end
icculus@12
   227
secret_menuitem_builders["passwords.Password"] = build_secret_menuitem_password
icculus@12
   228
icculus@12
   229
icculus@12
   230
local function build_secret_menuitem_bankacctus(menu, info, secure)
icculus@12
   231
    -- !!! FIXME: there's more data than this in a generic dictionary.
icculus@12
   232
    build_secret_menuitem(menu, "Account type", secure.accountType)
icculus@12
   233
    build_secret_menuitem(menu, "Routing number", secure.routingNo)
icculus@12
   234
    build_secret_menuitem(menu, "Account number", secure.accountNo)
icculus@12
   235
    build_secret_menuitem(menu, "Bank name", secure.bankName)
icculus@12
   236
    build_secret_menuitem(menu, "Owner", secure.owner)
icculus@41
   237
    build_secret_menuitem(menu, "SWIFT code", secure.swift)
icculus@41
   238
    build_secret_menuitem(menu, "PIN", secure.telephonePin)
icculus@12
   239
end
icculus@12
   240
secret_menuitem_builders["wallet.financial.BankAccountUS"] = build_secret_menuitem_bankacctus
icculus@12
   241
icculus@12
   242
icculus@12
   243
local function build_secret_menuitem_driverslic(menu, info, secure)
icculus@42
   244
    -- !!! FIXME: there's more data for this menuitem than this, in a generic dictionary.
icculus@42
   245
icculus@42
   246
    local birthdate = nil
icculus@42
   247
    if secure.birthdate_yy ~= nil then
icculus@42
   248
        birthdate = secure.birthdate_yy
icculus@42
   249
        if secure.birthdate_mm ~= nil then
icculus@42
   250
            birthdate = birthdate .. "/" .. string.sub("00" .. secure.birthdate_mm, -2)
icculus@42
   251
            if secure.birthdate_dd ~= nil then
icculus@42
   252
                birthdate = birthdate .. "/" .. string.sub("00" .. secure.birthdate_dd, -2)
icculus@42
   253
            end
icculus@42
   254
        end
icculus@42
   255
    end
icculus@42
   256
icculus@43
   257
    local expiredate = nil
icculus@42
   258
    if secure.expiry_date_yy ~= nil then
icculus@42
   259
        expiredate = secure.expiry_date_yy
icculus@42
   260
        if secure.expiry_date_mm ~= nil then
icculus@42
   261
            expiredate = expiredate .. "/" .. string.sub("00" .. secure.expiry_date_mm, -2)
icculus@42
   262
            if secure.expiry_date_dd ~= nil then
icculus@42
   263
                expiredate = expiredate .. "/" .. string.sub("00" .. secure.expiry_date_dd, -2)
icculus@42
   264
            end
icculus@42
   265
        end
icculus@42
   266
    end
icculus@42
   267
icculus@12
   268
    build_secret_menuitem(menu, "License number", secure.number)
icculus@12
   269
    build_secret_menuitem(menu, "Class", secure.class)
icculus@12
   270
    build_secret_menuitem(menu, "Expires", expiredate)
icculus@12
   271
    build_secret_menuitem(menu, "State", secure.state)
icculus@12
   272
    build_secret_menuitem(menu, "Country", secure.country)
icculus@12
   273
    build_secret_menuitem(menu, "Conditions", secure.conditions)
icculus@12
   274
    build_secret_menuitem(menu, "Full name", secure.fullname)
icculus@12
   275
    build_secret_menuitem(menu, "Address", secure.address)
icculus@12
   276
    build_secret_menuitem(menu, "Gender", secure.sex)
icculus@12
   277
    build_secret_menuitem(menu, "Birthdate", birthdate)
icculus@12
   278
    build_secret_menuitem(menu, "Height", secure.height)
icculus@12
   279
end
icculus@12
   280
secret_menuitem_builders["wallet.government.DriversLicense"] = build_secret_menuitem_driverslic
icculus@12
   281
icculus@12
   282
icculus@12
   283
local function build_secret_menuitem_membership(menu, info, secure)
icculus@12
   284
    -- !!! FIXME: there's more data than this in a generic dictionary.
icculus@12
   285
    build_secret_menuitem(menu, "Membership number", secure.membership_no)
icculus@12
   286
end
icculus@12
   287
secret_menuitem_builders["wallet.membership.Membership"] = build_secret_menuitem_membership
icculus@12
   288
icculus@12
   289
icculus@12
   290
local function build_secret_menuitem_creditcard(menu, info, secure)
icculus@12
   291
    -- !!! FIXME: there's more data than this in a generic dictionary.
icculus@12
   292
    local expiredate = secure.expiry_yy .. "/" .. string.sub("00" .. secure.expiry_mm, -2)
icculus@12
   293
    build_secret_menuitem(menu, "Type", secure.type)
icculus@12
   294
    build_secret_menuitem(menu, "CC number", secure.ccnum, true)
icculus@12
   295
    build_secret_menuitem(menu, "CVV", secure.cvv, true)
icculus@38
   296
    build_secret_menuitem(menu, "Expires", expiredate)
icculus@12
   297
    build_secret_menuitem(menu, "Card holder", secure.cardholder)
icculus@12
   298
    build_secret_menuitem(menu, "Bank", secure.bank)
icculus@12
   299
end
icculus@12
   300
secret_menuitem_builders["wallet.financial.CreditCard"] = build_secret_menuitem_creditcard
icculus@12
   301
icculus@49
   302
icculus@25
   303
local function build_secret_menuitem_securenote(menu, info, secure)
icculus@25
   304
    build_secret_menuitem(menu, "Notes", secure.notesPlain, true)
icculus@25
   305
end
icculus@25
   306
secret_menuitem_builders["securenotes.SecureNote"] = build_secret_menuitem_securenote
icculus@12
   307
icculus@49
   308
icculus@49
   309
local function build_secret_menuitem_ssnus(menu, info, secure)
icculus@49
   310
    build_secret_menuitem(menu, "Name", secure.name, false)
icculus@49
   311
    build_secret_menuitem(menu, "SSN", secure.number, true)
icculus@49
   312
end
icculus@49
   313
secret_menuitem_builders["wallet.government.SsnUS"] = build_secret_menuitem_ssnus
icculus@49
   314
icculus@49
   315
icculus@49
   316
local function build_secret_menuitem_router(menu, info, secure)
icculus@49
   317
    build_secret_menuitem(menu, "Name", secure.name, false)
icculus@49
   318
    build_secret_menuitem(menu, "Password", secure.password, true)
icculus@49
   319
end
icculus@49
   320
secret_menuitem_builders["wallet.computer.Router"] = build_secret_menuitem_router
icculus@49
   321
icculus@49
   322
icculus@17
   323
local function build_secret_menuitems(info, menu)
icculus@12
   324
    local metadata = load_json(basedir .. "/" .. info.uuid .. ".1password")
icculus@40
   325
    if (metadata == nil) or (next(metadata) == nil) then  -- the "next" trick tests if table is empty.
icculus@12
   326
        return
icculus@12
   327
    end
icculus@12
   328
robbie@21
   329
    local securityLevel = metadata.securityLevel
robbie@21
   330
    if securityLevel == nil then
icculus@36
   331
        securityLevel = metadata.openContents.securityLevel
robbie@21
   332
    end
icculus@30
   333
    --print("title: " .. metadata.title)
robbie@21
   334
    if securityLevel == nil then
robbie@21
   335
        --print("can't find security level, assuming SL5" .. metadata.title)
robbie@21
   336
        securityLevel = "SL5"
robbie@21
   337
    end
robbie@21
   338
robbie@21
   339
    local plaintext = decryptBase64UsingKey(metadata.encrypted, loadKey(securityLevel, password))
icculus@12
   340
    if plaintext == nil then
icculus@12
   341
        return
icculus@12
   342
    end
icculus@12
   343
icculus@12
   344
    local secure = load_json_str(plaintext, info.uuid)
icculus@12
   345
    if secure == nil then
icculus@12
   346
        return
icculus@12
   347
    end
icculus@12
   348
    --dumptable("secure " .. info.name, secure)
icculus@12
   349
icculus@45
   350
    local menuitem = appendMenuItem(menu, info.name)
icculus@12
   351
icculus@12
   352
    if secret_menuitem_builders[info.type] == nil then
icculus@12
   353
        print("WARNING: don't know how to handle items of type " .. info.type)
icculus@12
   354
        dumptable("secure " .. info.type .. " (" .. info.name .. ")", secure)
icculus@12
   355
        return
icculus@12
   356
    end
icculus@12
   357
icculus@35
   358
    if metadata.faveIndex ~= nil then
icculus@35
   359
        --dumptable("fave metadata " .. info.name, metadata)
icculus@35
   360
        faveitems[metadata.faveIndex] = { info=info, secure=secure }
icculus@35
   361
    end
icculus@35
   362
icculus@45
   363
    local submenu = makeMenu()
icculus@12
   364
    secret_menuitem_builders[info.type](submenu, info, secure)
icculus@45
   365
    setMenuItemSubmenu(menuitem, submenu)
icculus@8
   366
end
icculus@8
   367
icculus@17
   368
local function prepItems()
icculus@17
   369
    items = {}
icculus@17
   370
    local contents = loadContents()
icculus@44
   371
    if contents == nil then
icculus@44
   372
        return false
icculus@44
   373
    end
icculus@17
   374
    for i,v in ipairs(contents) do
icculus@17
   375
        local t = v[2]
icculus@17
   376
        if items[t] == nil then
icculus@17
   377
            items[t] = {}
icculus@17
   378
        end
icculus@17
   379
        local bucket = items[t]
icculus@17
   380
        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.
icculus@17
   381
    end
icculus@44
   382
    return true
icculus@17
   383
end
icculus@17
   384
icculus@18
   385
local passwordUnlockTime = nil
icculus@18
   386
icculus@29
   387
local function lockKeychain()
icculus@29
   388
    -- lose the existing password and key, prompt user again.
icculus@29
   389
    password = argv[2]  -- might be nil, don't reset if on command line.
icculus@29
   390
    keys["SL5"] = nil
icculus@29
   391
    passwordUnlockTime = nil
icculus@29
   392
    setPowermateLED(false)
icculus@45
   393
icculus@45
   394
    -- kill the popup if it exists.
icculus@45
   395
    if (keyhookGuiMenus ~= nil) and (keyhookGuiMenus[1] ~= nil) then
icculus@45
   396
        guiDestroyMenu(keyhookGuiMenus[1])
icculus@45
   397
    end
icculus@29
   398
end
icculus@29
   399
icculus@29
   400
function pumpLua()  -- not local! Called from C!
icculus@29
   401
    -- !!! FIXME: this should lose the key in RAM and turn off the Powermate
icculus@29
   402
    -- !!! FIXME:  LED when the time expires instead of if the time has
icculus@29
   403
    -- !!! FIXME:  expired when the user is trying to get at the keychain.
icculus@29
   404
    if passwordUnlockTime ~= nil then
icculus@29
   405
        local now = os.time()
icculus@29
   406
        local maxTime = (15 * 60)  -- !!! FIXME: don't hardcode.
icculus@29
   407
        if os.difftime(now, passwordUnlockTime) > maxTime then
icculus@29
   408
            lockKeychain()
icculus@29
   409
        end
icculus@29
   410
    end
icculus@29
   411
end
icculus@29
   412
icculus@45
   413
function escapePressed()  -- not local! Called from C!
icculus@45
   414
    if keyhookGuiMenus[1] then
icculus@45
   415
        guiDestroyMenu(keyhookGuiMenus[1])
icculus@45
   416
    end
icculus@45
   417
end
icculus@45
   418
icculus@45
   419
icculus@45
   420
local buildGuiMenuList
icculus@45
   421
icculus@45
   422
local function spawnSubMenu(button, submenu, depth)
icculus@45
   423
    local guimenu = guiCreateSubMenu(button)
icculus@45
   424
icculus@45
   425
    for i = #keyhookGuiMenus, depth, -1 do
icculus@45
   426
        if keyhookGuiMenus[i] then
icculus@45
   427
            --print("Destroying conflicting submenu at depth " .. i)
icculus@45
   428
            guiDestroyMenu(keyhookGuiMenus[i])
icculus@45
   429
            keyhookGuiMenus[i] = nil
icculus@45
   430
        end
icculus@45
   431
    end
icculus@45
   432
icculus@45
   433
    --print("New submenu at depth " .. depth)
icculus@45
   434
    keyhookGuiMenus[depth] = guimenu
icculus@45
   435
icculus@45
   436
    buildGuiMenuList(guimenu, submenu)
icculus@45
   437
    guiShowWindow(guimenu)
icculus@45
   438
end
icculus@45
   439
icculus@45
   440
local function buildGuiMenuItem(guimenu, item)
icculus@45
   441
    local cb = item["callback"]
icculus@45
   442
    if cb == nil then
icculus@45
   443
        local submenu = item["submenu"]
icculus@45
   444
        local depth = #keyhookGuiMenus+1
icculus@45
   445
        cb = function (button)
icculus@45
   446
            return spawnSubMenu(button, submenu, depth)
icculus@45
   447
        end
icculus@45
   448
    end
icculus@46
   449
    guiAddMenuItem(guimenu, item["text"], item["checked"], cb)
icculus@45
   450
end
icculus@45
   451
icculus@45
   452
buildGuiMenuList = function(guimenu, list)
icculus@45
   453
    for i,v in ipairs(list) do
icculus@45
   454
        buildGuiMenuItem(guimenu, v)
icculus@45
   455
    end
icculus@45
   456
end
icculus@45
   457
icculus@45
   458
local function buildSearchResultsMenuCategory(guimenu, menu, str)
icculus@45
   459
    local submenu = menu["submenu"]
icculus@45
   460
    if not submenu then return end
icculus@45
   461
icculus@45
   462
    local name = menu["text"]
icculus@45
   463
    -- !!! FIXME: hacky. We should really list favorites first anyhow.
icculus@45
   464
    if name == "Favorites" then return end
icculus@45
   465
icculus@45
   466
    for i,v in ipairs(submenu) do
icculus@45
   467
        if string.find(string.lower(v["text"]), str, 1, true) ~= nil then
icculus@45
   468
            buildGuiMenuItem(guimenu, v)
icculus@45
   469
        end
icculus@45
   470
    end
icculus@45
   471
end
icculus@45
   472
icculus@45
   473
local function buildSearchResultsMenuList(guimenu, topmenu, str)
icculus@45
   474
    for i,v in ipairs(topmenu) do
icculus@45
   475
        buildSearchResultsMenuCategory(guimenu, v, str)
icculus@45
   476
    end
icculus@45
   477
end
icculus@45
   478
icculus@45
   479
local function searchEntryChanged(guimenu, str, topmenu)
icculus@45
   480
    --print("search changed to '" .. str .. "' ...")
icculus@45
   481
    guiRemoveAllMenuItems(guimenu)
icculus@45
   482
    if str == "" then
icculus@45
   483
        buildGuiMenuList(guimenu, topmenu)
icculus@45
   484
    else
icculus@45
   485
        buildSearchResultsMenuList(guimenu, topmenu, string.lower(str))
icculus@45
   486
    end
icculus@45
   487
    guiShowWindow(guimenu)
icculus@45
   488
end
icculus@45
   489
icculus@45
   490
local function handleMenuDestroyed()
icculus@45
   491
    --print("Destroying main menu...")
icculus@45
   492
    for i,v in ipairs(keyhookGuiMenus) do
icculus@45
   493
        if i > 1 then
icculus@45
   494
            guiDestroyMenu(v)
icculus@45
   495
        end
icculus@45
   496
    end
icculus@45
   497
    keyhookGuiMenus = nil
icculus@45
   498
    keyhookRunning = false
icculus@45
   499
icculus@45
   500
    runGarbageCollector()
icculus@45
   501
end
icculus@45
   502
icculus@45
   503
local function launchGuiMenu(topmenu)
icculus@45
   504
    local guimenu = guiCreateTopLevelMenu("1pass",
icculus@45
   505
icculus@45
   506
        function(guimenu, str) -- search text changed callback
icculus@45
   507
            return searchEntryChanged(guimenu, str, topmenu)
icculus@45
   508
        end,
icculus@45
   509
icculus@45
   510
        function()  -- window destroyed callback
icculus@45
   511
            handleMenuDestroyed()
icculus@45
   512
        end
icculus@45
   513
    )
icculus@45
   514
    keyhookGuiMenus = {}
icculus@45
   515
    keyhookGuiMenus[#keyhookGuiMenus+1] = guimenu
icculus@45
   516
    buildGuiMenuList(guimenu, topmenu)
icculus@45
   517
    guiShowWindow(guimenu)
icculus@45
   518
end
icculus@29
   519
icculus@46
   520
local trustedDisks = {}
icculus@46
   521
icculus@46
   522
local function getTrustedDiskChecksumPath(mntpoint)
icculus@46
   523
    return mntpoint .. "/1pass.dat"
icculus@46
   524
end
icculus@46
   525
icculus@46
   526
local function getTrustedDiskChecksum(mntpoint)
icculus@46
   527
    local f = io.open(getTrustedDiskChecksumPath(mntpoint), "rb")
icculus@46
   528
    if f == nil then
icculus@46
   529
        return nil
icculus@46
   530
    end
icculus@46
   531
icculus@46
   532
    local str = f:read("*a")
icculus@46
   533
    f:close()
icculus@46
   534
    return calcSha256(str)
icculus@46
   535
end
icculus@46
   536
icculus@46
   537
local function choseTrustedDisk(mntpoint)
icculus@46
   538
    if trustedDisks[mntpoint] ~= nil then
icculus@46
   539
        trustedDisks[mntpoint] = nil  -- no longer check existing trusted disk.
icculus@46
   540
    else
icculus@46
   541
        -- !!! FIXME: probably needs a message box if this fails.
icculus@46
   542
        local checksum = getTrustedDiskChecksum(mntpoint)
icculus@46
   543
        -- No checksum file yet? Generate and write out a random string.
icculus@46
   544
        if checksum == nil then
icculus@46
   545
            local f = io.open("/dev/urandom", "rb")
icculus@46
   546
            if f ~= nil then
icculus@46
   547
                local str = f:read(4096)
icculus@46
   548
                f:close()
icculus@46
   549
                if (str ~= nil) and (#str == 4096) then
icculus@46
   550
                    f = io.open(getTrustedDiskChecksumPath(mntpoint), "wb")
icculus@46
   551
                    if f ~= nil then
icculus@46
   552
                        if f:write(str) and f:flush() then
icculus@46
   553
                            checksum = calcSha256(str)
icculus@46
   554
                        end
icculus@46
   555
                        f:close()
icculus@46
   556
                    end
icculus@46
   557
                end
icculus@46
   558
            end
icculus@46
   559
        end
icculus@46
   560
        trustedDisks[mntpoint] = checksum
icculus@46
   561
    end
icculus@46
   562
icculus@46
   563
    -- kill the popup if it exists.
icculus@46
   564
    -- !!! FIXME: put this in its own function, this is a copy/paste from elsewhere.
icculus@46
   565
    if (keyhookGuiMenus ~= nil) and (keyhookGuiMenus[1] ~= nil) then
icculus@46
   566
        guiDestroyMenu(keyhookGuiMenus[1])
icculus@46
   567
    end
icculus@46
   568
end
icculus@46
   569
icculus@46
   570
local function buildTrustedDeviceMenu()
icculus@46
   571
    local menu = makeMenu()
icculus@46
   572
    local disks = getMountedDisks()  -- this is a C function.
icculus@46
   573
icculus@46
   574
    table.sort(disks, function(a, b) return a < b end)
icculus@46
   575
    for i,v in ipairs(disks) do
icculus@46
   576
        local item = appendMenuItem(menu, v, function() choseTrustedDisk(v) end)
icculus@46
   577
        if trustedDisks[v] ~= nil then
icculus@46
   578
            setMenuItemChecked(item, true)
icculus@46
   579
        end
icculus@46
   580
    end
icculus@46
   581
icculus@46
   582
    return menu
icculus@46
   583
end
icculus@46
   584
icculus@17
   585
function keyhookPressed()  -- not local! Called from C!
icculus@45
   586
    --print("keyhookPressed: running==" .. tostring(keyhookRunning))
icculus@45
   587
    if keyhookRunning then
icculus@45
   588
        return
icculus@45
   589
    end
icculus@17
   590
icculus@17
   591
    keyhookRunning = true
icculus@17
   592
icculus@46
   593
    local allowaccess = true;
icculus@46
   594
    for mntpoint,checksum in pairs(trustedDisks) do
icculus@46
   595
        if getTrustedDiskChecksum(mntpoint) ~= checksum then
icculus@46
   596
            allowaccess = false
icculus@46
   597
            break
icculus@46
   598
        end
icculus@46
   599
    end
icculus@46
   600
icculus@46
   601
    if not allowaccess then
icculus@46
   602
        -- !!! FIXME: probably needs a message box if this happens.
icculus@46
   603
        keyhookRunning = false
icculus@46
   604
        return
icculus@46
   605
    end
icculus@46
   606
icculus@17
   607
    while password == nil do
icculus@17
   608
        password = runGuiPasswordPrompt(getHint())
icculus@17
   609
        if password == nil then
icculus@17
   610
            keyhookRunning = false
icculus@17
   611
            return
icculus@17
   612
        end
icculus@17
   613
        if loadKey("SL5", password) == nil then
icculus@17
   614
            password = nil  -- wrong password
icculus@17
   615
            local start = os.time()  -- cook the CPU for three seconds.
icculus@17
   616
            local now = start
icculus@17
   617
            while os.difftime(now, start) < 3 do
icculus@17
   618
                now = os.time()
icculus@17
   619
            end
icculus@18
   620
        else
icculus@18
   621
            passwordUnlockTime = os.time()
icculus@28
   622
            setPowermateLED(true)
icculus@17
   623
        end
icculus@17
   624
    end
icculus@17
   625
icculus@44
   626
    if not prepItems() then
icculus@44
   627
        keyhookRunning = false
icculus@44
   628
        return
icculus@44
   629
    end
icculus@17
   630
icculus@45
   631
    local topmenu = makeMenu()
icculus@45
   632
    local favesmenu = makeMenu()
icculus@46
   633
    local securitymenu = makeMenu()
icculus@35
   634
    faveitems = {}
icculus@35
   635
icculus@45
   636
    setMenuItemSubmenu(appendMenuItem(topmenu, "Favorites"), favesmenu)
icculus@46
   637
    setMenuItemSubmenu(appendMenuItem(topmenu, "Security"), securitymenu)
icculus@18
   638
icculus@46
   639
    appendMenuItem(securitymenu, "Lock keychain now", function() lockKeychain() end)
icculus@46
   640
    setMenuItemSubmenu(appendMenuItem(securitymenu, "Require trusted device"), buildTrustedDeviceMenu())
icculus@18
   641
icculus@17
   642
    for orderi,type in ipairs(passwordTypeOrdering) do
icculus@17
   643
        local bucket = items[type]
robbie@21
   644
        if bucket ~= nil then
robbie@21
   645
            local realname = passwordTypeNameMap[type]
robbie@21
   646
            if realname == nil then
robbie@21
   647
                realname = type
robbie@21
   648
            end
icculus@45
   649
            local menuitem = appendMenuItem(topmenu, realname)
icculus@45
   650
            local submenu = makeMenu()
robbie@21
   651
            table.sort(bucket, function(a, b) return a.name < b.name end)
robbie@21
   652
            for i,v in pairs(bucket) do
robbie@21
   653
                build_secret_menuitems(v, submenu)
robbie@21
   654
            end
icculus@45
   655
            setMenuItemSubmenu(menuitem, submenu)
robbie@21
   656
        else
icculus@37
   657
            --print("no bucket found for item type '" .. type .. "'")
icculus@17
   658
        end
icculus@17
   659
    end
icculus@35
   660
    
icculus@35
   661
    -- This favepairs stuff is obnoxious.
icculus@35
   662
    local function favepairs(t)
icculus@35
   663
        local a = {}
icculus@35
   664
        for n in pairs(t) do table.insert(a, n) end
icculus@35
   665
        table.sort(a)
icculus@35
   666
        local i = 0
icculus@35
   667
        local iter = function()
icculus@35
   668
            i = i + 1
icculus@35
   669
            if a[i] == nil then
icculus@35
   670
                return nil
icculus@35
   671
            else
icculus@35
   672
                return a[i], t[a[i]]
icculus@35
   673
            end
icculus@35
   674
        end
icculus@35
   675
        return iter
icculus@35
   676
    end
icculus@35
   677
icculus@35
   678
    for i,v in favepairs(faveitems) do
icculus@35
   679
        --dumptable("fave " .. i, v)
icculus@45
   680
        local menuitem = appendMenuItem(favesmenu, v.info.name)
icculus@45
   681
        local submenu = makeMenu()
icculus@35
   682
        secret_menuitem_builders[v.info.type](submenu, v.info, v.secure)
icculus@45
   683
        setMenuItemSubmenu(menuitem, submenu)
icculus@35
   684
    end
icculus@35
   685
icculus@35
   686
    favepairs = nil
icculus@35
   687
    faveitems = nil
icculus@17
   688
icculus@45
   689
    launchGuiMenu(topmenu)
icculus@17
   690
end
icculus@17
   691
icculus@6
   692
icculus@1
   693
-- Mainline!
icculus@1
   694
icculus@7
   695
--for i,v in ipairs(argv) do
icculus@7
   696
--    print("argv[" .. i .. "] = " .. v)
icculus@7
   697
--end
icculus@7
   698
icculus@17
   699
-- !!! FIXME: message box, exit if basedir is wack.
icculus@44
   700
local f = io.open(basedir .. "/contents.js", "rb")
icculus@44
   701
if f == nil then
icculus@44
   702
    print("ERROR: Couldn't read your 1Password keychain in '" .. basedir .. "'.")
icculus@44
   703
    print("ERROR: Please make sure it exists and you have permission to access it.")
icculus@44
   704
    print("ERROR: (maybe you need to run 'ln -s ~/Dropbox/1Password' here?")
icculus@44
   705
    print("ERROR: Giving up for now.")
icculus@44
   706
    os.exit(1)
icculus@44
   707
end
icculus@44
   708
f:close()
icculus@44
   709
icculus@17
   710
-- !!! FIXME: this can probably happen in C now (the Lua mainline is basically gone now).
icculus@28
   711
setPowermateLED(false)  -- off by default
icculus@27
   712
print("Now waiting for the magic key combo (probably Alt-Meta-\\) ...")
icculus@11
   713
giveControlToGui()
icculus@11
   714
icculus@0
   715
-- end of 1pass.lua ...
icculus@0
   716