1 /* -*- mode: c++; c-basic-offset:4 -*-
4 This file is part of Kleopatra, the KDE keymanager
5 Copyright (c) 2007 Klarälvdalens Datakonsult AB
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.
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.
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
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
33 #include <config-kleopatra.h>
36 #include "uiserver_p.h"
38 #include "sessiondata.h"
40 #include <utils/detail_p.h>
41 #include <utils/gnupg-helper.h>
43 #include <kleo/stl_util.h>
44 #include <kleo/exception.h>
47 #include <KLocalizedString>
55 #include <boost/range/empty.hpp>
56 #include <boost/bind.hpp>
63 using namespace boost
;
66 void UiServer::setLogStream( FILE * stream
) {
67 assuan_set_assuan_log_stream( stream
);
70 UiServer::Private::Private( UiServer
* qq
)
76 suggestedSocketName(),
78 cryptoCommandsEnabled( false )
81 assuan_set_assuan_err_source( GPG_ERR_SOURCE_DEFAULT
);
83 assuan_set_gpg_err_source( GPG_ERR_SOURCE_DEFAULT
);
88 bool UiServer::Private::isStaleAssuanSocket( const QString
& fileName
)
90 assuan_context_t ctx
= 0;
92 const bool error
= assuan_socket_connect_ext( &ctx
, QFile::encodeName( fileName
).constData(), -1, 0 );
94 const bool error
= assuan_new( &ctx
) || assuan_socket_connect( ctx
, QFile::encodeName( fileName
).constData(), ASSUAN_INVALID_PID
, 0 );
98 assuan_disconnect( ctx
);
100 assuan_release( ctx
);
105 UiServer::UiServer( const QString
& socket
, QObject
* p
)
106 : QObject( p
), d( new Private( this ) )
108 d
->suggestedSocketName
= d
->makeFileName( socket
);
111 UiServer::~UiServer() {
112 if ( QFile::exists( d
->actualSocketName
) )
113 QFile::remove( d
->actualSocketName
);
116 bool UiServer::registerCommandFactory( const shared_ptr
<AssuanCommandFactory
> & cf
) {
117 if ( cf
&& empty( std::equal_range( d
->factories
.begin(), d
->factories
.end(), cf
, _detail::ByName
<std::less
>() ) ) ) {
118 d
->factories
.push_back( cf
);
119 std::inplace_merge( d
->factories
.begin(), d
->factories
.end() - 1, d
->factories
.end(), _detail::ByName
<std::less
>() );
123 kWarning() << "NULL factory";
125 kWarning() << ( void* )cf
.get() << " factory already registered";
132 void UiServer::start() {
133 d
->makeListeningSocket();
136 void UiServer::stop() {
140 if ( d
->file
.exists() )
144 SessionDataHandler::instance()->clear();
150 void UiServer::enableCryptoCommands( bool on
) {
151 if ( on
== d
->cryptoCommandsEnabled
)
153 d
->cryptoCommandsEnabled
= on
;
154 kdtools::for_each( d
->connections
,
155 boost::bind( &AssuanServerConnection::enableCryptoCommands
, _1
, on
) );
158 QString
UiServer::socketName() const {
159 return d
->actualSocketName
;
162 bool UiServer::waitForStopped( unsigned int ms
) {
167 timer
.setInterval( ms
);
168 timer
.setSingleShot( true );
169 connect( &timer
, SIGNAL(timeout()), &loop
, SLOT(quit()) );
170 connect( this, SIGNAL(stopped()), &loop
, SLOT(quit()) );
172 return !timer
.isActive();
175 bool UiServer::isStopped() const {
176 return d
->connections
.empty() && !d
->isListening() ;
179 bool UiServer::isStopping() const {
180 return !d
->connections
.empty() && !d
->isListening() ;
183 void UiServer::Private::slotConnectionClosed( Kleo::AssuanServerConnection
* conn
) {
184 kDebug() << "UiServer: connection " << ( void* )conn
<< " closed";
185 connections
.erase( std::remove_if( connections
.begin(), connections
.end(),
186 boost::bind( &boost::shared_ptr
<AssuanServerConnection
>::get
, _1
) == conn
),
188 if ( q
->isStopped() ) {
189 SessionDataHandler::instance()->clear();
195 void UiServer::Private::incomingConnection( int fd
) {
197 kDebug() << "UiServer: client connect on fd " << fd
;
198 #if defined(HAVE_ASSUAN_SOCK_GET_NONCE) || defined(HAVE_ASSUAN2)
199 if ( assuan_sock_check_nonce( (assuan_fd_t
)fd
, &nonce
) ) {
200 kDebug() << "UiServer: nonce check failed";
201 assuan_sock_close( (assuan_fd_t
)fd
);
205 const shared_ptr
<AssuanServerConnection
> c( new AssuanServerConnection( (assuan_fd_t
)fd
, factories
) );
206 connect( c
.get(), SIGNAL(closed(Kleo::AssuanServerConnection
*)),
207 this, SLOT(slotConnectionClosed(Kleo::AssuanServerConnection
*)) );
208 connect( c
.get(), SIGNAL(startKeyManagerRequested()),
209 q
, SIGNAL(startKeyManagerRequested()), Qt::QueuedConnection
);
210 connect( c
.get(), SIGNAL(startConfigDialogRequested()),
211 q
, SIGNAL(startConfigDialogRequested()), Qt::QueuedConnection
);
212 c
->enableCryptoCommands( cryptoCommandsEnabled
);
213 connections
.push_back( c
);
214 kDebug() << "UiServer: client connection " << ( void *)c
.get() << " established successfully";
215 } catch ( const Exception
& e
) {
216 kDebug() << "UiServer: client connection failed: " << e
.what();
218 s
.setSocketDescriptor( fd
);
219 QTextStream( &s
) << "ERR " << e
.error_code() << " " << e
.what() << "\r\n";
220 s
.waitForBytesWritten();
223 kDebug() << "UiServer: client connection failed: unknown exception caught";
224 // this should never happen...
226 s
.setSocketDescriptor( fd
);
227 QTextStream( &s
) << "ERR 63 unknown exception caught\r\n";
228 s
.waitForBytesWritten();
233 QString
UiServer::Private::makeFileName( const QString
& socket
) const {
234 if ( !socket
.isEmpty() )
236 const QString gnupgHome
= gnupgHomeDirectory();
237 if ( gnupgHome
.isEmpty() )
238 throw_
<std::runtime_error
>( i18n( "Could not determine the GnuPG home directory. Consider setting the GNUPGHOME environment variable." ) );
239 ensureDirectoryExists( gnupgHome
);
240 const QDir
dir( gnupgHome
);
241 assert( dir
.exists() );
242 return dir
.absoluteFilePath( QLatin1String("S.uiserver") );
245 void UiServer::Private::ensureDirectoryExists( const QString
& path
) const {
246 const QFileInfo
info( path
);
247 if ( info
.exists() && !info
.isDir() )
248 throw_
<std::runtime_error
>( i18n( "Cannot determine the GnuPG home directory: %1 exists but is not a directory.", path
) );
251 const QDir dummy
; //there is no static QDir::mkpath()...
253 if ( !dummy
.mkpath( path
) )
254 throw_
<std::runtime_error
>( i18n( "Could not create GnuPG home directory %1: %2", path
, systemErrorString() ) );
257 void UiServer::Private::makeListeningSocket() {
259 // First, create a file (we do this only for the name, gmpfh)
260 const QString fileName
= suggestedSocketName
;
262 if ( QFile::exists( fileName
) ) {
263 if ( isStaleAssuanSocket( fileName
) ) {
264 QFile::remove( fileName
);
266 throw_
<std::runtime_error
>( i18n( "Detected another running gnupg UI server listening at %1.", fileName
) );
270 doMakeListeningSocket( QFile::encodeName( fileName
) );
272 actualSocketName
= suggestedSocketName
;
275 #include "moc_uiserver_p.cpp"