KeyRing.cc

Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00012 #include <iostream>
00013 #include <fstream>
00014 #include <sys/file.h>
00015 #include <cstdio>
00016 #include <unistd.h>
00017 
00018 #include "zypp/ZYppFactory.h"
00019 #include "zypp/ZYpp.h"
00020 
00021 #include "zypp/base/Logger.h"
00022 #include "zypp/base/IOStream.h"
00023 #include "zypp/base/String.h"
00024 #include "zypp/Pathname.h"
00025 #include "zypp/KeyRing.h"
00026 #include "zypp/ExternalProgram.h"
00027 #include "zypp/TmpPath.h"
00028 
00029 using std::endl;
00030 using namespace zypp::filesystem;
00031 using namespace std;
00032 
00033 #undef  ZYPP_BASE_LOGGER_LOGGROUP
00034 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::KeyRing"
00035 
00037 namespace zypp
00038 { 
00039 
00040   IMPL_PTR_TYPE(KeyRing);
00041 
00042   static void dumpRegexpResults( const str::smatch &what )
00043   {
00044     for ( unsigned int k=0; k < what.size(); k++)
00045     {
00046       XXX << "[match "<< k << "] [" << what[k] << "]" << std::endl;
00047     }
00048   }
00049 
00050   static bool printLine( const std::string &line )
00051   {
00052     MIL <<  line << std::endl;
00053     return true;
00054   }
00055 
00056   static void dumpFile(const Pathname &file)
00057   {
00058     std::ifstream is(file.asString().c_str());
00059     iostr::forEachLine( is, printLine);
00060   }
00061 
00062   namespace
00063   {
00064     bool _keyRingDefaultAccept( getenv("ZYPP_KEYRING_DEFAULT_ACCEPT_ALL") );
00065   }
00066 
00067   bool KeyRingReport::askUserToAcceptUnsignedFile( const std::string &file )
00068   { return _keyRingDefaultAccept; }
00069 
00070   bool KeyRingReport::askUserToAcceptUnknownKey( const std::string &file, const std::string &id )
00071   { return _keyRingDefaultAccept; }
00072 
00073   bool KeyRingReport::askUserToTrustKey( const PublicKey &key )
00074   { return _keyRingDefaultAccept; }
00075 
00076   bool KeyRingReport::askUserToImportKey( const PublicKey &key)
00077   { return _keyRingDefaultAccept; }
00078   
00079   bool KeyRingReport::askUserToAcceptVerificationFailed( const std::string &file, const PublicKey &key )
00080   { return _keyRingDefaultAccept; }
00081   
00083   //
00084   //    CLASS NAME : KeyRing::Impl
00085   //
00087   struct KeyRing::Impl
00088   {
00089     Impl(const Pathname &baseTmpDir)
00090     : _trusted_tmp_dir(baseTmpDir, "zypp-trusted-kr")
00091    ,  _general_tmp_dir(baseTmpDir, "zypp-general-kr")
00092    , _base_dir( baseTmpDir )
00093 
00094     {
00095     }
00096 
00097     /*
00098     Impl( const Pathname &general_kr, const Pathname &trusted_kr )
00099     {
00100       filesystem::assert_dir(general_kr);
00101       filesystem::assert_dir(trusted_kr);
00102 
00103       generalKeyRing() = general_kr;
00104       trustedKeyRing() = trusted_kr;
00105     }
00106     */
00107 
00108     void importKey( const PublicKey &key, bool trusted = false);
00109     void deleteKey( const std::string &id, bool trusted );
00110     
00111     std::string readSignatureKeyId( const Pathname &signature );
00112     
00113     bool isKeyTrusted( const std::string &id);
00114     bool isKeyKnown( const std::string &id );
00115     
00116     std::list<PublicKey> trustedPublicKeys();
00117     std::list<PublicKey> publicKeys();
00118 
00119     void dumpPublicKey( const std::string &id, bool trusted, std::ostream &stream );
00120 
00121     bool verifyFileSignatureWorkflow( const Pathname &file, const std::string filedesc, const Pathname &signature);
00122 
00123     bool verifyFileSignature( const Pathname &file, const Pathname &signature);
00124     bool verifyFileTrustedSignature( const Pathname &file, const Pathname &signature);
00125   private:
00126     //mutable std::map<Locale, std::string> translations;
00127     bool verifyFile( const Pathname &file, const Pathname &signature, const Pathname &keyring);
00128     void importKey( const Pathname &keyfile, const Pathname &keyring);
00129     PublicKey exportKey( std::string id, const Pathname &keyring);
00130     void dumpPublicKey( const std::string &id, const Pathname &keyring, std::ostream &stream );
00131     void deleteKey( const std::string &id, const Pathname &keyring );
00132     std::list<PublicKey> publicKeys(const Pathname &keyring);
00133 
00134     bool publicKeyExists( std::string id, const Pathname &keyring);
00135 
00136     const Pathname generalKeyRing() const;
00137     const Pathname trustedKeyRing() const;
00138 
00139     // Used for trusted and untrusted keyrings
00140     TmpDir _trusted_tmp_dir;
00141     TmpDir _general_tmp_dir;
00142     Pathname _base_dir;
00143   public:
00145     static shared_ptr<Impl> nullimpl()
00146     {
00147       static shared_ptr<Impl> _nullimpl( new Impl( Pathname("/var/tmp") ) );
00148       return _nullimpl;
00149     }
00150 
00151   private:
00152     friend Impl * rwcowClone<Impl>( const Impl * rhs );
00154     Impl * clone() const
00155     { return new Impl( *this ); }
00156   };
00157 
00158 
00159   const Pathname KeyRing::Impl::generalKeyRing() const
00160   {
00161     return _general_tmp_dir.path();
00162   }
00163 
00164   const Pathname KeyRing::Impl::trustedKeyRing() const
00165   {
00166     return _trusted_tmp_dir.path();
00167   }
00168 
00169   void KeyRing::Impl::importKey( const PublicKey &key, bool trusted)
00170   {
00171     callback::SendReport<KeyRingSignals> emitSignal;
00172     
00173     importKey( key.path(), trusted ? trustedKeyRing() : generalKeyRing() );
00174   }
00175 
00176   void KeyRing::Impl::deleteKey( const std::string &id, bool trusted)
00177   {
00178     deleteKey( id, trusted ? trustedKeyRing() : generalKeyRing() );
00179   }
00180 
00181   std::list<PublicKey> KeyRing::Impl::publicKeys()
00182   {
00183     return publicKeys( generalKeyRing() );
00184   }
00185 
00186   std::list<PublicKey> KeyRing::Impl::trustedPublicKeys()
00187   {
00188     return publicKeys( trustedKeyRing() );
00189   }
00190 
00191   bool KeyRing::Impl::verifyFileTrustedSignature( const Pathname &file, const Pathname &signature)
00192   {
00193     return verifyFile( file, signature, trustedKeyRing() );
00194   }
00195 
00196   bool KeyRing::Impl::verifyFileSignature( const Pathname &file, const Pathname &signature)
00197   {
00198     return verifyFile( file, signature, generalKeyRing() );
00199   }
00200 
00201   bool KeyRing::Impl::isKeyTrusted( const std::string &id)
00202   {
00203     return publicKeyExists( id, trustedKeyRing() );
00204   }
00205   
00206   bool KeyRing::Impl::isKeyKnown( const std::string &id )
00207   {
00208     if ( publicKeyExists( id, trustedKeyRing() ) )
00209       return true;
00210     else
00211       return publicKeyExists( id, generalKeyRing() );
00212   }
00213   
00214   bool KeyRing::Impl::publicKeyExists( std::string id, const Pathname &keyring)
00215   {
00216     MIL << "Searching key [" << id << "] in keyring " << keyring << std::endl;
00217     std::list<PublicKey> keys = publicKeys(keyring);
00218     for (std::list<PublicKey>::const_iterator it = keys.begin(); it != keys.end(); it++)
00219     {
00220       if ( id == (*it).id() )
00221         return true;
00222     }
00223     return false;
00224   }
00225   
00226   PublicKey KeyRing::Impl::exportKey( std::string id, const Pathname &keyring)
00227   {
00228     TmpFile tmp_file( _base_dir, "pubkey-"+id+"-" );
00229     Pathname keyfile = tmp_file.path();
00230     MIL << "Going to export key " << id << " from " << keyring << " to " << keyfile << endl;
00231      
00232     try {
00233       std::ofstream os(keyfile.asString().c_str());
00234       dumpPublicKey( id, keyring, os );
00235       os.close();
00236       PublicKey key(keyfile);
00237       return key;
00238     }
00239     catch (BadKeyException &e)
00240     {
00241       ERR << "Cannot create public key " << id << " from " << keyring << " keyring  to file " << e.keyFile() << std::endl;
00242       ZYPP_THROW(Exception("Cannot create public key " + id + " from " + keyring.asString() + " keyring to file " + e.keyFile().asString() ) );
00243     }
00244     catch (std::exception &e)
00245     {
00246       ERR << "Cannot export key " << id << " from " << keyring << " keyring  to file " << keyfile << std::endl;
00247     }
00248     return PublicKey();
00249   }
00250 
00251   void KeyRing::Impl::dumpPublicKey( const std::string &id, bool trusted, std::ostream &stream )
00252   {
00253      dumpPublicKey( id, ( trusted ? trustedKeyRing() : generalKeyRing() ), stream );
00254   }
00255   
00256   void KeyRing::Impl::dumpPublicKey( const std::string &id, const Pathname &keyring, std::ostream &stream )
00257   {
00258     const char* argv[] =
00259     {
00260       "gpg",
00261       "--no-default-keyring",
00262       "--quiet",
00263       "--no-tty",
00264       "--no-greeting",
00265       "--no-permission-warning",
00266       "--batch",
00267       "--homedir",
00268       keyring.asString().c_str(),
00269       "-a",
00270       "--export",
00271       id.c_str(),
00272       NULL
00273     };
00274     ExternalProgram prog(argv,ExternalProgram::Discard_Stderr, false, -1, true);
00275     std::string line;
00276     int count;
00277     for(line = prog.receiveLine(), count=0; !line.empty(); line = prog.receiveLine(), count++ )
00278     {
00279       stream << line;
00280     }
00281     prog.close();
00282   }
00283 
00284 
00285   bool KeyRing::Impl::verifyFileSignatureWorkflow( const Pathname &file, const std::string filedesc, const Pathname &signature)
00286   {
00287     callback::SendReport<KeyRingReport> report;
00288     callback::SendReport<KeyRingSignals> emitSignal;
00289     MIL << "Going to verify signature for " << file << " with " << signature << std::endl;
00290 
00291     // if signature does not exists, ask user if he wants to accept unsigned file.
00292     if( signature.empty() || (!PathInfo(signature).isExist()) )
00293     {
00294       bool res = report->askUserToAcceptUnsignedFile( filedesc );
00295       MIL << "User decision on unsigned file: " << res << endl;
00296       return res;
00297     }
00298 
00299     // get the id of the signature
00300     std::string id = readSignatureKeyId(signature);
00301 
00302     // doeskey exists in trusted keyring
00303     if ( publicKeyExists( id, trustedKeyRing() ) )
00304     {
00305       PublicKey key = exportKey( id, trustedKeyRing() );
00306       
00307       MIL << "Key " << id << " " << key.name() << " is trusted" << std::endl;
00308       // it exists, is trusted, does it validates?
00309       if ( verifyFile( file, signature, trustedKeyRing() ) )
00310         return true;
00311       else
00312         return report->askUserToAcceptVerificationFailed( filedesc, key );
00313     }
00314     else
00315     {
00316       if ( publicKeyExists( id, generalKeyRing() ) )
00317       {
00318         PublicKey key =  exportKey( id, generalKeyRing());
00319         MIL << "Exported key " << id << " to " << key.path() << std::endl;
00320         MIL << "Key " << id << " " << key.name() << " is not trusted" << std::endl;
00321         // ok the key is not trusted, ask the user to trust it or not
00322         #warning We need the key details passed to the callback
00323         if ( report->askUserToTrustKey( key ) )
00324         {
00325           MIL << "User wants to trust key " << id << " " << key.name() << std::endl;
00326           //dumpFile(unKey.path());
00327 
00328           Pathname which_keyring;
00329           if ( report->askUserToImportKey( key ) )
00330           {
00331             MIL << "User wants to import key " << id << " " << key.name() << std::endl;
00332             importKey( key.path(), trustedKeyRing() );
00333             emitSignal->trustedKeyAdded( (const KeyRing &)(*this), key );
00334             which_keyring = trustedKeyRing();
00335           }
00336           else
00337           {
00338             which_keyring = generalKeyRing();
00339           }
00340 
00341           // emit key added
00342           if ( verifyFile( file, signature, which_keyring ) )
00343           {
00344             MIL << "File signature is verified" << std::endl;
00345             return true;
00346           }
00347           else
00348           {
00349             MIL << "File signature check fails" << std::endl;
00350             if ( report->askUserToAcceptVerificationFailed( filedesc, key ) )
00351             {
00352               MIL << "User continues anyway." << std::endl;
00353               return true;
00354             }
00355             else
00356             {
00357               MIL << "User does not want to continue" << std::endl;
00358               return false;
00359             }
00360           }
00361         }
00362         else
00363         {
00364           MIL << "User does not want to trust key " << id << " " << key.name() << std::endl;
00365           return false;
00366         }
00367       }
00368       else
00369       {
00370         // unknown key...
00371         MIL << "File [" << file << "] ( " << filedesc << " ) signed with unknown key [" << id << "]" << std::endl;
00372         if ( report->askUserToAcceptUnknownKey( filedesc, id ) )
00373         {
00374           MIL << "User wants to accept unknown key " << id << std::endl;
00375           return true;
00376         }
00377         else
00378         {
00379           MIL << "User does not want to accept unknown key " << id << std::endl;
00380           return false;
00381         }
00382       }
00383     }
00384     return false;
00385   }
00386 
00387   std::list<PublicKey> KeyRing::Impl::publicKeys(const Pathname &keyring)
00388   {
00389     const char* argv[] =
00390     {
00391       "gpg",
00392       "--no-default-keyring",
00393       "--quiet",
00394       "--list-public-keys",
00395       "--with-colons",
00396       "--with-fingerprint",
00397       "--no-tty",
00398       "--no-greeting",
00399       "--batch",
00400       "--status-fd",
00401       "1",
00402       "--homedir",
00403       keyring.asString().c_str(),
00404       NULL
00405     };
00406     std::list<PublicKey> keys;
00407 
00408     ExternalProgram prog(argv,ExternalProgram::Discard_Stderr, false, -1, true);
00409     std::string line;
00410     int count = 0;
00411 
00412     str::regex rxColons("^([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):\n$");
00413     str::regex rxColonsFpr("^([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):\n$");
00414 
00415     for(line = prog.receiveLine(), count=0; !line.empty(); line = prog.receiveLine(), count++ )
00416     {
00417       //MIL << line << std::endl;
00418       str::smatch what;
00419       if(str::regex_match(line, what, rxColons, str::match_extra))
00420       {
00421         string id;
00422         if ( what[1] == "pub" )
00423         {
00424           id = what[5];
00425           
00426           std::string line2;
00427           for(line2 = prog.receiveLine(); !line2.empty(); line2 = prog.receiveLine(), count++ )
00428           {
00429             str::smatch what2;
00430             if (str::regex_match(line2, what2, rxColonsFpr, str::match_extra))
00431             {
00432               if ( (what2[1] == "fpr") && (what2[1] != "pub") && (what2[1] !="sub"))
00433               {
00434                 //key.fingerprint = what2[10];
00435                 break;
00436               }
00437             }
00438           }
00439           PublicKey key(exportKey( id, keyring ));
00440           keys.push_back(key);
00441           MIL << "Found key " << "[" << key.id() << "]" << " [" << key.name() << "]" << " [" << key.fingerprint() << "]" << std::endl;
00442         }
00443         //dumpRegexpResults(what);
00444       }
00445     }
00446     prog.close();
00447     return keys;
00448   }
00449     
00450   void KeyRing::Impl::importKey( const Pathname &keyfile, const Pathname &keyring)
00451   {
00452     if ( ! PathInfo(keyfile).isExist() )
00453       ZYPP_THROW(KeyRingException("Tried to import not existant key " + keyfile.asString() + " into keyring " + keyring.asString()));
00454     
00455     const char* argv[] =
00456     {
00457       "gpg",
00458       "--no-default-keyring",
00459       "--quiet",
00460       "--no-tty",
00461       "--no-greeting",
00462       "--no-permission-warning",
00463       "--status-fd",
00464       "1",
00465       "--homedir",
00466       keyring.asString().c_str(),
00467       "--import",
00468       keyfile.asString().c_str(),
00469       NULL
00470     };
00471 
00472     int code;
00473     ExternalProgram prog(argv,ExternalProgram::Discard_Stderr, false, -1, true);
00474     code = prog.close();
00475 
00476     //if ( code != 0 )
00477     //  ZYPP_THROW(Exception("failed to import key"));
00478   }
00479 
00480   void KeyRing::Impl::deleteKey( const std::string &id, const Pathname &keyring )
00481   {
00482     const char* argv[] =
00483     {
00484       "gpg",
00485       "--no-default-keyring",
00486       "--yes",
00487       "--quiet",
00488       "--no-tty",
00489       "--batch",
00490       "--status-fd",
00491       "1",
00492       "--homedir",
00493       keyring.asString().c_str(),
00494       "--delete-keys",
00495       id.c_str(),
00496       NULL
00497     };
00498 
00499     ExternalProgram prog(argv,ExternalProgram::Discard_Stderr, false, -1, true);
00500 
00501     int code = prog.close();
00502     if ( code )
00503       ZYPP_THROW(Exception("Failed to delete key."));
00504     else
00505       MIL << "Deleted key " << id << " from keyring " << keyring << std::endl;
00506   }
00507 
00508 
00509   std::string KeyRing::Impl::readSignatureKeyId(const Pathname &signature )
00510   {
00511     MIL << "Deetermining key id if signature " << signature << std::endl;
00512     // HACK create a tmp keyring with no keys
00513     TmpDir dir(_base_dir, "fake-keyring");
00514     TmpFile fakeData(_base_dir, "fake-data");
00515 
00516     const char* argv[] =
00517     {
00518       "gpg",
00519       "--no-default-keyring",
00520       "--quiet",
00521       "--no-tty",
00522       "--no-greeting",
00523       "--batch",
00524       "--status-fd",
00525       "1",
00526       "--homedir",
00527       dir.path().asString().c_str(),
00528       "--verify",
00529       signature.asString().c_str(),
00530       fakeData.path().asString().c_str(),
00531       NULL
00532     };
00533 
00534     ExternalProgram prog(argv,ExternalProgram::Discard_Stderr, false, -1, true);
00535 
00536     std::string line;
00537     int count = 0;
00538 
00539     str::regex rxNoKey("^\\[GNUPG:\\] NO_PUBKEY (.+)\n$");
00540     std::string id;
00541     for(line = prog.receiveLine(), count=0; !line.empty(); line = prog.receiveLine(), count++ )
00542     {
00543       //MIL << "[" << line << "]" << std::endl;
00544       str::smatch what;
00545       if(str::regex_match(line, what, rxNoKey, str::match_extra))
00546       {
00547         if ( what.size() > 1 )
00548           id = what[1];
00549         //dumpRegexpResults(what);
00550       }
00551     }
00552     MIL << "Determined key id [" << id << "] for signature " << signature << std::endl;
00553     prog.close();
00554     return id;
00555   }
00556 
00557   bool KeyRing::Impl::verifyFile( const Pathname &file, const Pathname &signature, const Pathname &keyring)
00558   {
00559     const char* argv[] =
00560     {
00561       "gpg",
00562       "--no-default-keyring",
00563       "--quiet",
00564       "--no-tty",
00565       "--batch",
00566       "--no-greeting",
00567       "--status-fd",
00568       "1",
00569       "--homedir",
00570       keyring.asString().c_str(),
00571       "--verify",
00572       signature.asString().c_str(),
00573       file.asString().c_str(),
00574       NULL
00575     };
00576 
00577     // no need to parse output for now
00578     //     [GNUPG:] SIG_ID yCc4u223XRJnLnVAIllvYbUd8mQ 2006-03-29 1143618744
00579     //     [GNUPG:] GOODSIG A84EDAE89C800ACA SuSE Package Signing Key <build@suse.de>
00580     //     gpg: Good signature from "SuSE Package Signing Key <build@suse.de>"
00581     //     [GNUPG:] VALIDSIG 79C179B2E1C820C1890F9994A84EDAE89C800ACA 2006-03-29 1143618744 0 3 0 17 2 00 79C179B2E1C820C1890F9994A84EDAE89C800ACA
00582     //     [GNUPG:] TRUST_UNDEFINED
00583 
00584     //     [GNUPG:] ERRSIG A84EDAE89C800ACA 17 2 00 1143618744 9
00585     //     [GNUPG:] NO_PUBKEY A84EDAE89C800ACA
00586 
00587     ExternalProgram prog(argv,ExternalProgram::Discard_Stderr, false, -1, true);
00588 
00589     return (prog.close() == 0) ? true : false;
00590   }
00591 
00593 
00595   //
00596   //    CLASS NAME : KeyRing
00597   //
00599 
00601   //
00602   //    METHOD NAME : KeyRing::KeyRing
00603   //    METHOD TYPE : Ctor
00604   //
00605   KeyRing::KeyRing(const Pathname &baseTmpDir)
00606   : _pimpl( new Impl(baseTmpDir) )
00607   {}
00608 
00610   //
00611   //    METHOD NAME : KeyRing::KeyRing
00612   //    METHOD TYPE : Ctor
00613   //
00614   //KeyRing::KeyRing( const Pathname &general_kr, const Pathname &trusted_kr )
00615   //: _pimpl( new Impl(general_kr, trusted_kr) )
00616   //{}
00617 
00619   //
00620   //    METHOD NAME : KeyRing::~KeyRing
00621   //    METHOD TYPE : Dtor
00622   //
00623   KeyRing::~KeyRing()
00624   {}
00625 
00627   //
00628   // Forward to implementation:
00629   //
00631 
00632   
00633   void KeyRing::importKey( const PublicKey &key, bool trusted )
00634   {
00635     _pimpl->importKey( key.path(), trusted );
00636   }
00637   
00638   std::string KeyRing::readSignatureKeyId( const Pathname &signature )
00639   {
00640     return _pimpl->readSignatureKeyId(signature);
00641   }
00642 
00643   void KeyRing::deleteKey( const std::string &id, bool trusted )
00644   {
00645     _pimpl->deleteKey(id, trusted);
00646   }
00647 
00648   std::list<PublicKey> KeyRing::publicKeys()
00649   {
00650     return _pimpl->publicKeys();
00651   }
00652 
00653   std::list<PublicKey> KeyRing::trustedPublicKeys()
00654   {
00655     return _pimpl->trustedPublicKeys();
00656   }
00657 
00658   bool KeyRing::verifyFileSignatureWorkflow( const Pathname &file, const std::string filedesc, const Pathname &signature)
00659   {
00660     return _pimpl->verifyFileSignatureWorkflow(file, filedesc, signature);
00661   }
00662 
00663   bool KeyRing::verifyFileSignature( const Pathname &file, const Pathname &signature)
00664   {
00665     return _pimpl->verifyFileSignature(file, signature);
00666   }
00667 
00668   bool KeyRing::verifyFileTrustedSignature( const Pathname &file, const Pathname &signature)
00669   {
00670     return _pimpl->verifyFileTrustedSignature(file, signature);
00671   }
00672 
00673   void KeyRing::dumpPublicKey( const std::string &id, bool trusted, std::ostream &stream )
00674   {
00675     _pimpl->dumpPublicKey( id, trusted, stream);
00676   }
00677 
00678   bool KeyRing::isKeyTrusted( const std::string &id )
00679   {
00680     return _pimpl->isKeyTrusted(id);
00681   }
00682      
00683   bool KeyRing::isKeyKnown( const std::string &id )
00684   {
00685     return _pimpl->isKeyTrusted(id);
00686   }
00687   
00689 } // namespace zypp

Generated on Thu Apr 24 02:24:48 2008 for zypp by  doxygen 1.4.6