1 | /* |
2 | Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org> |
3 | |
4 | This library is free software; you can redistribute it and/or modify it |
5 | under the terms of the GNU Library General Public License as published by |
6 | the Free Software Foundation; either version 2 of the License, or (at your |
7 | option) any later version. |
8 | |
9 | This library is distributed in the hope that it will be useful, but WITHOUT |
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public |
12 | License for more details. |
13 | |
14 | You should have received a copy of the GNU Library General Public License |
15 | along with this library; see the file COPYING.LIB. If not, write to the |
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
17 | 02110-1301, USA. |
18 | */ |
19 | |
20 | #include "transport.h" |
21 | #include "transport_p.h" |
22 | #include "legacydecrypt.h" |
23 | #include "mailtransport_defs.h" |
24 | #include "transportmanager.h" |
25 | #include "transporttype_p.h" |
26 | |
27 | #include <QTimer> |
28 | |
29 | #include <KConfigGroup> |
30 | #include <KDebug> |
31 | #include <KLocalizedString> |
32 | #include <KMessageBox> |
33 | #include <KStringHandler> |
34 | #include <KWallet/Wallet> |
35 | |
36 | #include <akonadi/agentinstance.h> |
37 | #include <akonadi/agentmanager.h> |
38 | |
39 | using namespace MailTransport; |
40 | using namespace KWallet; |
41 | |
42 | Transport::Transport( const QString &cfgGroup ) : |
43 | TransportBase( cfgGroup ), d( new TransportPrivate ) |
44 | { |
45 | kDebug() << cfgGroup; |
46 | d->passwordLoaded = false; |
47 | d->passwordDirty = false; |
48 | d->storePasswordInFile = false; |
49 | d->needsWalletMigration = false; |
50 | d->passwordNeedsUpdateFromWallet = false; |
51 | readConfig(); |
52 | } |
53 | |
54 | Transport::~Transport() |
55 | { |
56 | delete d; |
57 | } |
58 | |
59 | bool Transport::isValid() const |
60 | { |
61 | return ( id() > 0 ) && !host().isEmpty() && port() <= 65536; |
62 | } |
63 | |
64 | QString Transport::password() |
65 | { |
66 | if ( !d->passwordLoaded && requiresAuthentication() && storePassword() && |
67 | d->password.isEmpty() ) { |
68 | readPassword(); |
69 | } |
70 | return d->password; |
71 | } |
72 | |
73 | void Transport::setPassword( const QString &passwd ) |
74 | { |
75 | d->passwordLoaded = true; |
76 | if ( d->password == passwd ) { |
77 | return; |
78 | } |
79 | d->passwordDirty = true; |
80 | d->password = passwd; |
81 | } |
82 | |
83 | void Transport::forceUniqueName() |
84 | { |
85 | QStringList existingNames; |
86 | foreach ( Transport *t, TransportManager::self()->transports() ) { |
87 | if ( t->id() != id() ) { |
88 | existingNames << t->name(); |
89 | } |
90 | } |
91 | int suffix = 1; |
92 | QString origName = name(); |
93 | while ( existingNames.contains( name() ) ) { |
94 | setName( i18nc( "%1: name; %2: number appended to it to make " |
95 | "it unique among a list of names" , "%1 #%2" , origName, suffix ) ); |
96 | ++suffix; |
97 | } |
98 | |
99 | } |
100 | |
101 | void Transport::updatePasswordState() |
102 | { |
103 | Transport *original = TransportManager::self()->transportById( id(), false ); |
104 | if ( original == this ) { |
105 | kWarning() << "Tried to update password state of non-cloned transport." ; |
106 | return; |
107 | } |
108 | if ( original ) { |
109 | d->password = original->d->password; |
110 | d->passwordLoaded = original->d->passwordLoaded; |
111 | d->passwordDirty = original->d->passwordDirty; |
112 | } else { |
113 | kWarning() << "Transport with this ID not managed by transport manager." ; |
114 | } |
115 | } |
116 | |
117 | bool Transport::isComplete() const |
118 | { |
119 | return !requiresAuthentication() || !storePassword() || d->passwordLoaded; |
120 | } |
121 | |
122 | QString Transport::authenticationTypeString() const |
123 | { |
124 | return Transport::authenticationTypeString( authenticationType() ); |
125 | } |
126 | |
127 | QString Transport::authenticationTypeString( int type ) |
128 | { |
129 | switch ( type ) { |
130 | case EnumAuthenticationType::LOGIN: |
131 | return QLatin1String( "LOGIN" ); |
132 | case EnumAuthenticationType::PLAIN: |
133 | return QLatin1String( "PLAIN" ); |
134 | case EnumAuthenticationType::CRAM_MD5: |
135 | return QLatin1String( "CRAM-MD5" ); |
136 | case EnumAuthenticationType::DIGEST_MD5: |
137 | return QLatin1String( "DIGEST-MD5" ); |
138 | case EnumAuthenticationType::NTLM: |
139 | return QLatin1String( "NTLM" ); |
140 | case EnumAuthenticationType::GSSAPI: |
141 | return QLatin1String( "GSSAPI" ); |
142 | case EnumAuthenticationType::CLEAR: |
143 | return i18nc( "Authentication method" , "Clear text" ); |
144 | case EnumAuthenticationType::APOP: |
145 | return QLatin1String( "APOP" ); |
146 | case EnumAuthenticationType::ANONYMOUS: |
147 | return i18nc( "Authentication method" , "Anonymous" ); |
148 | } |
149 | Q_ASSERT( false ); |
150 | return QString(); |
151 | } |
152 | |
153 | void Transport::usrReadConfig() |
154 | { |
155 | TransportBase::usrReadConfig(); |
156 | |
157 | setHost( host().trimmed() ); |
158 | |
159 | if ( d->oldName.isEmpty() ) { |
160 | d->oldName = name(); |
161 | } |
162 | |
163 | // Set TransportType. |
164 | { |
165 | using namespace Akonadi; |
166 | d->transportType = TransportType(); |
167 | d->transportType.d->mType = type(); |
168 | kDebug() << "type" << type(); |
169 | if ( type() == EnumType::Akonadi ) { |
170 | const AgentInstance instance = AgentManager::self()->instance( host() ); |
171 | if ( !instance.isValid() ) { |
172 | kWarning() << "Akonadi transport with invalid resource instance." ; |
173 | } |
174 | d->transportType.d->mAgentType = instance.type(); |
175 | kDebug() << "agent type" << instance.type().name() << "id" << instance.type().identifier(); |
176 | } |
177 | // Now we have the type and possibly agentType. Get the name, description |
178 | // etc. from TransportManager. |
179 | const TransportType::List &types = TransportManager::self()->types(); |
180 | int index = types.indexOf( d->transportType ); |
181 | if ( index != -1 ) { |
182 | d->transportType = types[ index ]; |
183 | } else { |
184 | kWarning() << "Type unknown to manager." ; |
185 | d->transportType.d->mName = i18nc( "An unknown transport type" , "Unknown" ); |
186 | } |
187 | } |
188 | |
189 | // we have everything we need |
190 | if ( !storePassword() ) { |
191 | return; |
192 | } |
193 | |
194 | if ( d->passwordLoaded ) { |
195 | if ( d->passwordNeedsUpdateFromWallet ) { |
196 | d->passwordNeedsUpdateFromWallet = false; |
197 | // read password if wallet is open, defer otherwise |
198 | if ( Wallet::isOpen( Wallet::NetworkWallet() ) ) { |
199 | // Don't read the password right away because this can lead |
200 | // to reentrancy problems in KDBusServiceStarter when an application |
201 | // run in Kontact creates the transports (due to a QEventLoop in the |
202 | // synchronous KWallet openWallet call). |
203 | QTimer::singleShot( 0, this, SLOT(readPassword()) ); |
204 | } else { |
205 | d->passwordLoaded = false; |
206 | } |
207 | } |
208 | |
209 | return; |
210 | } |
211 | |
212 | // try to find a password in the config file otherwise |
213 | KConfigGroup group( config(), currentGroup() ); |
214 | if ( group.hasKey( "password" ) ) { |
215 | d->password = KStringHandler::obscure( group.readEntry( "password" ) ); |
216 | } else if ( group.hasKey( "password-kmail" ) ) { |
217 | d->password = Legacy::decryptKMail( group.readEntry( "password-kmail" ) ); |
218 | } else if ( group.hasKey( "password-knode" ) ) { |
219 | d->password = Legacy::decryptKNode( group.readEntry( "password-knode" ) ); |
220 | } |
221 | |
222 | if ( !d->password.isEmpty() ) { |
223 | d->passwordLoaded = true; |
224 | if ( Wallet::isEnabled() ) { |
225 | d->needsWalletMigration = true; |
226 | } else { |
227 | d->storePasswordInFile = true; |
228 | } |
229 | } |
230 | } |
231 | |
232 | void Transport::usrWriteConfig() |
233 | { |
234 | if ( requiresAuthentication() && storePassword() && d->passwordDirty ) { |
235 | Wallet *wallet = TransportManager::self()->wallet(); |
236 | if ( !wallet || wallet->writePassword( QString::number( id() ), d->password ) != 0 ) { |
237 | // wallet saving failed, ask if we should store in the config file instead |
238 | if ( d->storePasswordInFile || KMessageBox::warningYesNo( |
239 | 0, |
240 | i18n( "KWallet is not available. It is strongly recommended to use " |
241 | "KWallet for managing your passwords.\n" |
242 | "However, the password can be stored in the configuration " |
243 | "file instead. The password is stored in an obfuscated format, " |
244 | "but should not be considered secure from decryption efforts " |
245 | "if access to the configuration file is obtained.\n" |
246 | "Do you want to store the password for server '%1' in the " |
247 | "configuration file?" , name() ), |
248 | i18n( "KWallet Not Available" ), |
249 | KGuiItem( i18n( "Store Password" ) ), |
250 | KGuiItem( i18n( "Do Not Store Password" ) ) ) == KMessageBox::Yes ) { |
251 | // write to config file |
252 | KConfigGroup group( config(), currentGroup() ); |
253 | group.writeEntry( "password" , KStringHandler::obscure( d->password ) ); |
254 | d->storePasswordInFile = true; |
255 | } |
256 | } |
257 | d->passwordDirty = false; |
258 | } |
259 | |
260 | TransportBase::usrWriteConfig(); |
261 | TransportManager::self()->emitChangesCommitted(); |
262 | if ( name() != d->oldName ) { |
263 | emit TransportManager::self()->transportRenamed( id(), d->oldName, name() ); |
264 | d->oldName = name(); |
265 | } |
266 | } |
267 | |
268 | void Transport::readPassword() |
269 | { |
270 | // no need to load a password if the account doesn't require auth |
271 | if ( !requiresAuthentication() ) { |
272 | return; |
273 | } |
274 | d->passwordLoaded = true; |
275 | |
276 | // check whether there is a chance to find our password at all |
277 | if ( Wallet::folderDoesNotExist( Wallet::NetworkWallet(), WALLET_FOLDER ) || |
278 | Wallet::keyDoesNotExist( Wallet::NetworkWallet(), WALLET_FOLDER, |
279 | QString::number( id() ) ) ) { |
280 | // try migrating password from kmail |
281 | if ( Wallet::folderDoesNotExist( Wallet::NetworkWallet(), KMAIL_WALLET_FOLDER ) || |
282 | Wallet::keyDoesNotExist( Wallet::NetworkWallet(), KMAIL_WALLET_FOLDER, |
283 | QString::fromLatin1( "transport-%1" ).arg( id() ) ) ) { |
284 | return; |
285 | } |
286 | kDebug() << "migrating password from kmail wallet" ; |
287 | KWallet::Wallet *wallet = TransportManager::self()->wallet(); |
288 | if ( wallet ) { |
289 | QString pwd; |
290 | wallet->setFolder( KMAIL_WALLET_FOLDER ); |
291 | if ( wallet->readPassword( QString::fromLatin1( "transport-%1" ).arg( id() ), pwd ) == 0 ) { |
292 | setPassword( pwd ); |
293 | writeConfig(); |
294 | } else { |
295 | d->password.clear(); |
296 | d->passwordLoaded = false; |
297 | } |
298 | wallet->removeEntry( QString::fromLatin1( "transport-%1" ).arg( id() ) ); |
299 | wallet->setFolder( WALLET_FOLDER ); |
300 | } |
301 | return; |
302 | } |
303 | |
304 | // finally try to open the wallet and read the password |
305 | KWallet::Wallet *wallet = TransportManager::self()->wallet(); |
306 | if ( wallet ) { |
307 | QString pwd; |
308 | if ( wallet->readPassword( QString::number( id() ), pwd ) == 0 ) { |
309 | setPassword( pwd ); |
310 | } else { |
311 | d->password.clear(); |
312 | d->passwordLoaded = false; |
313 | } |
314 | } |
315 | } |
316 | |
317 | bool Transport::needsWalletMigration() const |
318 | { |
319 | return d->needsWalletMigration; |
320 | } |
321 | |
322 | void Transport::migrateToWallet() |
323 | { |
324 | kDebug() << "migrating" << id() << "to wallet" ; |
325 | d->needsWalletMigration = false; |
326 | KConfigGroup group( config(), currentGroup() ); |
327 | group.deleteEntry( "password" ); |
328 | group.deleteEntry( "password-kmail" ); |
329 | group.deleteEntry( "password-knode" ); |
330 | d->passwordDirty = true; |
331 | d->storePasswordInFile = false; |
332 | writeConfig(); |
333 | } |
334 | |
335 | Transport *Transport::clone() const |
336 | { |
337 | QString id = currentGroup().mid( 10 ); |
338 | return new Transport( id ); |
339 | } |
340 | |
341 | TransportType Transport::transportType() const |
342 | { |
343 | if ( !d->transportType.isValid() ) { |
344 | kWarning() << "Invalid transport type." ; |
345 | } |
346 | return d->transportType; |
347 | } |
348 | |
349 | void Transport::setTransportType( const TransportType &type ) |
350 | { |
351 | Q_ASSERT( type.isValid() ); |
352 | d->transportType = type; |
353 | setType( type.type() ); |
354 | } |
355 | |
356 | |