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.

271 lines
7.7 KiB

17 years ago
  1. #!/usr/bin/env python
  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., 59 Temple
  16. # Place, Suite 330, Boston, MA 02111-1307 USA
  17. import dbus
  18. import sys
  19. import os
  20. from xml.dom import minidom
  21. class Device:
  22. def __init__(self, udi):
  23. self.__udi = udi
  24. self.__findStorageDevice()
  25. deviceObj = bus.get_object('org.freedesktop.Hal',
  26. udi)
  27. deviceProperties = deviceObj.GetAllProperties(
  28. dbus_interface = 'org.freedesktop.Hal.Device')
  29. self.vendor = deviceProperties['usb_device.vendor']
  30. self.product = deviceProperties['info.product']
  31. self.serialNumber = deviceProperties['usb_device.serial']
  32. if len(self.volumes()) < 1:
  33. raise Exception, '%s does not contain any volume' % self.__udi
  34. def __isChildOfDevice(self, udi):
  35. obj = bus.get_object('org.freedesktop.Hal', udi)
  36. properties = obj.GetAllProperties(
  37. dbus_interface = 'org.freedesktop.Hal.Device')
  38. if not properties.has_key('info.parent'):
  39. return False
  40. if properties.has_key('info.bus') and properties['info.bus'] == 'usb_device':
  41. return False
  42. if properties['info.parent'] == self.__udi:
  43. return True
  44. return self.__isChildOfDevice(properties['info.parent'])
  45. def __findStorageDevice(self):
  46. for child in halManager.FindDeviceByCapability('storage'):
  47. if self.__isChildOfDevice(child):
  48. self.__storageUdi = child
  49. return
  50. raise Exception, '%s is not a storage device.' % self.__udi
  51. def __repr__(self):
  52. return "%s %s (%s)" % (self.vendor, self.product, self.serialNumber)
  53. def volumes(self):
  54. vols = []
  55. for volume in halManager.FindDeviceByCapability('volume'):
  56. deviceObj = bus.get_object('org.freedesktop.Hal',
  57. volume)
  58. deviceProperties = deviceObj.GetAllProperties(
  59. dbus_interface = 'org.freedesktop.Hal.Device')
  60. if deviceProperties['block.storage_device'] != self.__storageUdi:
  61. continue
  62. vols.append({'uuid' : deviceProperties['volume.uuid'],
  63. 'device' : deviceProperties['block.device']})
  64. return vols
  65. def listOptions(question, options, autodetect = True):
  66. if autodetect == True and len(options) == 1:
  67. print question
  68. print "* Using \"%s\" (only option)" % options[0]
  69. print
  70. return 0
  71. while True:
  72. try:
  73. print question
  74. for i in range(len(options)):
  75. print "%d) %s" % (i, options[i])
  76. print
  77. sys.stdout.write('[%s-%s]: ' % (0, len(options) - 1))
  78. optionId = int(sys.stdin.readline())
  79. print
  80. if optionId not in range(len(options)):
  81. raise Exception
  82. return optionId
  83. except KeyboardInterrupt: sys.exit()
  84. except Exception: pass
  85. else: break
  86. def writeConf(options, doc):
  87. try:
  88. f = open(options['configFile'], 'w')
  89. doc.writexml(f)
  90. f.close()
  91. except Exception, err:
  92. print 'Unable to save %s: %s' % (options['configFile'], err)
  93. sys.exit(1)
  94. else:
  95. print 'Done.'
  96. def shouldSave(options, items):
  97. print "\n".join(["%s\t\t: %s" % item for item in items])
  98. print
  99. print 'Save to %s ?' % options['configFile']
  100. sys.stdout.write('[Y/n] ')
  101. response = sys.stdin.readline().strip()
  102. if len(response) > 0 and response.lower() != 'y':
  103. sys.exit(1)
  104. def prettifyElement(element):
  105. tmp = minidom.parseString(element.toprettyxml())
  106. return tmp.lastChild
  107. def addUser(options):
  108. try:
  109. doc = minidom.parse(options['configFile'])
  110. except Exception, err:
  111. print 'Unable to read %s: %s' % (options['configFile'], err)
  112. sys.exit(1)
  113. devSection = doc.getElementsByTagName('devices')
  114. if len(devSection) == 0:
  115. print 'Malformed configuration file: No <devices> section found.'
  116. sys.exit(1)
  117. devicesObj = devSection[0].getElementsByTagName('device')
  118. if len(devicesObj) == 0:
  119. print 'No devices found.'
  120. print 'You must add a device (--add-device) before adding users'
  121. sys.exit(1)
  122. devices = []
  123. for device in devicesObj:
  124. devices.append(device.getAttribute('id'))
  125. device = devices[listOptions("Which device would you like to use for authentication ?",
  126. devices)]
  127. shouldSave(options, [
  128. ('User', options['userName']),
  129. ('Device', device)
  130. ])
  131. users = doc.getElementsByTagName('users')
  132. user = doc.createElement('user')
  133. user.attributes['id'] = options['userName']
  134. e = doc.createElement('device')
  135. t = doc.createTextNode(device)
  136. e.appendChild(t)
  137. user.appendChild(e)
  138. users[0].appendChild(prettifyElement(user))
  139. writeConf(options, doc)
  140. def addDevice(options):
  141. devices = []
  142. for udi in halManager.FindDeviceStringMatch('info.bus', 'usb_device'):
  143. try:
  144. devices.append(Device(udi))
  145. except Exception, ex:
  146. pass
  147. if len(devices) == 0:
  148. print 'No devices detected.'
  149. sys.exit()
  150. device = devices[listOptions("Please select the device you wish to add.", devices)]
  151. volumes = device.volumes()
  152. volume = volumes[listOptions("Which volume would you like to use for " \
  153. "storing data ?",
  154. ["%s (UUID: %s)" % (volume['device'],
  155. volume['uuid'] or "<UNDEFINED>")
  156. for volume in volumes]
  157. )]
  158. if volume['uuid'] == '':
  159. print 'WARNING: No UUID detected for device %s. One time pads will be disabled.' % volume['device']
  160. shouldSave(options,[
  161. ('Name', options['deviceName']),
  162. ('Vendor', device.vendor),
  163. ('Model', device.product),
  164. ('Serial', device.serialNumber),
  165. ('UUID', volume['uuid'] or "<UNDEFINED>")
  166. ])
  167. try:
  168. doc = minidom.parse(options['configFile'])
  169. except Exception, err:
  170. print 'Unable to read %s: %s' % (options['configFile'], err)
  171. sys.exit(1)
  172. devs = doc.getElementsByTagName('devices')
  173. dev = doc.createElement('device')
  174. dev.attributes['id'] = options['deviceName']
  175. for name, value in (('vendor', device.vendor),
  176. ('model', device.product),
  177. ('serial', device.serialNumber),
  178. ('volume_uuid', volume['uuid'])):
  179. if value == '':
  180. continue
  181. e = doc.createElement(name)
  182. t = doc.createTextNode(value)
  183. e.appendChild(t)
  184. dev.appendChild(e)
  185. # Disable one time pads if there's no device UUID
  186. if volume['uuid'] == '':
  187. e = doc.createElement('option')
  188. e.setAttribute('name', 'one_time_pad')
  189. e.appendChild(doc.createTextNode('false'))
  190. dev.appendChild(e)
  191. devs[0].appendChild(prettifyElement(dev))
  192. writeConf(options, doc)
  193. def usage():
  194. print 'Usage: %s [--help] [--config=path] [--add-user=name | --add-device=name]' % os.path.basename(__file__)
  195. sys.exit(1)
  196. import getopt
  197. try:
  198. opts, args = getopt.getopt(sys.argv[1:], "hd:nu:c:",
  199. ["help", "add-device=", "add-user=", "config="])
  200. except getopt.GetoptError:
  201. usage()
  202. if len(args) != 0:
  203. usage()
  204. options = { 'deviceName' : None, 'userName' : None,
  205. 'configFile' : '/etc/pamusb.conf' }
  206. for o, a in opts:
  207. if o in ("-h", "--help"):
  208. usage()
  209. if o in ("-d", "--add-device"):
  210. options['deviceName'] = a
  211. if o in ("-u", "--add-user"):
  212. options['userName'] = a
  213. if o in ("-c", "--config"):
  214. options['configFile'] = a
  215. if options['deviceName'] is not None and options['userName'] is not None:
  216. print 'You cannot use both --add-user and --add-device'
  217. usage()
  218. if options['deviceName'] is None and options['userName'] is None:
  219. usage()
  220. if options['deviceName'] is not None:
  221. bus = dbus.SystemBus()
  222. halService = bus.get_object('org.freedesktop.Hal',
  223. '/org/freedesktop/Hal/Manager')
  224. halManager = dbus.Interface(halService, 'org.freedesktop.Hal.Manager')
  225. try:
  226. addDevice(options)
  227. except KeyboardInterrupt:
  228. sys.exit(1)
  229. if options['userName'] is not None:
  230. try:
  231. addUser(options)
  232. except KeyboardInterrupt:
  233. sys.exit(1)