From 07137a6ba9397180f6edb5b61a74bb6c18746811 Mon Sep 17 00:00:00 2001 From: David Adam Date: Fri, 22 Feb 2008 15:10:33 +0000 Subject: [PATCH 1/1] Add MIFARE interface support, including LDAP layer. All enquiries to . --- sql-edition/servers/LDAPConnector.py | 74 +++++++++++ sql-edition/servers/MIFAREClient.py | 37 ++++++ sql-edition/servers/MIFAREDriver.py | 184 +++++++++++++++++++++++++++ 3 files changed, 295 insertions(+) create mode 100644 sql-edition/servers/LDAPConnector.py create mode 100644 sql-edition/servers/MIFAREClient.py create mode 100644 sql-edition/servers/MIFAREDriver.py diff --git a/sql-edition/servers/LDAPConnector.py b/sql-edition/servers/LDAPConnector.py new file mode 100644 index 0000000..1f35c20 --- /dev/null +++ b/sql-edition/servers/LDAPConnector.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python2.4 + +import ldap +import ldap.filter + +LDAP_TIMEOUT = 10 + +def get_ldap_connection(): + ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, '/etc/ssl/UCC-CA.crt') + ldap.set_option(ldap.OPT_X_TLS,1) + ldap.set_option(ldap.OPT_X_TLS_ALLOW,1) + #ldap.set_option(ldap.OPT_DEBUG_LEVEL,255) + conn = ldap.initialize('ldaps://mussel.ucc.gu.uwa.edu.au:636/') + + binddn = 'cn=admin,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' + passfile = open('/etc/pam_ldap.secret') + password = passfile.readline().strip() + passfile.close() + + conn.simple_bind_s(binddn, password) + return conn + +def get_uid(card_id): + ldapconn = get_ldap_connection() + + basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' + filter = ldap.filter.filter_format('(uccDispenseMIFARE=%s)', (card_id, )) + attrs = ('uidNumber',) + + results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT) + + ldapconn.unbind() + + if len(results) != 1: + raise ValueError, "no UID found for card ID" + + return results[0][1]['uidNumber'][0] + +def set_card_id(uidNumber, card_id): + ldapconn = get_ldap_connection() + + basedn = 'ou=People,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au' + filter = ldap.filter.filter_format('(uidNumber=%s)', (uidNumber, )) + attrs = ('objectClass', ) + + results = ldapconn.search_st(basedn, ldap.SCOPE_SUBTREE, filter, attrs, timeout=LDAP_TIMEOUT) + + if len(results) != 1: + raise "ValueError", 'error in uidNumber' + + user_dn = results[0][0] + + mod_attrs = [] + + # Does it have the correct object class? + if 'uccDispenseAccount' not in results[0][1]['objectClass']: + # Add uccDispenseAccount objectclass + mod_attrs.append((ldap.MOD_ADD, 'objectClass', 'uccDispenseAccount')) + + # Add MIFARE Card ID + mod_attrs.append((ldap.MOD_ADD, 'uccDispenseMIFARE', card_id)) + + # Use a double-try here to work around something that's fixed in Python 2.5 + try: + try: + ldapconn.modify_s(user_dn, mod_attrs) + except ldap.TYPE_OR_VALUE_EXISTS, e: + pass + finally: + ldapconn.unbind() + +if __name__ == '__main__': + #print get_uid('\x01\x02\x03\x04\x05\x06') + set_card_id('11251', '\x01\x02\x03\x04\x05\x06') diff --git a/sql-edition/servers/MIFAREClient.py b/sql-edition/servers/MIFAREClient.py new file mode 100644 index 0000000..358f550 --- /dev/null +++ b/sql-edition/servers/MIFAREClient.py @@ -0,0 +1,37 @@ +from MIFAREDriver import MIFAREReader, MIFAREException +from serial import Serial +from LDAPConnector import get_uid, set_card_id + +class MIFAREClient: + def __init__(self): + self.port = Serial('/dev/ttyS2', baudrate = 19200) + self.reader = MIFAREReader(self.port) + self.reader.set_led(red = False, green = True) + self.reader.beep(100) + + def get_card(self): + self.reader.set_led(red = True, green = False) + try: + card_id, capacity = self.reader.select_card() + except MIFAREException: + self.reader.set_led(red = False, green = True) + return None + else: + self.reader.set_led(red = False, green = True) + self.reader.beep(100) + return card_id + + def add_card(self, uid): + self.reader.set_led(red = True, green = False) + for attempt in range(5): + self.reader.beep(50) + try: + card_id, capacity = self.reader.select_card() + except MIFAREException: + pass + else: + set_card_id(card_id, uid) + self.reader.set_led(red = False, green = True) + return True + self.reader.set_led(red = False, green = True) + return False \ No newline at end of file diff --git a/sql-edition/servers/MIFAREDriver.py b/sql-edition/servers/MIFAREDriver.py new file mode 100644 index 0000000..a3a65b6 --- /dev/null +++ b/sql-edition/servers/MIFAREDriver.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python2.5 + +'''mifare - a library for interacting with MIFARE readers. +Written by David Adam +Requires Python 2.5. + +Licensed under an MIT-style license: see LICENSE file for details. +''' + +import serial + +xor = lambda x, y: x ^ y +def checksum(string): + return chr(reduce(xor, [ord(i) for i in string])) + + +class MIFAREException(Exception): + pass + + +class MIFARECommunicationException(MIFAREException): + pass + + +class MIFAREAuthenticationException(MIFAREException): + pass + + +class MIFAREReader: + '''An interface to a particular MIFARE reader.''' + + def __init__(self, io): + '''Returns an interface to a MIFARE reader given a file-like object. + The file-like object is generally a pyserial Serial object.''' + self.io = io + if isinstance(self.io, serial.Serial): + self.io.setTimeout(2) + self.address = '\x00\x00' + + def get_absolute_block(self, vector): + if vector[0] < 32: + return vector[0] * 4 + vector[1] + else: + # Sectors below 32 are 4 blocks + # Sectors above are 16 blocks + # Thus, sector 32 starts at block 128, 33 at 144, and so on + return 128 + (vector[0] - 32) * 16 + vector[1] + + def send_packet(self, data): + '''Constructs a packet for the supplied data string, sends it to the + MIFARE reader, then returns the response (if any) to the commmand.''' + + # Occasionally the reader inserts extra trailing characters into its + # responses, so flush the buffers if possible beforehand. + if isinstance(self.io, serial.Serial): + self.io.flushInput() + self.io.flushOutput() + + # XXX - Needs more error checking. + data = '\x00' + self.address + data + packet = '\xAA\xBB' + chr(len(data)) + data + checksum(data) + self.io.write(packet) + response = '' + header = self.io.read(2) + if header == '\xaa\xbb': + length = ord(self.io.read(1)) + data = self.io.read(length) + packet_xsum = self.io.read(1) + if checksum(data) == packet_xsum and len(data) == length: + # Strip off separator and address header + return data[3:] + else: + raise MIFARECommunicationException, "Invalid response received" + + def set_antenna(self, state = True): + """Turn the card reader's antenna on or off (no return value)""" + command = '\x0C\x01' + chr(int(state)) + response = self.send_packet(command) + if response == '\x0c\x01\x00': + return None + else: + raise MIFAREException, 'command failed: set_antenna (%s)' % state + + def select_card(self, include_halted = False): + """Selects a card and returns a tuple of (serial number, capacity). + + If include_halted is set, may select a card that halt() has previously + been called on.""" + + # Request type of card available + command = command = '\x01\x02' + if include_halted: + command += '\x52' + else: + command += '\x26' + + card_type_response = self.send_packet(command) + + if card_type_response[2] == '\x14': + raise MIFAREException, "select_card: no card available" + card_type = card_type_response[3:5] + + if card_type == '\x44\x00': # MIFARE UltraLight + raise NotImplementedError, "UltraLight card selected - no functions available" + + else: + # Otherwise, must be a standard MIFARE card. + # Anticollision + command = '\x02\x02\x04' + # No error handling on this command + serial = self.send_packet(command)[3:] + + # Select the card for use + capacity = ord(self.send_packet('\x03\x02' + serial)[3]) + return (serial, capacity) + + def sector_login(self, blockvect, key, keytype=0): + """Log in to a block using the six-byte key. + + Use a keytype of 1 to use key B.""" + sector = self.get_absolute_block((blockvect[0], 0)) + + if len(key) != 6: + raise ValueError, 'key must be a six-byte string' + + keytype = 96 + keytype + + data = chr(keytype) + chr(sector) + key + + result = self.send_packet('\x07\x02' + data) + if ord(result[2]) == 22: + raise MIFAREAuthenticationException, "incorrect key provided" + + return + + def read_block(self, blockvect): + "Read the 16-byte block at vector (sector, block)." + block = self.get_absolute_block(blockvect) + + result = self.send_packet('\x08\x02' + chr(block)) + return result[3:19] + + def write_block(self, blockvect, data): + """Write the 16 bytes in data to the block at vector (sector, block).""" + block = self.get_absolute_block(blockvect) + if len(data) != 16: + raise ValueError, "invalid data length - must be 16 bytes" + + result = self.send_packet('\x09\x02' + chr(block) + data) + return + + def write_key(self, key): + pass + + def value_block_increment(self, blocknum, increment): + pass + + def value_block_decrement(self, blocknum, decrement): + pass + + def copy_block(self, source, dest): + pass + + def halt(self): + """Halt the current card - no further transactions will be performed with it.""" + self.send_packet('\x04\x02') + + def set_led(self, red = False, green = False): + led_state = 0 + if red: + led_state += 1 + if green: + led_state += 2 + self.send_packet('\x07\x01' + chr(led_state)) + + def beep(self, length): + '''Beep for a specified length of milliseconds.''' + length = int(round(length / 10.)) + if length > 255: + length = 255 + self.send_packet('\x06\x01' + chr(length)) + + def reset(self): + pass -- 2.20.1