diff --git a/cards/vrs_application.py b/cards/vrs_application.py new file mode 100644 index 0000000..549bb84 --- /dev/null +++ b/cards/vrs_application.py @@ -0,0 +1,117 @@ +from generic_application import Application +import struct, sha, binascii, os, datetime, sys, time +from iso_7816_4_card import ISO_7816_4_Card +import utils, TLV_utils, generic_card + + +class VRS_Application(Application): + DRIVER_NAME = ["VRS"] + + AID_LIST = [ + "d2760000254b414e4d303100", + "d2760001354b414e4d303100", + ] + +class VrsTicket(object): + def __init__(self): + self._birthdate = None + self._maindata = [] + self._mainblob = None + self._rawdata = None + self._tlvdata = None + self._card = None + + def from_card(cls, card, record_no = 1): + if not isinstance(card, VRS_Application): + if not isinstance(card, ISO_7816_4_Card): + raise ValueError, "card must be a VRS_Application object or a ISO_7816_4_Card object, not %s" % type(card) + else: + result = card.select_application(binascii.a2b_hex(VRS_Application.AID_LIST[0])) + if not card.check_sw(result.sw): + raise EnvironmentError, "card did not accept SELECT APPLICATION, sw was %02x %02x" % (result.sw1, result.sw2) + assert isinstance(card, VRS_Application) + + c = cls() + c._card = card + + result = card.open_file("\x0c\x05") + if card.check_sw(result.sw): + contents = card.read_record(record_no, 4) + if len(contents) > 0: + c._parse( contents ) + else: + raise KeyError, "No ticket in record no. %i" % record_no + else: + raise EnvironmentError, "card did not accept SELECT FILE, sw was %02x %02x" % (result.sw1, result.sw2) + + return c + + def _parse(self, contents): + self._rawdata = contents + self._tlvdata = TLV_utils.unpack(contents) + + tmp = TLV_utils.tlv_find_tag(self._tlvdata, 0xEA, num_results = 1) + if len(tmp) == 0: + raise ValueError, "Can't parse information file, tag 0xEA not found" + tmp = TLV_utils.tlv_find_tag(tmp, 0x85, num_results = 1) + if len(tmp) == 0: + raise ValueError, "Can't parse information file, tag 0x85 not found" + self._mainblob = tmp[0][2] + + tmp = self._mainblob + some_id, tmp = tmp[:4], tmp[4:] + + ascii_field_len = ord(tmp[0]) + tmp = tmp[1:] + + ascii_field, tmp = tmp[:ascii_field_len], tmp[ascii_field_len:] + self._maindata = ascii_field.split(" ") + + if len(tmp) > 0: + if tmp[0] == "\x01": + tmp = tmp[1:] + birthdate_bin, tmp = tmp[:4], tmp[4:] + + birthdate = binascii.b2a_hex(birthdate_bin) + self._birthdate = datetime.date( int(birthdate[0:4]), int(birthdate[4:6]), int(birthdate[6:8]) ) + + if len(tmp) > 0: + print "Warning: unparsed data trailing: %r" % tmp + + from_card = classmethod(from_card) + + def getter(index, encoding=None): + def g(self): + if self._maindata is None or len(self._maindata) <= index: + return None + if encoding is None: + return unicode( self._maindata[index] ) + else: + return unicode( self._maindata[index], encoding = encoding ) + + return g + + def _get_alter(self): + now = datetime.date.fromtimestamp( time.time() ) + diff = now.year-self.geburtsdatum.year + thisyearsbirthday = datetime.date( now.year, self.geburtsdatum.month, self.geburtsdatum.day ) + if now < thisyearsbirthday: diff = diff - 1 + return diff + + def __str__(self): + return "%s: %s %s" % (self.tickettyp, self.name_klar, self.abonr) + + + tickettyp = property(getter(0)) + rnummer = property(getter(1)) + gueltigkeit = property(getter(2)) + feld4 = property(getter(3)) + name_raw = property(getter(4)) + vorname = property(lambda self: self.name_raw and "".join(self.name_raw.split(",_")[1:]).replace("_", " ")) + nachname = property(lambda self: self.name_raw and "".join(self.name_raw.split(",_")[:1]).replace("_", " ")) + name_klar = property(lambda self: self.vorname + " " + self.nachname) + schule = abonr = property(getter(5,'cp850')) + geburtsdatum = property(lambda self: self._birthdate) + alter = property(lambda self: self._birthdate and self._get_alter()) + + diff --git a/gui/__init__.py b/gui/__init__.py index 4dfd457..704b508 100644 --- a/gui/__init__.py +++ b/gui/__init__.py @@ -1 +1,2 @@ from PassportGUI import * +from ireadyou import * diff --git a/gui/ireadyou.py b/gui/ireadyou.py new file mode 100644 index 0000000..820723e --- /dev/null +++ b/gui/ireadyou.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python +# -*- coding: iso-8859-1 -*- + +import gtk,gtk.glade,gobject +import sys, os, time +try: + import utils, TLV_utils, cards +except ImportError, e: + try: + sys.path.append(".") + import utils, TLV_utils, cards + except ImportError: + raise e + +from smartcard.CardMonitoring import CardMonitor, CardObserver +from smartcard.ReaderMonitoring import ReaderMonitor, ReaderObserver +import smartcard + +class FileLikeTextBuffer(object): + def __init__(self): + self.had_newline = True + self.buffer = gtk.TextBuffer() + self.endmark = self.buffer.create_mark("The End", self.buffer.get_end_iter(), False) + self.views = [] + + def add_view(self, v): + self.views.append(v) + v.scroll_mark_onscreen( self.endmark ) + + def writelines(self, sequence): + for s in sequence: self.write(s) + + def write(self, s): + d = "%s: " % time.strftime("%F %T") + + parts = s.split("\n") + if self.had_newline: + self.had_newline = False + s = d + else: + s = "" + + if parts[-1] == '': + del parts[-1] + self.had_newline = True + + s = s + ("\n"+d).join(parts) + if self.had_newline: s = s + "\n" + + self.buffer.insert( self.buffer.get_end_iter(), s) + for v in self.views: + v.scroll_mark_onscreen( self.endmark ) + + def flush(self): pass + + def for_stream(self, stream): + class stream_to_buf(object): + def __init__(self, parent, stream): + self.parent = parent + self.stream = stream + + def flush(self): + self.parent.flush() + self.stream.flush() + + def write(self, s): + self.parent.write(s) + self.stream.write(s) + + def writelines(self, s): + self.parent.writelines(s) + self.stream.writelines(s) + + return stream_to_buf(self, stream) + +class ireadyou(object,CardObserver,ReaderObserver): + GLADE_FILE = "gui/ireadyou/ireadyou.glade" + + def __init__(self, ticket = None): + "Create and show main window." + self.main_window_xml = gtk.glade.XML(self.GLADE_FILE, "main") + self.main_window = self.main_window_xml.get_widget("main") + + self.card_tabs = self.main_window_xml.get_widget("card_tabs") + while self.card_tabs.get_n_pages() > 0: + self.card_tabs.remove_page(0) + for t in self.CARD_TYPES: + a, b, l = gtk.Alignment(yscale=1,xscale=1,xalign=0.5,yalign=0.5), gtk.VBox(), gtk.Label(t[1]) + a.add(b) + a.show() + b.show() + l.show() + + self.card_tabs.append_page(a, tab_label=l) + + self.ticket_button_group = gtk.RadioButton() + self.ticket_button_group._ticket = None + + self.status_area = self.main_window_xml.get_widget("status_area") + self.known_readers = [] + self.known_cards = {} # Note stupid: the keys to this dict are not objects from the known_readers list but rather reader name strings + self.connected_cards = {} # Again: the keys are not cards but repr(card) + self.tickets = {} # ditto + self.ticket_displayed = None # This is either None or a tuple (card object, ticket object) + + self._update_status() + + self.logbuf = FileLikeTextBuffer() + sys.stdout = self.logbuf.for_stream(sys.stdout) + sys.stderr = self.logbuf.for_stream(sys.stderr) + self.logview = self.main_window_xml.get_widget("logview") + self.logview.set_buffer(self.logbuf.buffer) + self.logbuf.add_view(self.logview) + + signals = { + "on_exit_clicked": self.exit_clicked, + "on_main_delete_event": self.exit_clicked, + "on_main_destroy": gtk.main_quit, + } + self.main_window_xml.signal_autoconnect(signals) + + self._clear_display() + + self.rmon = ReaderMonitor() + self.cmon = CardMonitor() + + self.rmon.addObserver(self) + self.cmon.addObserver(self) + + def _clear_display(self): + self.card_tabs.set_current_page(0) + + for i in range(self.card_tabs.get_n_pages()): + a = self.card_tabs.get_nth_page(i) + vbox = a.get_child() + for c in vbox.get_children(): + vbox.remove(c) + label = self.card_tabs.get_tab_label(a) + label.set_property("sensitive", False) + + def _update_status(self): + for c in self.status_area.get_children(): + self.status_area.remove(c) + + if len(self.known_readers) == 0: + self.status_area.add( gtk.Label(u"Keine Lesegeräte angeschlossen.") ) + else: + for reader in self.known_readers: + frame = gtk.Frame(label=str(reader)) + + if len(self.known_cards[ reader.name ]) == 0: + frame.add( gtk.Label(u"Keine Karten verbunden.") ) + else: + vbox = gtk.VBox() + for card in self.known_cards[ reader.name ]: + if self.connected_cards.has_key(repr(card)): + card_ = self.connected_cards[ repr(card) ] + cardname = card_.get_driver_name() + else: + cardname = str(card) + + hbox = gtk.HBox() + cardlabel = gtk.Label( "%s: " % cardname ) + cardlabel.set_use_markup(True) + hbox.pack_start(cardlabel, expand=False) + + vbox2 = gtk.VBox() + hbox.pack_start(vbox2, expand=True) + for ticket in self.tickets[ repr(card) ]: + button = gtk.RadioButton(group=self.ticket_button_group, label=str(ticket), use_underline=False) + vbox2.pack_start(button, expand=False) + + button.connect("toggled", self._ticket_button_toggled) + button._ticket = (card, ticket) + + if self.ticket_displayed is not None and ticket == self.ticket_displayed[1]: + button.set_active(True) + + vbox.add(hbox) + frame.add(vbox) + + self.status_area.add(frame) + + self.status_area.show_all() + + def _format_datum(d): + return d.strftime("%x") + + CARD_TYPES = [ + (("SCHUL_T",), + "Schulticket", ( + ("Name", "name_klar", None), + ("Alter", "alter", None), + ("Geburtsdatum", "geburtsdatum", _format_datum), + ("Schule", "schule", None), + (u"Kartengültigkeit", "gueltigkeit", None), + ), + ), + (("JOBT_ERW",), + "Jobticket", ( + ("Name", "name_klar", None), + ("Geburtsdatum", "geburtsdatum", _format_datum), + (u"Kartengültigkeit", "gueltigkeit", None), + ), + ), + (("MT_ABO",), + "Monatsabo", ( + ("Abo-Nummer", "abonr", None), + (u"Kartengültigkeit", "gueltigkeit", None), + ), + ), + (None, + "Anderes", ( + ), + ), + ] + + def _ticket_button_toggled(self, togglebutton): + self.ticket_displayed = None + for b in togglebutton.get_group(): + if b.get_active(): + if hasattr(b, "_ticket"): + self.ticket_displayed = b._ticket + self._update_ticket_display() + + def _update_ticket_display(self): + self._clear_display() + if self.ticket_displayed is None: + return + + todisplay = self.ticket_displayed[1] + + for i,t in enumerate(self.CARD_TYPES): + if todisplay.tickettyp in t[0]: + break + # Note: implicit selection of the last card type when no match is found + + self.card_tabs.set_current_page(i) + a = self.card_tabs.get_nth_page(i) + vbox = a.get_child() + label = self.card_tabs.get_tab_label(a) + label.set_property("sensitive", True) + + for labeltext, propertyname, transformation in t[2]: + frame = gtk.Frame(label=labeltext) + content = getattr(todisplay, propertyname, None) + contenttext = str( transformation is not None and transformation(content) or content ) + contentlabel = gtk.Label("%s" % contenttext) + contentlabel.set_use_markup(True) + contentlabel.show() + frame.add( contentlabel ) + frame.show() + + vbox.add(frame) + + def exit_clicked(self, widget, event=None, data=None): + gtk.main_quit() + return True + + def run(self): + gtk.main() + + # From the CardObserver and ReaderObserver classes + def update( self, observable, (added, removed) ): + try: + gtk.gdk.threads_enter() + #print observable, added, removed + if observable is self.rmon.instance: + self.reader_update(observable, (added, removed) ) + elif observable is self.cmon.instance: + self.card_update(observable, (added, removed) ) + self._update_status() + self._update_ticket_display() + finally: + gtk.gdk.threads_leave() + + def reader_update( self, observable, (added, removed) ): + for r in removed: + if r in self.known_readers: + for card in list(self.known_cards[ r.name ]): + self._remove_card(card) + assert len(self.known_cards[ r.name ]) == 0 + del self.known_cards[ r.name ] + self.known_readers.remove(r) + for a in added: + if a not in self.known_readers: + self.known_readers.append(a) + self.known_cards[ a.name ] = [] + + def card_update( self, observable, (added, removed) ): + for r in removed: + if not self.known_cards.has_key(r.reader): continue + if r in self.known_cards[r.reader]: + self._remove_card(r) + for a in added: + if not self.known_cards.has_key(a.reader): continue + if a not in self.known_cards[a.reader]: + self._add_card(a) + + def _add_card(self, card): + self.known_cards[ card.reader ].append(card) + if not self.tickets.has_key( repr(card) ): + self.tickets[ repr(card) ] = [] + + conn = card.createConnection() + connected = False + try: + conn.connect() + connected = True + except smartcard.Exceptions.NoCardException, e: + pass + + if connected: + card_ = cards.new_card_object(conn) + cards.generic_card.DEBUG = False + self.connected_cards[ repr(card) ] = card_ + + for i in range(1,9): + try: + ticket = cards.vrs_application.VrsTicket.from_card(card_, record_no = i) + print "Loaded ticket '%s' from record %i" % (ticket, i) + self._add_ticket(card, ticket) + except (KeyboardInterrupt, SystemExit): + raise + except Exception,e: + if not str(e).startswith("'No ticket in record no."): + print e + + if not isinstance(card_, cards.vrs_application.VRS_Application): + break + + def _remove_card(self, card): + if self.tickets.has_key( repr(card) ): + for t in list(self.tickets[ repr(card) ]): + self._remove_ticket(card, t) + assert len(self.tickets[ repr(card) ]) == 0 + del self.tickets[ repr(card) ] + + if self.connected_cards.has_key( repr(card) ): + try: + self.connected_cards[ repr(card) ].close_card() + except smartcard.Exceptions.CardConnectionException, e: + pass + + del self.connected_cards[ repr(card) ] + self.known_cards[ card.reader ].remove(card) + + def _add_ticket(self, card, ticket): + self.tickets[ repr(card) ].append( ticket ) + if self.ticket_displayed is None: + self.ticket_displayed = ( card, ticket ) + + def _remove_ticket(self, card, ticket): + if self.ticket_displayed is not None and self.ticket_displayed[1] == ticket: + self.ticket_displayed = None + # TODO: Find a different ticket to display + self.tickets[ repr(card) ].remove(ticket) + +OPTIONS = "" +LONG_OPTIONS = [] + +if __name__ == "__main__": +## c = utils.CommandLineArgumentHelper() +## +## (options, arguments) = c.getopt(sys.argv[1:], OPTIONS, LONG_OPTIONS) +## +## card_object = c.connect() +## card = cards.new_card_object(card_object) +## #cards.generic_card.DEBUG = False +## +## print >>sys.stderr, "Using %s" % card.DRIVER_NAME +## +## if len(arguments) > 0: +## ticket = cards.vrs_application.VrsTicket.from_card(card, record_no = int(arguments[0], 0)) +## else: +## ticket = cards.vrs_application.VrsTicket.from_card(card) + + gtk.gdk.threads_init() + g = ireadyou() + g.run() diff --git a/gui/ireadyou/ireadyou.glade b/gui/ireadyou/ireadyou.glade new file mode 100644 index 0000000..be7f04a --- /dev/null +++ b/gui/ireadyou/ireadyou.glade @@ -0,0 +1,337 @@ + + + + + + + True + iReadYou + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + + + True + 0 + 0 + 1 + 1 + 0 + 0 + 0 + 0 + + + + True + False + 0 + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 0 + 0 + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0 + 0 + 0 + 0 + 0 + 0 + 12 + 0 + + + + True + True + True + True + GTK_POS_TOP + False + False + + + + True + False + 0 + + + + True + label11 + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + False + True + + + + + + True + label10 + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + + + + + + True + <b>Kartendaten</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + + + 0 + True + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0 + 0 + 0 + 1 + 0 + 0 + 12 + 0 + + + + True + False + 0 + + + + + + + + + + + + + + + + + + + + True + <b>Status</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + True + + + + + + True + True + False + 0 + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + + + + + + True + <b>Log</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + + + + diff --git a/gui/ireadyou/ireadyou.gladep b/gui/ireadyou/ireadyou.gladep new file mode 100644 index 0000000..7d453b2 --- /dev/null +++ b/gui/ireadyou/ireadyou.gladep @@ -0,0 +1,8 @@ + + + + + iReadYou + ireadyou + FALSE +