-
Notifications
You must be signed in to change notification settings - Fork 1
/
yubiswitch.py
executable file
·130 lines (112 loc) · 4.15 KB
/
yubiswitch.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env python3
# Python version of https://github.com/gsstark/yubiswitch-for-linux
import os
import sys
import subprocess
import argparse
class YubiKey:
def __init__(self, dev_id, product):
self.dev_id = dev_id
self.product = product
def is_active(self):
try:
return bool(os.lstat(
os.path.join('/sys/bus/usb/drivers/usbhid', self.dev_id)))
except FileNotFoundError:
return False
def activate(self):
'''
Activate the Yubikey. Return True if succeeded, False if the key is
already active. Raise an exception if it fails otherwise.
'''
if self.is_active():
return False
with open('/sys/bus/usb/drivers/usbhid/bind', 'w') as fd:
fd.write(self.dev_id)
return True
def deactivate(self):
'''
Deactivate the Yubikey. Return True if succeeded, False if the key is
already active. Raise an exception if it fails otherwise.
'''
if not self.is_active():
return False
with open('/sys/bus/usb/drivers/usbhid/unbind', 'w') as fd:
fd.write(self.dev_id)
return True
def __repr__(self):
return '<{c} (dev_id={d!r}, product={p!r})>'.format(
c=self.__class__.__name__,
d=self.dev_id,
p=self.product,
)
def get_yubikeys(debug=False):
yubikeys = []
basepath = '/sys/bus/usb/devices/'
if debug:
print('Scanning {p}'.format(p=basepath))
for dev_id in os.listdir(basepath):
manufacturer_path = os.path.join(basepath, dev_id, 'manufacturer')
if debug:
print('Opening manufacturer file {f}'.format(f=manufacturer_path))
try:
with open(manufacturer_path) as fd:
manufacturer = fd.read()
except (OSError, IOError) as exc:
continue
if manufacturer == 'Yubico\n':
if debug:
print('Found Yubico device at {p}, scanning sub-devices'
.format(p=manufacturer_path))
product_path = os.path.join(basepath, dev_id, 'product')
if debug:
print('Opening product file {p}'.format(p=product_path))
with open(product_path) as fd:
product = fd.read().strip()
for subdev in os.listdir(os.path.join(basepath, dev_id)):
if not os.path.isdir(os.path.join(basepath, dev_id, subdev)):
continue
if not set(subdev).issubset(set('0123456789-:.')):
continue
yubikeys.append(YubiKey(subdev, product))
return yubikeys
def rerun_as_root(argv=None):
print('Root access required, re-running with sudo')
if argv is None:
argv = sys.argv
cmd = ['sudo'] + argv
try:
sys.exit(subprocess.check_call(cmd))
except subprocess.CalledProcessError as exc:
sys.exit(exc.returncode)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('command', choices=['on', 'off', 'list'])
parser.add_argument('-d', '--debug', action='store_true',
help='Print debug output')
args = parser.parse_args()
if args.command in ('on', 'off') and os.geteuid() != 0:
rerun_as_root()
yubikeys = get_yubikeys(debug=args.debug)
if args.command == 'on':
for yubikey in yubikeys:
if yubikey.activate():
print('Yubikey activated successfully: {}'.format(yubikey))
else:
print('Yubikey already active: {}'.format(yubikey))
elif args.command == 'off':
for yubikey in yubikeys:
if yubikey.deactivate():
print('Yubikey deactivated successfully: {}'.format(yubikey))
else:
print('Yubikey already inactive: {}'.format(yubikey))
elif args.command == 'list':
for idx, yubikey in enumerate(yubikeys, 1):
print('{i}) {d:<10} {p} (active={s})'.format(
i=idx,
d=yubikey.dev_id,
p=yubikey.product,
s=yubikey.is_active(),
))
if __name__ == '__main__':
main()