Hardware authentication for Linux using ordinary USB Flash Drives.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

275 lines
8.0 KiB

18 years ago
18 years ago
18 years ago
9 years ago
18 years ago
18 years ago
9 years ago
9 years ago
18 years ago
9 years ago
9 years ago
18 years ago
9 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
9 years ago
18 years ago
18 years ago
18 years ago
17 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
9 years ago
  1. #!/usr/bin/env python2
  2. #
  3. # Copyright (c) 2003-2007 Andrea Luzzardi <scox@sig11.org>
  4. #
  5. # This file is part of the pam_usb project. pam_usb is free software;
  6. # you can redistribute it and/or modify it under the terms of the GNU General
  7. # Public License version 2, as published by the Free Software Foundation.
  8. #
  9. # pam_usb is distributed in the hope that it will be useful, but WITHOUT ANY
  10. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  12. # details.
  13. #
  14. # You should have received a copy of the GNU General Public License along with
  15. # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
  16. # Street, Fifth Floor, Boston, MA 02110-1301 USA.
  17. import sys
  18. import os
  19. import gi
  20. gi.require_version('UDisks', '2.0')
  21. from gi.repository import UDisks
  22. from xml.dom import minidom
  23. class Device:
  24. def __init__(self, udi):
  25. self.__udi = udi
  26. deviceObj = udisksObjectManager.get_object(udi)
  27. driveObj = deviceObj.get_drive()
  28. if not driveObj.get_property('removable'):
  29. # Workaround for removable devices with fixed media (such as SD cards)
  30. if not "mmcblk" in udi:
  31. raise Exception, 'Not a removable device'
  32. self.vendor = driveObj.get_property('vendor')
  33. self.product = driveObj.get_property('model')
  34. self.serialNumber = driveObj.get_property('serial')
  35. if len(self.volumes()) < 1:
  36. raise Exception, 'Device does not contain any volume'
  37. def volumes(self):
  38. vols = []
  39. for udi in [o.get_object_path() for o in udisksObjectManager.get_objects() if o.get_block()]:
  40. obj = udisks.get_object(udi)
  41. blockObj = obj.get_block()
  42. if blockObj.get_property('drive') != self.__udi:
  43. continue
  44. if not obj.get_filesystem():
  45. continue
  46. vols.append({'uuid' : blockObj.get_property('id-uuid'),
  47. 'device' : blockObj.get_property('device')})
  48. return vols
  49. def __repr__(self):
  50. if self.product is not None:
  51. return "%s %s (%s)" % (self.vendor, self.product, self.serialNumber)
  52. return self.serialNumber
  53. def listOptions(question, options, autodetect = True):
  54. if autodetect == True and len(options) == 1:
  55. print question
  56. print "* Using \"%s\" (only option)" % options[0]
  57. print
  58. return 0
  59. while True:
  60. try:
  61. print question
  62. for i in range(len(options)):
  63. print "%d) %s" % (i, options[i])
  64. print
  65. sys.stdout.write('[%s-%s]: ' % (0, len(options) - 1))
  66. optionId = int(sys.stdin.readline())
  67. print
  68. if optionId not in range(len(options)):
  69. raise Exception
  70. return optionId
  71. except KeyboardInterrupt: sys.exit()
  72. except Exception: pass
  73. else: break
  74. def writeConf(options, doc):
  75. try:
  76. f = open(options['configFile'], 'w')
  77. doc.writexml(f)
  78. f.close()
  79. except Exception, err:
  80. print 'Unable to save %s: %s' % (options['configFile'], err)
  81. sys.exit(1)
  82. else:
  83. print 'Done.'
  84. def shouldSave(options, items):
  85. print "\n".join(["%s\t\t: %s" % item for item in items])
  86. print
  87. print 'Save to %s ?' % options['configFile']
  88. sys.stdout.write('[Y/n] ')
  89. response = sys.stdin.readline().strip()
  90. if len(response) > 0 and response.lower() != 'y':
  91. sys.exit(1)
  92. def prettifyElement(element):
  93. tmp = minidom.parseString(element.toprettyxml())
  94. return tmp.lastChild
  95. def addUser(options):
  96. try:
  97. doc = minidom.parse(options['configFile'])
  98. except Exception, err:
  99. print 'Unable to read %s: %s' % (options['configFile'], err)
  100. sys.exit(1)
  101. devSection = doc.getElementsByTagName('devices')
  102. if len(devSection) == 0:
  103. print 'Malformed configuration file: No <devices> section found.'
  104. sys.exit(1)
  105. devicesObj = devSection[0].getElementsByTagName('device')
  106. if len(devicesObj) == 0:
  107. print 'No devices found.'
  108. print 'You must add a device (--add-device) before adding users'
  109. sys.exit(1)
  110. devices = []
  111. for device in devicesObj:
  112. devices.append(device.getAttribute('id'))
  113. device = devices[listOptions("Which device would you like to use for authentication ?",
  114. devices)]
  115. shouldSave(options, [
  116. ('User', options['userName']),
  117. ('Device', device)
  118. ])
  119. users = doc.getElementsByTagName('users')
  120. user = doc.createElement('user')
  121. user.attributes['id'] = options['userName']
  122. e = doc.createElement('device')
  123. t = doc.createTextNode(device)
  124. e.appendChild(t)
  125. user.appendChild(e)
  126. users[0].appendChild(prettifyElement(user))
  127. writeConf(options, doc)
  128. def addDevice(options):
  129. devices = []
  130. for udi in [o.get_object_path() for o in udisksObjectManager.get_objects() if o.get_drive()]:
  131. try:
  132. if options['verbose']:
  133. print 'Inspecting %s' % udi
  134. devices.append(Device(udi))
  135. except Exception, ex:
  136. if options['verbose']:
  137. print "\tInvalid: %s" % ex
  138. pass
  139. else:
  140. if options['verbose']:
  141. print "\tValid"
  142. if len(devices) == 0:
  143. print 'No devices detected. Try running in verbose (-v) mode to see what\'s going on.'
  144. sys.exit()
  145. device = devices[listOptions("Please select the device you wish to add.", devices)]
  146. volumes = device.volumes()
  147. volume = volumes[listOptions("Which volume would you like to use for " \
  148. "storing data ?",
  149. ["%s (UUID: %s)" % (volume['device'],
  150. volume['uuid'] or "<UNDEFINED>")
  151. for volume in volumes]
  152. )]
  153. if volume['uuid'] == '':
  154. print 'WARNING: No UUID detected for device %s. One time pads will be disabled.' % volume['device']
  155. shouldSave(options,[
  156. ('Name', options['deviceName']),
  157. ('Vendor', device.vendor or "Unknown"),
  158. ('Model', device.product or "Unknown"),
  159. ('Serial', device.serialNumber),
  160. ('UUID', volume['uuid'] or "Unknown")
  161. ])
  162. try:
  163. doc = minidom.parse(options['configFile'])
  164. except Exception, err:
  165. print 'Unable to read %s: %s' % (options['configFile'], err)
  166. sys.exit(1)
  167. devs = doc.getElementsByTagName('devices')
  168. # Check that the id of the device to add is not already present in the configFile
  169. for devices in devs:
  170. for device in devices.getElementsByTagName("device"):
  171. if device.getAttribute("id") == options['deviceName']:
  172. msg = [ '\nWARNING: A device node already exits for new device \'%s\'.',
  173. '\nTo proceed re-run --add-device using a different name or remove the existing entry in %s.' ]
  174. print '\n'.join(msg) % (options['deviceName'], options['configFile'])
  175. sys.exit(2)
  176. dev = doc.createElement('device')
  177. dev.attributes['id'] = options['deviceName']
  178. for name, value in (('vendor', device.vendor),
  179. ('model', device.product),
  180. ('serial', device.serialNumber),
  181. ('volume_uuid', volume['uuid'])):
  182. if value is None or value == '':
  183. continue
  184. e = doc.createElement(name)
  185. t = doc.createTextNode(value)
  186. e.appendChild(t)
  187. dev.appendChild(e)
  188. # Disable one time pads if there's no device UUID
  189. if volume['uuid'] == '':
  190. e = doc.createElement('option')
  191. e.setAttribute('name', 'one_time_pad')
  192. e.appendChild(doc.createTextNode('false'))
  193. dev.appendChild(e)
  194. devs[0].appendChild(prettifyElement(dev))
  195. writeConf(options, doc)
  196. def usage():
  197. print 'Usage: %s [--help] [--verbose] [--config=path] [--add-user=name | --add-device=name]' % os.path.basename(__file__)
  198. sys.exit(1)
  199. import getopt
  200. try:
  201. opts, args = getopt.getopt(sys.argv[1:], "hvd:nu:c:",
  202. ["help", "verbose", "add-device=", "add-user=", "config="])
  203. except getopt.GetoptError:
  204. usage()
  205. if len(args) != 0:
  206. usage()
  207. options = { 'deviceName' : None, 'userName' : None,
  208. 'configFile' : '/etc/security/pam_usb.conf', 'verbose' : False }
  209. for o, a in opts:
  210. if o in ("-h", "--help"):
  211. usage()
  212. if o in ("-v", "--verbose"):
  213. options['verbose'] = True
  214. if o in ("-d", "--add-device"):
  215. options['deviceName'] = a
  216. if o in ("-u", "--add-user"):
  217. options['userName'] = a
  218. if o in ("-c", "--config"):
  219. options['configFile'] = a
  220. if options['deviceName'] is not None and options['userName'] is not None:
  221. print 'You cannot use both --add-user and --add-device'
  222. usage()
  223. if options['deviceName'] is None and options['userName'] is None:
  224. usage()
  225. if options['deviceName'] is not None:
  226. udisks = UDisks.Client.new_sync()
  227. udisksObjectManager = udisks.get_object_manager()
  228. try:
  229. addDevice(options)
  230. except KeyboardInterrupt:
  231. sys.exit(1)
  232. if options['userName'] is not None:
  233. try:
  234. addUser(options)
  235. except KeyboardInterrupt:
  236. sys.exit(1)