2003-02-22 Havoc Pennington <hp@pobox.com>
[platform/upstream/dbus.git] / dbus / dbus-keyring.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* dbus-keyring.c Store secret cookies in your homedir
3  *
4  * Copyright (C) 2003  Red Hat Inc.
5  *
6  * Licensed under the Academic Free License version 1.2
7  * 
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  * 
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #include "dbus-keyring.h"
25 #include <dbus/dbus-string.h>
26 #include <dbus/dbus-list.h>
27 #include <dbus/dbus-sysdeps.h>
28
29 /**
30  * @defgroup DBusKeyring keyring class
31  * @ingroup  DBusInternals
32  * @brief DBusKeyring data structure
33  *
34  * Types and functions related to DBusKeyring. DBusKeyring is intended
35  * to manage cookies used to authenticate clients to servers.  This is
36  * essentially the "verify that client can read the user's homedir"
37  * authentication mechanism.  Both client and server must have access
38  * to the homedir.
39  *
40  * The secret keys are not kept in locked memory, and are written to a
41  * file in the user's homedir. However they are transient (only used
42  * by a single server instance for a fixed period of time, then
43  * discarded). Also, the keys are not sent over the wire.
44  */
45
46 /**
47  * @defgroup DBusKeyringInternals DBusKeyring implementation details
48  * @ingroup  DBusInternals
49  * @brief DBusKeyring implementation details
50  *
51  * The guts of DBusKeyring.
52  *
53  * @{
54  */
55
56 /** The maximum age of a key before we create a new key to use in
57  * challenges.  This isn't super-reliably enforced, since system
58  * clocks can change or be wrong, but we make a best effort to only
59  * use keys for a short time.
60  */
61 #define NEW_KEY_TIMEOUT     (60*5)
62 /**
63  * The time after which we drop a key from the secrets file
64  */
65 #define EXPIRE_KEYS_TIMEOUT (NEW_KEY_TIMEOUT + (60*2))
66
67 typedef struct
68 {
69   dbus_int32_t id; /**< identifier used to refer to the key */
70
71   unsigned long creation_time; /**< when the key was generated,
72                                 *   as unix timestamp
73                                 */
74   
75   DBusString secret; /**< the actual key */
76
77 } DBusKey;
78
79 /**
80  * @brief Internals of DBusKeyring.
81  * 
82  * DBusKeyring internals. DBusKeyring is an opaque object, it must be
83  * used via accessor functions.
84  */
85 struct DBusKeyring
86 {
87   int refcount;             /**< Reference count */
88   DBusString directory;     /**< Directory the below two items are inside */
89   DBusString filename;      /**< Keyring filename */
90   DBusString filename_lock; /**< Name of lockfile */
91   DBusKey *keys; /**< Keys loaded from the file */
92   int n_keys;    /**< Number of keys */
93 };
94
95 static DBusKeyring*
96 _dbus_keyring_new (void)
97 {
98   DBusKeyring *keyring;
99
100   keyring = dbus_new0 (DBusKeyring, 1);
101   if (keyring == NULL)
102     goto out_0;
103   
104   if (!_dbus_string_init (&keyring->directory))
105     goto out_1;
106
107   if (!_dbus_string_init (&keyring->filename))
108     goto out_2;
109
110   if (!_dbus_string_init (&keyring->filename_lock))
111     goto out_3;
112
113   keyring->refcount = 1;
114   keyring->keys = NULL;
115   keyring->n_keys = 0;
116
117   return keyring;
118   
119  out_3:
120   _dbus_string_free (&keyring->filename);
121  out_2:
122   _dbus_string_free (&keyring->directory);
123  out_1:
124   dbus_free (keyring);
125  out_0:
126   return NULL;
127 }
128
129 static void
130 free_keys (DBusKey *keys,
131            int      n_keys)
132 {
133   int i;
134
135   /* should be safe for args NULL, 0 */
136   
137   i = 0;
138   while (i < n_keys)
139     {
140       _dbus_string_free (&keys[i].secret);
141       ++i;
142     }
143
144   dbus_free (keys);
145 }
146
147 /* Our locking scheme is highly unreliable.  However, there is
148  * unfortunately no reliable locking scheme in user home directories;
149  * between bugs in Linux NFS, people using Tru64 or other total crap
150  * NFS, AFS, random-file-system-of-the-week, and so forth, fcntl() in
151  * homedirs simply generates tons of bug reports. This has been
152  * learned through hard experience with GConf, unfortunately.
153  *
154  * This bad hack might work better for the kind of lock we have here,
155  * which we don't expect to hold for any length of time.  Crashing
156  * while we hold it should be unlikely, and timing out such that we
157  * delete a stale lock should also be unlikely except when the
158  * filesystem is running really slowly.  Stuff might break in corner
159  * cases but as long as it's not a security-level breakage it should
160  * be OK.
161  */
162
163 /** Maximum number of timeouts waiting for lock before we decide it's stale */
164 #define MAX_LOCK_TIMEOUTS 6
165 /** Length of each timeout while waiting for a lock */
166 #define LOCK_TIMEOUT 500
167
168 static dbus_bool_t
169 _dbus_keyring_lock (DBusKeyring *keyring)
170 {
171   int n_timeouts;
172   
173   n_timeouts = 0;
174   while (n_timeouts < MAX_LOCK_TIMEOUTS)
175     {
176       DBusError error;
177
178       dbus_error_init (&error);
179       if (_dbus_create_file_exclusively (&keyring->filename_lock,
180                                          &error))
181         break;
182
183       _dbus_verbose ("Did not get lock file: %s\n",
184                      error.message);
185       dbus_error_free (&error);
186
187       _dbus_sleep_milliseconds (LOCK_TIMEOUT);
188       
189       ++n_timeouts;
190     }
191
192   if (n_timeouts == MAX_LOCK_TIMEOUTS)
193     {
194       _dbus_verbose ("Lock file timed out, assuming stale\n");
195
196       _dbus_delete_file (&keyring->filename_lock);
197
198       if (!_dbus_create_file_exclusively (&keyring->filename_lock,
199                                           NULL))
200         {
201           _dbus_verbose ("Couldn't create lock file after trying to delete the stale one, giving up\n");
202           return FALSE;
203         }
204     }
205   
206   return TRUE;
207 }
208
209 static void
210 _dbus_keyring_unlock (DBusKeyring *keyring)
211 {
212   if (!_dbus_delete_file (&keyring->filename_lock))
213     _dbus_warn ("Failed to delete lock file\n");
214 }
215
216 /**
217  * Reloads the keyring file, optionally adds one new key to the file,
218  * removes all expired keys from the file, then resaves the file.
219  * Stores the keys from the file in keyring->keys.
220  *
221  * @param keyring the keyring
222  * @param add_new #TRUE to add a new key to the file before resave
223  * @param error return location for errors
224  * @returns #FALSE on failure
225  */
226 static dbus_bool_t
227 _dbus_keyring_reload (DBusKeyring *keyring,
228                       dbus_bool_t  add_new,
229                       DBusError   *error)
230 {
231   /* FIXME */
232
233 }
234
235 /** @} */ /* end of internals */
236
237 /**
238  * @addtogroup DBusKeyring
239  *
240  * @{
241  */
242
243 /**
244  * Increments reference count of the keyring
245  *
246  * @param keyring the keyring
247  */
248 void
249 _dbus_keyring_ref (DBusKeyring *keyring)
250 {
251   keyring->refcount += 1;
252 }
253
254 /**
255  * Decrements refcount and finalizes if it reaches
256  * zero.
257  *
258  * @param keyring the keyring
259  */
260 void
261 _dbus_keyring_unref (DBusKeyring *keyring)
262 {
263   keyring->refcount -= 1;
264
265   if (keyring->refcount == 0)
266     {
267       _dbus_string_free (&keyring->filename);
268       _dbus_string_free (&keyring->filename_lock);
269       _dbus_string_free (&keyring->directory);
270       free_keys (keyring->keys, keyring->n_keys);
271       dbus_free (keyring);      
272     }
273 }
274
275 /**
276  * Creates a new keyring that lives in the ~/.dbus-keyrings
277  * directory of the given user. If the username is #NULL,
278  * uses the user owning the current process.
279  *
280  * @param username username to get keyring for, or #NULL
281  * @param context which keyring to get
282  * @param error return location for errors
283  * @returns the keyring or #NULL on error
284  */
285 DBusKeyring*
286 _dbus_keyring_new_homedir (const DBusString *username,
287                            const DBusString *context,
288                            DBusError        *error)
289 {
290   DBusString homedir;
291   DBusKeyring *keyring;
292   dbus_bool_t error_set;
293   DBusString dotdir;
294   DBusString lock_extension;
295   
296   keyring = NULL;
297   error_set = FALSE;
298   
299   if (!_dbus_string_init (&homedir, _DBUS_INT_MAX))
300     return FALSE;
301
302   _dbus_string_init_const (&dotdir, ".dbus-keyrings");
303   _dbus_string_init_const (&lock_extension, ".lock");
304   
305   if (username == NULL)
306     {
307       const DBusString *const_homedir;
308       
309       if (!_dbus_user_info_from_current_process (&username,
310                                                  &const_homedir,
311                                                  NULL))
312         goto failed;
313
314       if (!_dbus_string_copy (const_homedir, 0,
315                               &homedir, 0))
316         goto failed;
317     }
318   else
319     {
320       if (!_dbus_homedir_from_username (username, &homedir))
321         goto failed;
322     }
323
324   keyring = _dbus_keyring_new ();
325   if (keyring == NULL)
326     goto failed;
327
328   /* should have been validated already, but paranoia check here */
329   if (!_dbus_keyring_validate_context (context))
330     {
331       error_set = TRUE;
332       dbus_set_error_const (error,
333                             DBUS_ERROR_FAILED,
334                             "Invalid context in keyring creation");
335       goto failed;
336     }
337       
338   if (!_dbus_string_copy (&homedir, 0,
339                           &keyring->directory, 0))
340     goto failed;
341
342   if (!_dbus_concat_dir_and_file (&keyring->directory,
343                                   &dotdir))
344     goto failed;
345
346   if (!_dbus_string_copy (&keyring->directory, 0,
347                           &keyring->filename, 0))
348     goto failed;
349
350   if (!_dbus_concat_dir_and_file (&keyring->filename,
351                                   context))
352     goto failed;
353
354   if (!_dbus_string_copy (&keyring->filename, 0,
355                           &keyring->filename_lock, 0))
356     goto failed;
357
358   if (!_dbus_concat_dir_and_file (&keyring->filename_lock,
359                                   &lock_extension))
360     goto failed;
361
362   return keyring;
363   
364  failed:
365   if (!error_set)
366     dbus_set_error_const (error,
367                           DBUS_ERROR_NO_MEMORY,
368                           "No memory to create keyring");
369   if (keyring)
370     _dbus_keyring_unref (keyring);
371   _dbus_string_free (&homedir);
372   return FALSE;
373
374 }
375
376 /**
377  * Checks whether the context is a valid context.
378  * Contexts that might cause confusion when used
379  * in filenames are not allowed (contexts can't
380  * start with a dot or contain dir separators).
381  *
382  * @param context the context
383  * @returns #TRUE if valid
384  */
385 dbus_bool_t
386 _dbus_keyring_validate_context (const DBusString *context)
387 {
388   if (_dbus_string_length (context) == 0)
389     {
390       _dbus_verbose ("context is zero-length\n");
391       return FALSE;
392     }
393
394   if (!_dbus_string_validate_ascii (context, 0,
395                                     _dbus_string_get_length (context)))
396     {
397       _dbus_verbose ("context not valid ascii\n");
398       return FALSE;
399     }
400   
401   /* no directory separators */  
402   if (_dbus_string_find (context, 0, "/", NULL))
403     {
404       _dbus_verbose ("context contains a slash\n");
405       return FALSE;
406     }
407
408   if (_dbus_string_find (context, 0, "\\", NULL))
409     {
410       _dbus_verbose ("context contains a backslash\n");
411       return FALSE;
412     }
413
414   /* prevent attempts to use dotfiles or ".." or ".lock"
415    * all of which might allow some kind of attack
416    */
417   if (_dbus_string_find (context, 0, ".", NULL))
418     {
419       _dbus_verbose ("context contains a dot\n");
420       return FALSE;
421     }
422
423   return TRUE;
424 }
425
426 static DBusKey*
427 find_recent_key (DBusKeyring *keyring)
428 {
429   int i;
430   long tv_sec, tv_usec;
431
432   _dbus_get_current_time (&tv_sec, &tv_usec);
433   
434   i = 0;
435   while (i < keyring->n_keys)
436     {
437       DBusKey *key = &keyring->keys[i];
438
439       if (tv_sec - NEW_KEY_TIMEOUT < key->creation_time)
440         return key;
441       
442       ++i;
443     }
444
445   return NULL;
446 }
447
448 /**
449  * Gets a recent key to use for authentication.
450  * If no recent key exists, creates one. Returns
451  * the key ID. If a key can't be written to the keyring
452  * file so no recent key can be created, returns -1.
453  * All valid keys are > 0.
454  *
455  * @param keyring the keyring
456  * @param error error on failure
457  * @returns key ID to use for auth, or -1 on failure
458  */
459 int
460 _dbus_keyring_get_best_key (DBusKeyring  *keyring,
461                             DBusError   **error)
462 {
463   DBusKey *key;
464
465   key = find_recent_key (keyring);
466   if (key)
467     return key->id;
468
469   /* All our keys are too old, or we've never loaded the
470    * keyring. Create a new one.
471    */
472   if (!_dbus_keyring_reload (keyring, TRUE,
473                              error))
474     return -1;
475
476   key = find_recent_key (keyring);
477   if (key)
478     return key->id;
479   else
480     {
481       dbus_set_error_const (error,
482                             DBUS_ERROR_FAILED,
483                             "No recent-enough key found in keyring, and unable to create a new key");
484       return -1;
485     }
486 }
487
488 /** @} */ /* end of exposed API */
489