1/* -*- mode: c++; c-basic-offset:4 -*-
2 smartcard/readerstatus.cpp
3
4 This file is part of Kleopatra, the KDE keymanager
5 Copyright (c) 2009 Klarälvdalens Datakonsult AB
6
7 Kleopatra is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 Kleopatra is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
21 In addition, as a special exception, the copyright holders give
22 permission to link the code of this program with any edition of
23 the Qt library by Trolltech AS, Norway (or with modified versions
24 of Qt that use the same license as Qt), and distribute linked
25 combinations including the two. You must obey the GNU General
26 Public License in all respects for all of the code used other than
27 Qt. If you modify this file, you may extend this exception to
28 your version of the file, but you are not obligated to do so. If
29 you do not wish to do so, delete this exception statement from
30 your version.
31*/
32
33#include <config-kleopatra.h>
34
35#include "readerstatus.h"
36
37#include <utils/gnupg-helper.h>
38#include <utils/kdsignalblocker.h>
39#include <utils/filesystemwatcher.h>
40
41#include <kleo/stl_util.h>
42
43#include <gpgme++/context.h>
44#include <gpgme++/assuanresult.h>
45#include <gpgme++/defaultassuantransaction.h>
46#include <gpgme++/key.h>
47#include <gpgme++/keylistresult.h>
48
49#include <gpg-error.h>
50
51#include <KDebug>
52
53#include <QStringList>
54#include <QDir>
55#include <QFileInfo>
56#include <QMutex>
57#include <QWaitCondition>
58#include <QThread>
59#include <QPointer>
60
61#include <boost/algorithm/string/split.hpp>
62#include <boost/algorithm/string/classification.hpp>
63#include <boost/algorithm/string/case_conv.hpp>
64#include <boost/static_assert.hpp>
65#include <boost/range.hpp>
66#include <boost/bind.hpp>
67
68#include <vector>
69#include <set>
70#include <list>
71#include <algorithm>
72#include <iterator>
73#include <utility>
74#include <cstdlib>
75
76using namespace Kleo;
77using namespace Kleo::SmartCard;
78using namespace GpgME;
79using namespace boost;
80
81static const unsigned int RETRY_WAIT = 2; // seconds
82static const unsigned int CHECK_INTERVAL = 2000; // msecs
83
84static ReaderStatus * self = 0;
85
86struct CardInfo {
87 CardInfo()
88 : fileName(),
89 status( ReaderStatus::NoCard ),
90 appType( ReaderStatus::UnknownApplication ),
91 appVersion( -1 )
92 {
93
94 }
95 CardInfo( const QString & fn, ReaderStatus::Status s )
96 : fileName( fn ),
97 status( s ),
98 appType( ReaderStatus::UnknownApplication ),
99 appVersion( -1 )
100 {
101
102 }
103
104 QString fileName;
105 ReaderStatus::Status status;
106 std::string serialNumber;
107 ReaderStatus::AppType appType;
108 int appVersion;
109 std::vector<ReaderStatus::PinState> pinStates;
110};
111
112static const char * flags[] = {
113 "NOCARD",
114 "PRESENT",
115 "ACTIVE",
116 "USABLE",
117};
118BOOST_STATIC_ASSERT(( sizeof flags/sizeof *flags == ReaderStatus::_NumScdStates ));
119
120static const char * prettyFlags[] = {
121 "NoCard",
122 "CardPresent",
123 "CardActive",
124 "CardUsable",
125 "CardCanLearnKeys",
126 "CardHasNullPin",
127 "CardError",
128};
129BOOST_STATIC_ASSERT(( sizeof prettyFlags/sizeof *prettyFlags == ReaderStatus::NumStates ));
130
131static QByteArray read_file( const QString & fileName ) {
132 QFile file( fileName );
133 if ( !file.exists() ) {
134 kDebug() << "read_file: file" << fileName << "does not exist";
135 return QByteArray();
136 }
137 if ( !file.open( QIODevice::ReadOnly ) ) {
138 kDebug() << "read_file: failed to open" << fileName << ':' << file.errorString();
139 return QByteArray();
140 }
141 return file.readAll().trimmed();
142}
143
144static unsigned int parseFileName( const QString & fileName, bool * ok ) {
145 QRegExp rx( QLatin1String( "reader_(\\d+)\\.status" ) );
146 if ( ok )
147 *ok = false;
148 if ( rx.exactMatch( QFileInfo( fileName ).fileName() ) )
149 return rx.cap(1).toUInt( ok, 10 );
150 return 0;
151}
152
153namespace {
154 template <typename T_Target, typename T_Source>
155 std::auto_ptr<T_Target> dynamic_pointer_cast( std::auto_ptr<T_Source> & in ) {
156 if ( T_Target * const target = dynamic_cast<T_Target*>( in.get() ) ) {
157 in.release();
158 return std::auto_ptr<T_Target>( target );
159 } else {
160 return std::auto_ptr<T_Target>();
161 }
162 }
163
164 template <typename T>
165 const T & _trace__impl( const T & t, const char * msg ) {
166 kDebug() << msg << t;
167 return t;
168 }
169#define TRACE( x ) _trace__impl( x, #x )
170}
171
172static QDebug operator<<( QDebug s, const std::vector< std::pair<std::string,std::string> > & v ) {
173 typedef std::pair<std::string,std::string> pair;
174 s << '(';
175 Q_FOREACH( const pair & p, v )
176 s << "status(" << QString::fromStdString( p.first ) << ") =" << QString::fromStdString( p.second ) << endl;
177 return s << ')';
178}
179
180static const char * app_types[] = {
181 "_", // will hopefully never be used as an app-type :)
182 "openpgp",
183 "nks",
184 "p15",
185 "dinsig",
186 "geldkarte",
187};
188BOOST_STATIC_ASSERT(( sizeof app_types / sizeof *app_types == ReaderStatus::NumAppTypes ));
189
190
191static ReaderStatus::AppType parse_app_type( const std::string & s ) {
192 kDebug() << "parse_app_type(" << s.c_str() << ")";
193 const char ** it = std::find( begin( app_types ), end( app_types ), to_lower_copy( s ) );
194 if ( it == end( app_types ) )
195 return TRACE( ReaderStatus::UnknownApplication );
196 return TRACE( static_cast<ReaderStatus::AppType>( it - begin( app_types ) ) );
197
198}
199
200static int parse_app_version( const std::string & s ) {
201 return std::atoi( s.c_str() );
202}
203
204static ReaderStatus::PinState parse_pin_state( const std::string & s ) {
205 switch ( int i = std::atoi( s.c_str() ) ) {
206 case -4: return ReaderStatus::NullPin;
207 case -3: return ReaderStatus::PinBlocked;
208 case -2: return ReaderStatus::NoPin;
209 case -1: return ReaderStatus::UnknownPinState;
210 default:
211 if ( i < 0 )
212 return ReaderStatus::UnknownPinState;
213 else
214 return ReaderStatus::PinOk;
215 }
216}
217
218static std::auto_ptr<DefaultAssuanTransaction> gpgagent_transact( shared_ptr<Context> & gpgAgent, const char * command, Error & err ) {
219#ifdef DEBUG_SCREADER
220 kDebug() << "gpgagent_transact(" << command << ")";
221#endif
222 const AssuanResult res = gpgAgent->assuanTransact( command );
223 err = res.error();
224 if ( !err.code() )
225 err = res.assuanError();
226 if ( err.code() ) {
227#ifdef DEBUG_SCREADER
228 kDebug() << "gpgagent_transact(" << command << "):" << QString::fromLocal8Bit( err.asString() );
229#endif
230 if ( err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE ) {
231 kDebug() << "Assuan problem, killing context";
232 gpgAgent.reset();
233 }
234 return std::auto_ptr<DefaultAssuanTransaction>();
235 }
236 std::auto_ptr<AssuanTransaction> t = gpgAgent->takeLastAssuanTransaction();
237 return dynamic_pointer_cast<DefaultAssuanTransaction>( t );
238}
239
240// returns const std::string so template deduction in boost::split works, and we don't need a temporary
241static const std::string scd_getattr_status( shared_ptr<Context> & gpgAgent, const char * what, Error & err ) {
242 std::string cmd = "SCD GETATTR ";
243 cmd += what;
244 const std::auto_ptr<DefaultAssuanTransaction> t = gpgagent_transact( gpgAgent, cmd.c_str(), err );
245 if ( t.get() ) {
246 kDebug() << "scd_getattr_status(" << what << "): got" << t->statusLines();
247 return t->firstStatusLine( what );
248 } else {
249 kDebug() << "scd_getattr_status(" << what << "): t == NULL";
250 return std::string();
251 }
252}
253
254static unsigned int parse_event_counter( const std::string & str ) {
255 unsigned int result;
256 if ( sscanf( str.c_str(), "%*u %*u %u ", &result ) == 1 )
257 return result;
258 return -1;
259}
260
261static unsigned int get_event_counter( shared_ptr<Context> & gpgAgent ) {
262 Error err;
263 const std::auto_ptr<DefaultAssuanTransaction> t = gpgagent_transact( gpgAgent, "GETEVENTCOUNTER", err );
264 if ( err.code() )
265 kDebug() << "get_event_counter(): got error" << err.asString();
266 if ( t.get() ) {
267#ifdef DEBUG_SCREADER
268 kDebug() << "get_event_counter(): got" << t->statusLines();
269#endif
270 return parse_event_counter( t->firstStatusLine( "EVENTCOUNTER" ) );
271 } else {
272 kDebug() << "scd_getattr_status(): t == NULL";
273 return -1;
274 }
275}
276
277// returns const std::string so template deduction in boost::split works, and we don't need a temporary
278static const std::string gpgagent_data( shared_ptr<Context> & gpgAgent, const char * what, Error & err ) {
279 const std::auto_ptr<DefaultAssuanTransaction> t = gpgagent_transact( gpgAgent, what, err );
280 if ( t.get() )
281 return t->data();
282 else
283 return std::string();
284}
285
286static std::string parse_keypairinfo( const std::string & kpi ) {
287 static const char hexchars[] = "0123456789abcdefABCDEF";
288 return '&' + kpi.substr( 0, kpi.find_first_not_of( hexchars ) );
289}
290
291static bool parse_keypairinfo_and_lookup_key( Context * ctx, const std::string & kpi ) {
292 if ( !ctx )
293 return false;
294 const std::string pattern = parse_keypairinfo( kpi );
295 kDebug() << "parse_keypairinfo_and_lookup_key: pattern=" << pattern.c_str();
296 if ( const Error err = ctx->startKeyListing( pattern.c_str() ) ) {
297 kDebug() << "parse_keypairinfo_and_lookup_key: startKeyListing failed:" << err.asString();
298 return false;
299 }
300 Error e;
301 const Key key = ctx->nextKey( e );
302 ctx->endKeyListing();
303 kDebug() << "parse_keypairinfo_and_lookup_key: e=" << e.code() << "; key.isNull()" << key.isNull();
304 return !e && !key.isNull();
305}
306
307static CardInfo get_card_status( const QString & fileName, unsigned int idx, shared_ptr<Context> & gpg_agent ) {
308#ifdef DEBUG_SCREADER
309 kDebug() << "get_card_status(" << fileName << ',' << idx << ',' << gpg_agent.get() << ')';
310#endif
311 CardInfo ci( fileName, ReaderStatus::CardUsable );
312 if ( idx != 0 || !gpg_agent )
313 return ci;
314 Error err;
315 ci.serialNumber = gpgagent_data( gpg_agent, "SCD SERIALNO", err );
316 if ( err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED ) {
317 ci.status = ReaderStatus::NoCard;
318 return ci;
319 }
320 if ( err.code() ) {
321 ci.status = ReaderStatus::CardError;
322 return ci;
323 }
324 ci.appType = parse_app_type( scd_getattr_status( gpg_agent, "APPTYPE", err ) );
325 if ( err.code() )
326 return ci;
327 if ( ci.appType != ReaderStatus::NksApplication ) {
328 kDebug() << "get_card_status: not a NetKey card, giving up";
329 return ci;
330 }
331 ci.appVersion = parse_app_version( scd_getattr_status( gpg_agent, "NKS-VERSION", err ) );
332 if ( err.code() )
333 return ci;
334 if ( ci.appVersion != 3 ) {
335 kDebug() << "get_card_status: not a NetKey v3 card, giving up";
336 return ci;
337 }
338
339 // the following only works for NKS v3...
340 std::vector<std::string> chvStatus;
341 chvStatus.reserve( 4 ); // expected number of fields
342 split( chvStatus, scd_getattr_status( gpg_agent, "CHV-STATUS", err ), is_any_of( " \t" ), token_compress_on );
343 if ( err.code() )
344 return ci;
345 std::transform( chvStatus.begin(), chvStatus.end(),
346 std::back_inserter( ci.pinStates ),
347 parse_pin_state );
348
349 if ( kdtools::contains( ci.pinStates, ReaderStatus::NullPin ) ) {
350 ci.status = ReaderStatus::CardHasNullPin;
351 return ci;
352 }
353
354 // check for keys to learn:
355 const std::auto_ptr<DefaultAssuanTransaction> result = gpgagent_transact( gpg_agent, "SCD LEARN --keypairinfo", err );
356 if ( err.code() || !result.get() )
357 return ci;
358 const std::vector<std::string> keyPairInfos = result->statusLine( "KEYPAIRINFO" );
359 if ( keyPairInfos.empty() )
360 return ci;
361
362 // check that any of the
363 const std::auto_ptr<Context> klc( Context::createForProtocol( CMS ) ); // what about OpenPGP?
364 if ( !klc.get() )
365 return ci;
366 klc->setKeyListMode( Ephemeral );
367
368 if ( kdtools::any( keyPairInfos, !boost::bind( &parse_keypairinfo_and_lookup_key, klc.get(), _1 ) ) )
369 ci.status = ReaderStatus::CardCanLearnKeys;
370
371#ifdef DEBUG_SCREADER
372 kDebug() << "get_card_status: ci.status " << prettyFlags[ci.status];
373#endif
374
375 return ci;
376}
377
378static std::vector<CardInfo> update_cardinfo( const QString & gnupgHomePath, shared_ptr<Context> & gpgAgent ) {
379#ifdef DEBUG_SCREADER
380 kDebug() << "<update_cardinfo>";
381#endif
382 const QDir gnupgHome( gnupgHomePath );
383 if ( !gnupgHome.exists() )
384 kWarning() << "gnupg home" << gnupgHomePath << "does not exist!";
385
386 const CardInfo ci = get_card_status( gnupgHome.absoluteFilePath( QLatin1String( "reader_0.status" ) ), 0, gpgAgent );
387#ifdef DEBUG_SCREADER
388 kDebug() << "</update_cardinfo>";
389#endif
390 return std::vector<CardInfo>( 1, ci );
391}
392
393static bool check_event_counter_changed( shared_ptr<Context> & gpg_agent, unsigned int & counter ) {
394 const unsigned int oldCounter = counter;
395 counter = get_event_counter( gpg_agent );
396 if ( oldCounter != counter ) {
397#ifdef DEBUG_SCREADER
398 kDebug() << "ReaderStatusThread[2nd]: events:" << oldCounter << "->" << counter ;
399#endif
400 return true;
401 } else {
402 return false;
403 }
404}
405
406struct Transaction {
407 QByteArray command;
408 QPointer<QObject> receiver;
409 const char * slot;
410 GpgME::Error error;
411};
412
413static const Transaction checkTransaction = { "__check__", 0, 0, Error() };
414static const Transaction updateTransaction = { "__update__", 0, 0, Error() };
415static const Transaction quitTransaction = { "__quit__", 0, 0, Error() };
416
417namespace {
418 class ReaderStatusThread : public QThread {
419 Q_OBJECT
420 public:
421 explicit ReaderStatusThread( QObject * parent=0 )
422 : QThread( parent ),
423 m_gnupgHomePath( Kleo::gnupgHomeDirectory() ),
424 m_transactions( 1, updateTransaction ) // force initial scan
425 {
426 connect( this, SIGNAL(oneTransactionFinished()),
427 this, SLOT(slotOneTransactionFinished()) );
428 }
429
430 std::vector<CardInfo> cardInfos() const {
431 const QMutexLocker locker( &m_mutex );
432 return m_cardInfos;
433 }
434
435 ReaderStatus::Status cardStatus( unsigned int slot ) const {
436 const QMutexLocker locker( &m_mutex );
437 if ( slot < m_cardInfos.size() )
438 return m_cardInfos[slot].status;
439 else
440 return ReaderStatus::NoCard;
441 }
442
443 void addTransaction( const Transaction & t ) {
444 const QMutexLocker locker( &m_mutex );
445 m_transactions.push_back( t );
446 m_waitForTransactions.wakeOne();
447 }
448
449 // make QThread::sleep public
450 using QThread::sleep;
451
452 Q_SIGNALS:
453 void anyCardHasNullPinChanged( bool );
454 void anyCardCanLearnKeysChanged( bool );
455 void cardStatusChanged( unsigned int, Kleo::SmartCard::ReaderStatus::Status );
456 void oneTransactionFinished();
457
458 public Q_SLOTS:
459 void ping() {
460 kDebug() << "ReaderStatusThread[GUI]::ping()";
461 addTransaction( updateTransaction );
462 }
463
464 void stop() {
465 const QMutexLocker locker( &m_mutex );
466 m_transactions.push_front( quitTransaction );
467 m_waitForTransactions.wakeOne();
468 }
469
470 void slotReaderStatusFileChanged() {
471 const QDir gnupgHome( m_gnupgHomePath );
472 if ( !gnupgHome.exists() ) {
473 kWarning() << "gnupg home" << m_gnupgHomePath << "does not exist!";
474 return;
475 }
476
477 QStringList files = gnupgHome.entryList( QStringList( QLatin1String( "reader_*.status" ) ), QDir::Files, QDir::Name );
478 bool * dummy = 0;
479 kdtools::sort( files, boost::bind( parseFileName, _1, dummy ) < boost::bind( parseFileName, _2, dummy ) );
480
481 std::vector<QByteArray> contents;
482
483 Q_FOREACH( const QString & file, files ) {
484 bool ok = false;
485 const unsigned int idx = parseFileName( file, &ok );
486 if ( !ok ) {
487 kDebug() << "filename" << file << ": cannot parse reader slot number";
488 continue;
489 }
490 assert( idx >= contents.size() );
491 contents.resize( idx );
492 contents.push_back( read_file( gnupgHome.absoluteFilePath( file ) ) );
493 }
494
495 // canonicalise by removing empty stuff from the end
496 while ( !contents.empty() && contents.back().isEmpty() )
497 contents.pop_back();
498
499 if ( contents != readerStatusFileContents )
500 ping();
501
502 readerStatusFileContents.swap( contents );
503 }
504
505 private Q_SLOTS:
506 void slotOneTransactionFinished() {
507 std::list<Transaction> ft;
508 KDAB_SYNCHRONIZED( m_mutex )
509 ft.splice( ft.begin(), m_finishedTransactions );
510 Q_FOREACH( const Transaction & t, ft )
511 if ( t.receiver && t.slot && *t.slot )
512 QMetaObject::invokeMethod( t.receiver, t.slot, Qt::DirectConnection, Q_ARG( GpgME::Error, t.error ) );
513 }
514
515 private:
516 /* reimp */ void run() {
517
518 shared_ptr<Context> gpgAgent;
519 unsigned int eventCounter = -1;
520
521 while ( true ) {
522
523 QByteArray command;
524 bool nullSlot;
525 std::list<Transaction> item;
526 std::vector<CardInfo> oldCardInfos;
527
528 if ( !gpgAgent ) {
529 Error err;
530 std::auto_ptr<Context> c = Context::createForEngine( AssuanEngine, &err );
531 if ( err.code() == GPG_ERR_NOT_SUPPORTED )
532 return;
533 gpgAgent = c;
534 }
535
536 KDAB_SYNCHRONIZED( m_mutex ) {
537
538 while ( m_transactions.empty() ) {
539 // go to sleep waiting for more work:
540#ifdef DEBUG_SCREADER
541 kDebug() << "ReaderStatusThread[2nd]: .zZZ";
542#endif
543 if ( !m_waitForTransactions.wait( &m_mutex, CHECK_INTERVAL ) )
544 m_transactions.push_front( checkTransaction );
545#ifdef DEBUG_SCREADER
546 kDebug() << "ReaderStatusThread[2nd]: .oOO";
547#endif
548 }
549
550 // splice off the first transaction without
551 // copying, so we own it without really importing
552 // it into this thread (the QPointer isn't
553 // thread-safe):
554 item.splice( item.end(),
555 m_transactions, m_transactions.begin() );
556
557 // make local copies of the interesting stuff so
558 // we can release the mutex again:
559 command = item.front().command;
560 nullSlot = !item.front().slot;
561 oldCardInfos = m_cardInfos;
562 }
563
564#ifdef DEBUG_SCREADER
565 kDebug() << "ReaderStatusThread[2nd]: new iteration command=" << command << " ; nullSlot=" << nullSlot;
566#endif
567 // now, let's see what we got:
568
569 if ( nullSlot && command == quitTransaction.command )
570 return; // quit
571
572 if ( nullSlot && command == updateTransaction.command ||
573 nullSlot && command == checkTransaction.command ) {
574
575 if ( nullSlot && command == checkTransaction.command && !check_event_counter_changed( gpgAgent, eventCounter ) )
576 continue; // early out
577
578 std::vector<CardInfo> newCardInfos
579 = update_cardinfo( m_gnupgHomePath, gpgAgent );
580
581 newCardInfos.resize( std::max( newCardInfos.size(), oldCardInfos.size() ) );
582 oldCardInfos.resize( std::max( newCardInfos.size(), oldCardInfos.size() ) );
583
584 KDAB_SYNCHRONIZED( m_mutex )
585 m_cardInfos = newCardInfos;
586
587 std::vector<CardInfo>::const_iterator
588 nit = newCardInfos.begin(), nend = newCardInfos.end(),
589 oit = oldCardInfos.begin(), oend = oldCardInfos.end() ;
590
591 unsigned int idx = 0;
592 bool anyLC = false;
593 bool anyNP = false;
594 bool anyError = false;
595 while ( nit != nend && oit != oend ) {
596 if ( nit->status != oit->status ) {
597#ifdef DEBUG_SCREADER
598 kDebug() << "ReaderStatusThread[2nd]: slot" << idx << ":" << prettyFlags[oit->status] << "->" << prettyFlags[nit->status];
599#endif
600 emit cardStatusChanged( idx, nit->status );
601 }
602 if ( nit->status == ReaderStatus::CardCanLearnKeys )
603 anyLC = true;
604 if ( nit->status == ReaderStatus::CardHasNullPin )
605 anyNP = true;
606 if ( nit->status == ReaderStatus::CardError )
607 anyError = true;
608 ++nit;
609 ++oit;
610 ++idx;
611 }
612
613 emit anyCardHasNullPinChanged( anyNP );
614 emit anyCardCanLearnKeysChanged( anyLC );
615
616 if ( anyError )
617 gpgAgent.reset();
618
619 } else {
620
621 (void)gpgagent_transact( gpgAgent, command.constData(), item.front().error );
622
623 KDAB_SYNCHRONIZED( m_mutex )
624 // splice 'item' into m_finishedTransactions:
625 m_finishedTransactions.splice( m_finishedTransactions.end(), item );
626
627 emit oneTransactionFinished();
628
629 }
630
631 // update event counter in case anything above changed
632 // it:
633 if ( gpgAgent )
634 eventCounter = get_event_counter( gpgAgent );
635 else
636 eventCounter = -1;
637#ifdef DEBUG_SCREADER
638 kDebug() << "eventCounter:" << eventCounter;
639#endif
640
641 }
642 }
643
644 private:
645 mutable QMutex m_mutex;
646 QWaitCondition m_waitForTransactions;
647 const QString m_gnupgHomePath;
648 std::vector<QByteArray> readerStatusFileContents;
649 // protected by m_mutex:
650 std::vector<CardInfo> m_cardInfos;
651 std::list<Transaction> m_transactions, m_finishedTransactions;
652 };
653
654}
655
656class ReaderStatus::Private : ReaderStatusThread {
657 friend class Kleo::SmartCard::ReaderStatus;
658 ReaderStatus * const q;
659public:
660 explicit Private( ReaderStatus * qq )
661 : ReaderStatusThread( qq ),
662 q( qq ),
663 watcher()
664 {
665 KDAB_SET_OBJECT_NAME( watcher );
666
667 qRegisterMetaType<Status>( "Kleo::SmartCard::ReaderStatus::Status" );
668
669 watcher.whitelistFiles( QStringList( QLatin1String( "reader_*.status" ) ) );
670 watcher.addPath( Kleo::gnupgHomeDirectory() );
671 watcher.setDelay( 100 );
672
673 connect( this, SIGNAL(cardStatusChanged(uint,Kleo::SmartCard::ReaderStatus::Status)),
674 q, SIGNAL(cardStatusChanged(uint,Kleo::SmartCard::ReaderStatus::Status)) );
675 connect( this, SIGNAL(anyCardHasNullPinChanged(bool)),
676 q, SIGNAL(anyCardHasNullPinChanged(bool)) );
677 connect( this, SIGNAL(anyCardCanLearnKeysChanged(bool)),
678 q, SIGNAL(anyCardCanLearnKeysChanged(bool)) );
679
680 connect( &watcher, SIGNAL(triggered()), this, SLOT(slotReaderStatusFileChanged()) );
681
682 }
683 ~Private() {
684 stop();
685 if ( !wait( 100 ) ) {
686 terminate();
687 wait();
688 }
689 }
690
691private:
692 bool anyCardHasNullPinImpl() const {
693 return kdtools::any( cardInfos(), boost::bind( &CardInfo::status, _1 ) == CardHasNullPin );
694 }
695
696 bool anyCardCanLearnKeysImpl() const {
697 return kdtools::any( cardInfos(), boost::bind( &CardInfo::status, _1 ) == CardCanLearnKeys );
698 }
699
700private:
701 FileSystemWatcher watcher;
702};
703
704
705ReaderStatus::ReaderStatus( QObject * parent )
706 : QObject( parent ), d( new Private( this ) )
707{
708 self = this;
709}
710
711ReaderStatus::~ReaderStatus() { self = 0; }
712
713// slot
714void ReaderStatus::startMonitoring() {
715 d->start();
716}
717
718// static
719ReaderStatus * ReaderStatus::mutableInstance() {
720 return self;
721}
722
723// static
724const ReaderStatus * ReaderStatus::instance() {
725 return self;
726}
727
728ReaderStatus::Status ReaderStatus::cardStatus( unsigned int slot ) const {
729 return d->cardStatus( slot );
730}
731
732bool ReaderStatus::anyCardHasNullPin() const {
733 return d->anyCardHasNullPinImpl();
734}
735
736bool ReaderStatus::anyCardCanLearnKeys() const {
737 return d->anyCardCanLearnKeysImpl();
738}
739
740std::vector<ReaderStatus::PinState> ReaderStatus::pinStates( unsigned int slot ) const {
741 const std::vector<CardInfo> ci = d->cardInfos();
742 if ( slot < ci.size() )
743 return ci[slot].pinStates;
744 else
745 return std::vector<PinState>();
746}
747
748void ReaderStatus::startSimpleTransaction( const QByteArray & command, QObject * receiver, const char * slot ) {
749 const Transaction t = { command, receiver, slot, Error() };
750 d->addTransaction( t );
751}
752
753void ReaderStatus::updateStatus() {
754 d->ping();
755}
756
757#include "readerstatus.moc"
758