1/*
2 * kwallet.cpp: KWallet provider for SVN_AUTH_CRED_*
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26
27
28/*** Includes. ***/
29
30#include <stdlib.h>
31#include <string.h>
32#include <unistd.h>
33
34#include <apr_pools.h>
35#include <apr_strings.h>
36
37#include <dbus/dbus.h>
38#include <QtCore/QCoreApplication>
39#include <QtCore/QString>
40
41#include <kaboutdata.h>
42#include <kcmdlineargs.h>
43#include <kcomponentdata.h>
44#include <klocalizedstring.h>
45#include <kwallet.h>
46
47#include "svn_auth.h"
48#include "svn_config.h"
49#include "svn_error.h"
50#include "svn_io.h"
51#include "svn_pools.h"
52#include "svn_string.h"
53#include "svn_version.h"
54
55#include "private/svn_auth_private.h"
56
57#include "svn_private_config.h"
58
59
60/*-----------------------------------------------------------------------*/
61/* KWallet simple provider, puts passwords in KWallet                    */
62/*-----------------------------------------------------------------------*/
63
64static int q_argc = 1;
65static char q_argv0[] = "svn"; // Build non-const char * from string constant
66static char *q_argv[] = { q_argv0 };
67
68static const char *
69get_application_name(apr_hash_t *parameters,
70                     apr_pool_t *pool)
71{
72  svn_config_t *config =
73    static_cast<svn_config_t *> (apr_hash_get(parameters,
74                                              SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
75                                              APR_HASH_KEY_STRING));
76  svn_boolean_t svn_application_name_with_pid;
77  svn_config_get_bool(config,
78                      &svn_application_name_with_pid,
79                      SVN_CONFIG_SECTION_AUTH,
80                      SVN_CONFIG_OPTION_KWALLET_SVN_APPLICATION_NAME_WITH_PID,
81                      FALSE);
82  const char *svn_application_name;
83  if (svn_application_name_with_pid)
84    {
85      svn_application_name = apr_psprintf(pool, "Subversion [%ld]", long(getpid()));
86    }
87  else
88    {
89      svn_application_name = "Subversion";
90    }
91  return svn_application_name;
92}
93
94static QString
95get_wallet_name(apr_hash_t *parameters)
96{
97  svn_config_t *config =
98    static_cast<svn_config_t *> (apr_hash_get(parameters,
99                                              SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
100                                              APR_HASH_KEY_STRING));
101  const char *wallet_name;
102  svn_config_get(config,
103                 &wallet_name,
104                 SVN_CONFIG_SECTION_AUTH,
105                 SVN_CONFIG_OPTION_KWALLET_WALLET,
106                 "");
107  if (strcmp(wallet_name, "") == 0)
108    {
109      return KWallet::Wallet::NetworkWallet();
110    }
111  else
112    {
113      return QString::fromUtf8(wallet_name);
114    }
115}
116
117static WId
118get_wid(void)
119{
120  WId wid = 1;
121  const char *wid_env_string = getenv("WINDOWID");
122
123  if (wid_env_string)
124    {
125      apr_int64_t wid_env;
126      svn_error_t *err;
127
128      err = svn_cstring_atoi64(&wid_env, wid_env_string);
129      if (err)
130        svn_error_clear(err);
131      else
132        wid = (WId)wid_env;
133    }
134
135  return wid;
136}
137
138static KWallet::Wallet *
139get_wallet(QString wallet_name,
140           apr_hash_t *parameters)
141{
142  KWallet::Wallet *wallet =
143    static_cast<KWallet::Wallet *> (apr_hash_get(parameters,
144                                                 "kwallet-wallet",
145                                                 APR_HASH_KEY_STRING));
146  if (! wallet && ! apr_hash_get(parameters,
147                                 "kwallet-opening-failed",
148                                 APR_HASH_KEY_STRING))
149    {
150      wallet = KWallet::Wallet::openWallet(wallet_name, get_wid(),
151                                           KWallet::Wallet::Synchronous);
152    }
153  if (wallet)
154    {
155      apr_hash_set(parameters,
156                   "kwallet-wallet",
157                   APR_HASH_KEY_STRING,
158                   wallet);
159    }
160  else
161    {
162      apr_hash_set(parameters,
163                   "kwallet-opening-failed",
164                   APR_HASH_KEY_STRING,
165                   "");
166    }
167  return wallet;
168}
169
170static apr_status_t
171kwallet_terminate(void *data)
172{
173  apr_hash_t *parameters = static_cast<apr_hash_t *> (data);
174  if (apr_hash_get(parameters, "kwallet-initialized", APR_HASH_KEY_STRING))
175    {
176      KWallet::Wallet *wallet = get_wallet(NULL, parameters);
177      delete wallet;
178      apr_hash_set(parameters,
179                   "kwallet-initialized",
180                   APR_HASH_KEY_STRING,
181                   NULL);
182    }
183  return APR_SUCCESS;
184}
185
186/* Implementation of svn_auth__password_get_t that retrieves
187   the password from KWallet. */
188static svn_error_t *
189kwallet_password_get(svn_boolean_t *done,
190                     const char **password,
191                     apr_hash_t *creds,
192                     const char *realmstring,
193                     const char *username,
194                     apr_hash_t *parameters,
195                     svn_boolean_t non_interactive,
196                     apr_pool_t *pool)
197{
198  QString wallet_name = get_wallet_name(parameters);
199
200  *done = FALSE;
201
202  if (! dbus_bus_get(DBUS_BUS_SESSION, NULL))
203    {
204      return SVN_NO_ERROR;
205    }
206
207  if (non_interactive)
208    {
209      if (!KWallet::Wallet::isOpen(wallet_name))
210        return SVN_NO_ERROR;
211
212      /* There is a race here: the wallet was open just now, but will
213         it still be open when we come to use it below? */
214    }
215
216  QCoreApplication *app;
217  if (! qApp)
218    {
219      int argc = q_argc;
220      app = new QCoreApplication(argc, q_argv);
221    }
222
223  KCmdLineArgs::init(q_argc, q_argv,
224                     get_application_name(parameters, pool),
225                     "subversion",
226                     ki18n(get_application_name(parameters, pool)),
227                     SVN_VER_NUMBER,
228                     ki18n("Version control system"),
229                     KCmdLineArgs::CmdLineArgKDE);
230  KComponentData component_data(KCmdLineArgs::aboutData());
231  QString folder = QString::fromUtf8("Subversion");
232  QString key =
233    QString::fromUtf8(username) + "@" + QString::fromUtf8(realmstring);
234  if (! KWallet::Wallet::keyDoesNotExist(wallet_name, folder, key))
235    {
236      KWallet::Wallet *wallet = get_wallet(wallet_name, parameters);
237      if (wallet)
238        {
239          apr_hash_set(parameters,
240                       "kwallet-initialized",
241                       APR_HASH_KEY_STRING,
242                       "");
243          if (wallet->setFolder(folder))
244            {
245              QString q_password;
246              if (wallet->readPassword(key, q_password) == 0)
247                {
248                  *password = apr_pstrmemdup(pool,
249                                             q_password.toUtf8().data(),
250                                             q_password.size());
251                  *done = TRUE;
252                }
253            }
254        }
255    }
256
257  apr_pool_cleanup_register(pool, parameters, kwallet_terminate,
258                            apr_pool_cleanup_null);
259
260  return SVN_NO_ERROR;
261}
262
263/* Implementation of svn_auth__password_set_t that stores
264   the password in KWallet. */
265static svn_error_t *
266kwallet_password_set(svn_boolean_t *done,
267                     apr_hash_t *creds,
268                     const char *realmstring,
269                     const char *username,
270                     const char *password,
271                     apr_hash_t *parameters,
272                     svn_boolean_t non_interactive,
273                     apr_pool_t *pool)
274{
275  QString wallet_name = get_wallet_name(parameters);
276
277  *done = FALSE;
278
279  if (! dbus_bus_get(DBUS_BUS_SESSION, NULL))
280    {
281      return SVN_NO_ERROR;
282    }
283
284  if (non_interactive)
285    {
286      if (!KWallet::Wallet::isOpen(wallet_name))
287        return SVN_NO_ERROR;
288
289      /* There is a race here: the wallet was open just now, but will
290         it still be open when we come to use it below? */
291    }
292
293  QCoreApplication *app;
294  if (! qApp)
295    {
296      int argc = q_argc;
297      app = new QCoreApplication(argc, q_argv);
298    }
299
300  KCmdLineArgs::init(q_argc, q_argv,
301                     get_application_name(parameters, pool),
302                     "subversion",
303                     ki18n(get_application_name(parameters, pool)),
304                     SVN_VER_NUMBER,
305                     ki18n("Version control system"),
306                     KCmdLineArgs::CmdLineArgKDE);
307  KComponentData component_data(KCmdLineArgs::aboutData());
308  QString q_password = QString::fromUtf8(password);
309  QString folder = QString::fromUtf8("Subversion");
310  KWallet::Wallet *wallet = get_wallet(wallet_name, parameters);
311  if (wallet)
312    {
313      apr_hash_set(parameters,
314                   "kwallet-initialized",
315                   APR_HASH_KEY_STRING,
316                   "");
317      if (! wallet->hasFolder(folder))
318        {
319          wallet->createFolder(folder);
320        }
321      if (wallet->setFolder(folder))
322        {
323          QString key = QString::fromUtf8(username) + "@"
324            + QString::fromUtf8(realmstring);
325          if (wallet->writePassword(key, q_password) == 0)
326            {
327              *done = TRUE;
328            }
329        }
330    }
331
332  apr_pool_cleanup_register(pool, parameters, kwallet_terminate,
333                            apr_pool_cleanup_null);
334
335  return SVN_NO_ERROR;
336}
337
338/* Get cached encrypted credentials from the simple provider's cache. */
339static svn_error_t *
340kwallet_simple_first_creds(void **credentials,
341                           void **iter_baton,
342                           void *provider_baton,
343                           apr_hash_t *parameters,
344                           const char *realmstring,
345                           apr_pool_t *pool)
346{
347  return svn_auth__simple_creds_cache_get(credentials,
348                                          iter_baton,
349                                          provider_baton,
350                                          parameters,
351                                          realmstring,
352                                          kwallet_password_get,
353                                          SVN_AUTH__KWALLET_PASSWORD_TYPE,
354                                          pool);
355}
356
357/* Save encrypted credentials to the simple provider's cache. */
358static svn_error_t *
359kwallet_simple_save_creds(svn_boolean_t *saved,
360                          void *credentials,
361                          void *provider_baton,
362                          apr_hash_t *parameters,
363                          const char *realmstring,
364                          apr_pool_t *pool)
365{
366  return svn_auth__simple_creds_cache_set(saved, credentials,
367                                          provider_baton,
368                                          parameters,
369                                          realmstring,
370                                          kwallet_password_set,
371                                          SVN_AUTH__KWALLET_PASSWORD_TYPE,
372                                          pool);
373}
374
375static const svn_auth_provider_t kwallet_simple_provider = {
376  SVN_AUTH_CRED_SIMPLE,
377  kwallet_simple_first_creds,
378  NULL,
379  kwallet_simple_save_creds
380};
381
382/* Public API */
383extern "C" {
384void
385svn_auth_get_kwallet_simple_provider(svn_auth_provider_object_t **provider,
386                                     apr_pool_t *pool)
387{
388  svn_auth_provider_object_t *po =
389    static_cast<svn_auth_provider_object_t *> (apr_pcalloc(pool, sizeof(*po)));
390
391  po->vtable = &kwallet_simple_provider;
392  *provider = po;
393}
394}
395
396
397/*-----------------------------------------------------------------------*/
398/* KWallet SSL client certificate passphrase provider,                   */
399/* puts passphrases in KWallet                                           */
400/*-----------------------------------------------------------------------*/
401
402/* Get cached encrypted credentials from the ssl client cert password
403   provider's cache. */
404static svn_error_t *
405kwallet_ssl_client_cert_pw_first_creds(void **credentials,
406                                       void **iter_baton,
407                                       void *provider_baton,
408                                       apr_hash_t *parameters,
409                                       const char *realmstring,
410                                       apr_pool_t *pool)
411{
412  return svn_auth__ssl_client_cert_pw_cache_get(credentials,
413                                                iter_baton, provider_baton,
414                                                parameters, realmstring,
415                                                kwallet_password_get,
416                                                SVN_AUTH__KWALLET_PASSWORD_TYPE,
417                                                pool);
418}
419
420/* Save encrypted credentials to the ssl client cert password provider's
421   cache. */
422static svn_error_t *
423kwallet_ssl_client_cert_pw_save_creds(svn_boolean_t *saved,
424                                      void *credentials,
425                                      void *provider_baton,
426                                      apr_hash_t *parameters,
427                                      const char *realmstring,
428                                      apr_pool_t *pool)
429{
430  return svn_auth__ssl_client_cert_pw_cache_set(saved, credentials,
431                                                provider_baton, parameters,
432                                                realmstring,
433                                                kwallet_password_set,
434                                                SVN_AUTH__KWALLET_PASSWORD_TYPE,
435                                                pool);
436}
437
438static const svn_auth_provider_t kwallet_ssl_client_cert_pw_provider = {
439  SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
440  kwallet_ssl_client_cert_pw_first_creds,
441  NULL,
442  kwallet_ssl_client_cert_pw_save_creds
443};
444
445/* Public API */
446extern "C" {
447void
448svn_auth_get_kwallet_ssl_client_cert_pw_provider
449    (svn_auth_provider_object_t **provider,
450     apr_pool_t *pool)
451{
452  svn_auth_provider_object_t *po =
453    static_cast<svn_auth_provider_object_t *> (apr_pcalloc(pool, sizeof(*po)));
454
455  po->vtable = &kwallet_ssl_client_cert_pw_provider;
456  *provider = po;
457}
458}
459