netbank: Support hardware token authentication.
[fulbank.git] / netbank
CommitLineData
7f511828
FT
1#!/usr/bin/python3
2
6532632d 3import sys, os, getopt, pwd, operator
7f511828
FT
4from fulbank import auth
5
6sesstype = None
7sess = None
8
6532632d
FT
9def pfxmatch(pfx, item):
10 return str(item)[:len(pfx)] == pfx
11
12class ambiguous(LookupError):
13 def __init__(self, a, b):
14 super().__init__("ambigous match: %s and %s" % (a, b))
15 self.a = a
16 self.b = b
17
18def find(seq, *, item=None, test=None, match=None, key=None, default=LookupError):
7f511828
FT
19 if key is None:
20 key = lambda o: o
21 if match is None and item is not None:
6532632d
FT
22 match = lambda o: test(item, o)
23 if test is None:
24 test = operator.eq
25 found = None
7f511828
FT
26 for thing in seq:
27 if match(key(thing)):
6532632d
FT
28 if found is None:
29 found = thing
30 else:
31 if default is LookupError:
32 raise ambiguous(key(found), key(thing))
33 else:
34 return default
35 if found is not None:
36 return found
7f511828
FT
37 if default is LookupError:
38 raise LookupError()
39 else:
40 return default
41
42def usage(out):
df34dfd3 43 out.write("usage: netbank [-h] BANK-ID COMMAND [ARGS...]\n")
7f511828
FT
44
45def requiresess(fn):
46 def wrap(cmd, args):
47 if sess is None:
48 sys.stderr.write("netbank: %s: no current session\n" % (cmd))
49 sys.exit(1)
50 return fn(cmd, args)
51 return wrap
52
53commands = {}
54
55def cmd_login(cmd, args):
56 global sess
57 if len(args) < 1:
58 sys.stderr.write("usage: login TYPE\n")
59 sys.exit(1)
60 sess = sesstype.create()
61 if args[0] == "bankid":
a094b3b5
FT
62 authfun = sess.auth_bankid
63 elif args[0] == "token":
64 authfun = sess.auth_token
7f511828
FT
65 else:
66 sys.stderr.write("netbank: %s: unknown authentication type\n" % (args[0]))
67 sys.exit(1)
a094b3b5
FT
68 if len(args) < 2:
69 sys.stderr.write("usage: login bankid USER-ID\n")
70 sys.exit(1)
71 with auth.ttyconv() as conv:
72 try:
73 authfun(args[1], conv)
74 except auth.autherror as err:
75 sys.stderr.write("netbank: authentication failed: %s\n" % err)
76 sys.exit(1)
7f511828
FT
77commands["login"] = cmd_login
78
79@requiresess
80def cmd_logout(cmd, args):
d6d73d98 81 global sess
7f511828
FT
82 if sess is not None:
83 sess.close()
84 sess = None
85commands["logout"] = cmd_logout
86
87@requiresess
88def cmd_ping(cmd, args):
89 sess.keepalive()
90commands["ping"] = cmd_ping
91
92@requiresess
93def cmd_lsacct(cmd, args):
94 for acct in sess.accounts:
95 sys.stdout.write("%s (%s): %s\n" % (acct.number, acct.name, acct.balance))
96commands["lsacct"] = cmd_lsacct
97
98@requiresess
99def cmd_lstxn(cmd, args):
100 opts, args = getopt.getopt(args, "n:")
101 num = 10
102 for o, a in opts:
103 if o == "-n":
104 num = int(a)
105 if len(args) < 1:
106 sys.stderr.write("usage: lstxn [-n NUM] ACCOUNT\n")
107 sys.exit(1)
108 try:
6532632d
FT
109 acct = find(sess.accounts, item=args[0], key=lambda acct: acct.number, test=pfxmatch)
110 except ambiguous as exc:
111 sys.stderr.write("netbank: %s: ambiguous match between %s and %s\n" % (args[0], exc.a, exc.b))
112 sys.exit(1)
7f511828
FT
113 except LookupError:
114 sys.stderr.write("netbank: %s: no such account\n" % (args[0]))
115 sys.exit(1)
116 for i, txn in zip(range(num), acct.transactions()):
117 sys.stdout.write("%s %s: %s\n" % (txn.date.isoformat(), txn.value, txn.message))
118commands["lstxn"] = cmd_lstxn
119
120def main():
121 global sess, sesstype
122
123 opts, args = getopt.getopt(sys.argv[1:], "h")
124 for o, a in opts:
125 if o == "-h":
126 usage(sys.stdout)
127 sys.exit(0)
128 if len(args) < 2:
129 usage(sys.stderr)
130 sys.exit(1)
131
132 if args[0] == "fsb":
133 import fulbank.fsb
134 sesstype = fulbank.fsb.session
135 else:
136 sys.stderr.write("netbank: %s: unknown bank id\n" % (args[0]))
137 sys.exit(1)
138 sesspath = os.path.join(pwd.getpwuid(os.getuid()).pw_dir, ".cache/fulbank", args[0])
139 cmd = args[1]
140 args = args[2:]
141
142 if os.path.exists(sesspath):
143 sess = sesstype.load(sesspath)
144 else:
145 sess = None
146 if cmd in commands:
147 commands[cmd](cmd, args)
148 else:
149 sys.stderr.write("netbank: %s: unknown command\n" % (cmd))
150 sys.exit(1)
151 if sess is not None:
152 sessdir = os.path.dirname(sesspath)
153 if not os.path.isdir(sessdir):
154 os.makedirs(sessdir)
155 sess.save(sesspath)
156 else:
157 if os.path.exists(sesspath):
158 os.unlink(sesspath)
159
160try:
161 if __name__ == "__main__":
162 main()
163except KeyboardInterrupt:
164 sys.exit(1)