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 | |
76 | using namespace Kleo; |
77 | using namespace Kleo::SmartCard; |
78 | using namespace GpgME; |
79 | using namespace boost; |
80 | |
81 | static const unsigned int RETRY_WAIT = 2; // seconds |
82 | static const unsigned int CHECK_INTERVAL = 2000; // msecs |
83 | |
84 | static ReaderStatus * self = 0; |
85 | |
86 | struct 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 | |
112 | static const char * flags[] = { |
113 | "NOCARD" , |
114 | "PRESENT" , |
115 | "ACTIVE" , |
116 | "USABLE" , |
117 | }; |
118 | BOOST_STATIC_ASSERT(( sizeof flags/sizeof *flags == ReaderStatus::_NumScdStates )); |
119 | |
120 | static const char * prettyFlags[] = { |
121 | "NoCard" , |
122 | "CardPresent" , |
123 | "CardActive" , |
124 | "CardUsable" , |
125 | "CardCanLearnKeys" , |
126 | "CardHasNullPin" , |
127 | "CardError" , |
128 | }; |
129 | BOOST_STATIC_ASSERT(( sizeof prettyFlags/sizeof *prettyFlags == ReaderStatus::NumStates )); |
130 | |
131 | static 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 | |
144 | static 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 | |
153 | namespace { |
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 | |
172 | static 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 | |
180 | static 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 | }; |
188 | BOOST_STATIC_ASSERT(( sizeof app_types / sizeof *app_types == ReaderStatus::NumAppTypes )); |
189 | |
190 | |
191 | static 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 | |
200 | static int parse_app_version( const std::string & s ) { |
201 | return std::atoi( s.c_str() ); |
202 | } |
203 | |
204 | static 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 | |
218 | static 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 |
241 | static 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 | |
254 | static 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 | |
261 | static 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 |
278 | static 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 | |
286 | static 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 | |
291 | static 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 | |
307 | static 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 | |
378 | static 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 | |
393 | static 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 | |
406 | struct Transaction { |
407 | QByteArray command; |
408 | QPointer<QObject> receiver; |
409 | const char * slot; |
410 | GpgME::Error error; |
411 | }; |
412 | |
413 | static const Transaction checkTransaction = { "__check__" , 0, 0, Error() }; |
414 | static const Transaction updateTransaction = { "__update__" , 0, 0, Error() }; |
415 | static const Transaction quitTransaction = { "__quit__" , 0, 0, Error() }; |
416 | |
417 | namespace { |
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 | |
656 | class ReaderStatus::Private : ReaderStatusThread { |
657 | friend class Kleo::SmartCard::ReaderStatus; |
658 | ReaderStatus * const q; |
659 | public: |
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 | |
691 | private: |
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 | |
700 | private: |
701 | FileSystemWatcher watcher; |
702 | }; |
703 | |
704 | |
705 | ReaderStatus::ReaderStatus( QObject * parent ) |
706 | : QObject( parent ), d( new Private( this ) ) |
707 | { |
708 | self = this; |
709 | } |
710 | |
711 | ReaderStatus::~ReaderStatus() { self = 0; } |
712 | |
713 | // slot |
714 | void ReaderStatus::startMonitoring() { |
715 | d->start(); |
716 | } |
717 | |
718 | // static |
719 | ReaderStatus * ReaderStatus::mutableInstance() { |
720 | return self; |
721 | } |
722 | |
723 | // static |
724 | const ReaderStatus * ReaderStatus::instance() { |
725 | return self; |
726 | } |
727 | |
728 | ReaderStatus::Status ReaderStatus::cardStatus( unsigned int slot ) const { |
729 | return d->cardStatus( slot ); |
730 | } |
731 | |
732 | bool ReaderStatus::anyCardHasNullPin() const { |
733 | return d->anyCardHasNullPinImpl(); |
734 | } |
735 | |
736 | bool ReaderStatus::anyCardCanLearnKeys() const { |
737 | return d->anyCardCanLearnKeysImpl(); |
738 | } |
739 | |
740 | std::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 | |
748 | void ReaderStatus::startSimpleTransaction( const QByteArray & command, QObject * receiver, const char * slot ) { |
749 | const Transaction t = { command, receiver, slot, Error() }; |
750 | d->addTransaction( t ); |
751 | } |
752 | |
753 | void ReaderStatus::updateStatus() { |
754 | d->ping(); |
755 | } |
756 | |
757 | #include "readerstatus.moc" |
758 | |