polkit-policy-cache.c 16.9 KB
Newer Older
David Zeuthen's avatar
David Zeuthen committed
1
2
3
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
/***************************************************************************
 *
David Zeuthen's avatar
David Zeuthen committed
4
 * polkit-policy-cache.c : policy cache
David Zeuthen's avatar
David Zeuthen committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 *
 * Copyright (C) 2007 David Zeuthen, <david@fubar.dk>
 *
 * Licensed under the Academic Free License version 2.1
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307	 USA
 *
 **************************************************************************/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

30
31
#define _GNU_SOURCE

David Zeuthen's avatar
David Zeuthen committed
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <errno.h>
40
#include <syslog.h>
41
42
#include <fcntl.h>
#include <dirent.h>
David Zeuthen's avatar
David Zeuthen committed
43
44

#include <glib.h>
David Zeuthen's avatar
David Zeuthen committed
45
46
47
#include "polkit-debug.h"
#include "polkit-policy-file.h"
#include "polkit-policy-cache.h"
48
#include "polkit-private.h"
49
50
51
#include "polkit-memory.h"
#include "polkit-list.h"
#include "polkit-test.h"
David Zeuthen's avatar
David Zeuthen committed
52
53

/**
David Zeuthen's avatar
David Zeuthen committed
54
 * SECTION:polkit-policy-cache
David Zeuthen's avatar
David Zeuthen committed
55
56
 * @title: Policy Cache
 * @short_description: Holds the actions defined on the system.
David Zeuthen's avatar
David Zeuthen committed
57
58
59
60
61
62
63
64
65
66
67
 *
 * This class is used to hold all policy objects (stemming from policy
 * files) and provide look-up functions.
 **/

/**
 * PolKitPolicyCache:
 *
 * Instances of this class are used to hold all policy objects
 * (stemming from policy files) and provide look-up functions.
 **/
David Zeuthen's avatar
David Zeuthen committed
68
struct _PolKitPolicyCache
David Zeuthen's avatar
David Zeuthen committed
69
70
71
{
        int refcount;

72
        PolKitList *priv_entries;
David Zeuthen's avatar
David Zeuthen committed
73
74
75
};


76
77
static polkit_bool_t
_prepend_entry (PolKitPolicyFile       *policy_file,
78
79
               PolKitPolicyFileEntry  *policy_file_entry,
               void                   *user_data)
David Zeuthen's avatar
David Zeuthen committed
80
{
81
        PolKitList *l;
82
        PolKitPolicyCache *policy_cache = user_data;
David Zeuthen's avatar
David Zeuthen committed
83

David Zeuthen's avatar
David Zeuthen committed
84
        polkit_policy_file_entry_ref (policy_file_entry);
85
86
87
88
89
90
91
92
93
        l = polkit_list_prepend (policy_cache->priv_entries, policy_file_entry);
        if (l == NULL) {
                polkit_policy_file_entry_unref (policy_file_entry);
                goto oom;
        }
        policy_cache->priv_entries = l;
        return FALSE;
oom:
        return TRUE;
David Zeuthen's avatar
David Zeuthen committed
94
95
96
}

PolKitPolicyCache *
97
_polkit_policy_cache_new (const char *dirname, polkit_bool_t load_descriptions, PolKitError **error)
David Zeuthen's avatar
David Zeuthen committed
98
{
99
100
101
        DIR *dir;
        int dfd;
        struct dirent64 *d;
David Zeuthen's avatar
David Zeuthen committed
102
103
        PolKitPolicyCache *pc;

104
105
106
107
108
109
110
111
        dir = NULL;

        pc = p_new0 (PolKitPolicyCache, 1);
        if (pc == NULL) {
                polkit_error_set_error (error, POLKIT_ERROR_OUT_OF_MEMORY, "Out of memory");
                goto out;
        }

David Zeuthen's avatar
David Zeuthen committed
112
113
        pc->refcount = 1;

114
115
        dir = opendir (dirname);
        if (dir == NULL || (dfd = dirfd (dir)) == -1) {
116
                polkit_error_set_error (error, POLKIT_ERROR_POLICY_FILE_INVALID,
117
118
                                        "Cannot load policy files from directory %s: %m",
                                        dirname);
David Zeuthen's avatar
David Zeuthen committed
119
120
                goto out;
        }
121
122
123


        while ((d = readdir64(dir)) != NULL) {
David Zeuthen's avatar
David Zeuthen committed
124
125
                char *path;
                PolKitPolicyFile *pf;
126
                PolKitError *pk_error;
127
128
129
                size_t name_len;
                char *filename;
                static const char suffix[] = ".policy";
David Zeuthen's avatar
David Zeuthen committed
130

131
                if (d->d_type != DT_REG)
David Zeuthen's avatar
David Zeuthen committed
132
133
                        continue;

134
135
136
                filename = d->d_name;
                name_len = strlen (filename);
                if (name_len < sizeof (suffix) || strcmp ((filename + name_len - sizeof (suffix) + 1), suffix) != 0)
David Zeuthen's avatar
David Zeuthen committed
137
138
                        continue;

139
                path = g_strdup_printf ("%s/%s", dirname, filename);
David Zeuthen's avatar
David Zeuthen committed
140
141

                _pk_debug ("Loading %s", path);
142
143
                pk_error = NULL;
                pf = polkit_policy_file_new (path, load_descriptions, &pk_error);
David Zeuthen's avatar
David Zeuthen committed
144
145
146
                g_free (path);

                if (pf == NULL) {
147
148
149
150
151
                        if (polkit_error_get_error_code (pk_error) == POLKIT_ERROR_OUT_OF_MEMORY) {
                                polkit_error_set_error (error, POLKIT_ERROR_OUT_OF_MEMORY, "Out of memory");
                                goto out;
                        }

152
153
154
155
156
157
                        _pk_debug ("libpolkit: ignoring malformed policy file: %s", 
                                   polkit_error_get_error_message (pk_error));
                        syslog (LOG_ALERT, "libpolkit: ignoring malformed policy file: %s", 
                                polkit_error_get_error_message (pk_error));
                        polkit_error_free (pk_error);
                        continue;
David Zeuthen's avatar
David Zeuthen committed
158
159
                }

160
                /* steal entries */
161
162
163
164
165
166
                if (polkit_policy_file_entry_foreach (pf, _prepend_entry, pc)) {
                        /* OOM failure */
                        polkit_policy_file_unref (pf);
                        polkit_error_set_error (error, POLKIT_ERROR_OUT_OF_MEMORY, "Out of memory");
                        goto out;
                }
David Zeuthen's avatar
David Zeuthen committed
167
                polkit_policy_file_unref (pf);
David Zeuthen's avatar
David Zeuthen committed
168
        }
169
        closedir(dir);
David Zeuthen's avatar
David Zeuthen committed
170
171
172

        return pc;
out:
173
174
175
        if (dir != NULL)
                closedir(dir);

David Zeuthen's avatar
David Zeuthen committed
176
        if (pc != NULL)
177
                polkit_policy_cache_unref (pc);
David Zeuthen's avatar
David Zeuthen committed
178
179
180
181
        return NULL;
}

/**
David Zeuthen's avatar
David Zeuthen committed
182
 * polkit_policy_cache_ref:
David Zeuthen's avatar
David Zeuthen committed
183
184
185
186
187
188
189
 * @policy_cache: the policy cache object
 * 
 * Increase reference count.
 * 
 * Returns: the object
 **/
PolKitPolicyCache *
David Zeuthen's avatar
David Zeuthen committed
190
polkit_policy_cache_ref (PolKitPolicyCache *policy_cache)
David Zeuthen's avatar
David Zeuthen committed
191
192
193
194
195
196
197
{
        g_return_val_if_fail (policy_cache != NULL, policy_cache);
        policy_cache->refcount++;
        return policy_cache;
}

/**
David Zeuthen's avatar
David Zeuthen committed
198
 * polkit_policy_cache_unref:
David Zeuthen's avatar
David Zeuthen committed
199
200
201
202
203
204
205
 * @policy_cache: the policy cache object
 * 
 * Decreases the reference count of the object. If it becomes zero,
 * the object is freed. Before freeing, reference counts on embedded
 * objects are decresed by one.
 **/
void
David Zeuthen's avatar
David Zeuthen committed
206
polkit_policy_cache_unref (PolKitPolicyCache *policy_cache)
David Zeuthen's avatar
David Zeuthen committed
207
{
208
        PolKitList *i;
David Zeuthen's avatar
David Zeuthen committed
209
210
211
212
213
214

        g_return_if_fail (policy_cache != NULL);
        policy_cache->refcount--;
        if (policy_cache->refcount > 0) 
                return;

215
        for (i = policy_cache->priv_entries; i != NULL; i = i->next) {
David Zeuthen's avatar
David Zeuthen committed
216
                PolKitPolicyFileEntry *pfe = i->data;
David Zeuthen's avatar
David Zeuthen committed
217
                polkit_policy_file_entry_unref (pfe);
David Zeuthen's avatar
David Zeuthen committed
218
219
        }
        if (policy_cache->priv_entries != NULL)
220
                polkit_list_free (policy_cache->priv_entries);
David Zeuthen's avatar
David Zeuthen committed
221

222
        p_free (policy_cache);
David Zeuthen's avatar
David Zeuthen committed
223
224
225
}

/**
David Zeuthen's avatar
David Zeuthen committed
226
 * polkit_policy_cache_debug:
David Zeuthen's avatar
David Zeuthen committed
227
228
229
230
231
 * @policy_cache: the cache
 * 
 * Print debug information about object
 **/
void
David Zeuthen's avatar
David Zeuthen committed
232
polkit_policy_cache_debug (PolKitPolicyCache *policy_cache)
David Zeuthen's avatar
David Zeuthen committed
233
{
234
        PolKitList *i;
David Zeuthen's avatar
David Zeuthen committed
235
236
237
238
        g_return_if_fail (policy_cache != NULL);

        _pk_debug ("PolKitPolicyCache: refcount=%d num_entries=%d ...", 
                   policy_cache->refcount,
239
                   policy_cache->priv_entries == NULL ? 0 : polkit_list_length (policy_cache->priv_entries));
David Zeuthen's avatar
David Zeuthen committed
240

241
        for (i = policy_cache->priv_entries; i != NULL; i = i->next) {
David Zeuthen's avatar
David Zeuthen committed
242
                PolKitPolicyFileEntry *pfe = i->data;
David Zeuthen's avatar
David Zeuthen committed
243
                polkit_policy_file_entry_debug (pfe);
David Zeuthen's avatar
David Zeuthen committed
244
245
246
        }
}

247
248
249
/**
 * polkit_policy_cache_get_entry_by_id:
 * @policy_cache: the cache
David Zeuthen's avatar
David Zeuthen committed
250
 * @action_id: the action identifier
251
252
253
254
255
256
257
258
259
260
261
262
 * 
 * Given a action identifier, find the object describing the
 * definition of the policy; e.g. data stemming from files in
 * /usr/share/PolicyKit/policy.
 * 
 * Returns: A #PolKitPolicyFileEntry entry on sucess; otherwise
 * #NULL if the action wasn't identified. Caller shall not unref
 * this object.
 **/
PolKitPolicyFileEntry* 
polkit_policy_cache_get_entry_by_id (PolKitPolicyCache *policy_cache, const char *action_id)
{
263
        PolKitList *i;
264
265
266
267
268
269
270
        PolKitPolicyFileEntry *pfe;

        g_return_val_if_fail (policy_cache != NULL, NULL);
        g_return_val_if_fail (action_id != NULL, NULL);

        pfe = NULL;

271
        for (i = policy_cache->priv_entries; i != NULL; i = i->next) {
272
273
274
275
276
                pfe = i->data;
                if (strcmp (polkit_policy_file_entry_get_id (pfe), action_id) == 0) {
                        goto out;
                }
        }
277
        pfe = NULL;
278

279
280
281
282
        if (pfe == NULL) {
                /* the authdb backend may want to synthesize pfe's */
                pfe = _polkit_authorization_db_pfe_get_by_id (policy_cache, action_id);
        }
283
284
285
286
287

out:
        return pfe;        
}

David Zeuthen's avatar
David Zeuthen committed
288
/**
David Zeuthen's avatar
David Zeuthen committed
289
 * polkit_policy_cache_get_entry:
David Zeuthen's avatar
David Zeuthen committed
290
291
292
293
294
 * @policy_cache: the cache
 * @action: the action
 * 
 * Given a action, find the object describing the definition of the
 * policy; e.g. data stemming from files in
295
 * /usr/share/PolicyKit/policy.
David Zeuthen's avatar
David Zeuthen committed
296
297
298
299
300
301
 * 
 * Returns: A #PolKitPolicyFileEntry entry on sucess; otherwise
 * #NULL if the action wasn't identified. Caller shall not unref
 * this object.
 **/
PolKitPolicyFileEntry* 
David Zeuthen's avatar
David Zeuthen committed
302
polkit_policy_cache_get_entry (PolKitPolicyCache *policy_cache,
David Zeuthen's avatar
David Zeuthen committed
303
304
                                  PolKitAction      *action)
{
305
        char *action_id;
David Zeuthen's avatar
David Zeuthen committed
306
307
308
309
310
311
312
313
314
        PolKitPolicyFileEntry *pfe;

        /* I'm sure it would be easy to make this O(1)... */

        g_return_val_if_fail (policy_cache != NULL, NULL);
        g_return_val_if_fail (action != NULL, NULL);

        pfe = NULL;

315
316
317
318
        if (!polkit_action_get_action_id (action, &action_id))
                goto out;

        pfe = polkit_policy_cache_get_entry_by_id (policy_cache, action_id);
319

David Zeuthen's avatar
David Zeuthen committed
320
321
322
out:
        return pfe;
}
323
324
325
326
327
328
329
330

/**
 * polkit_policy_cache_foreach:
 * @policy_cache: the policy cache
 * @callback: callback function
 * @user_data: user data to pass to callback function
 * 
 * Visit all entries in the policy cache.
331
332
 *
 * Returns: #TRUE only if iteration was short-circuited
333
 **/
334
polkit_bool_t
335
336
337
338
polkit_policy_cache_foreach (PolKitPolicyCache *policy_cache, 
                             PolKitPolicyCacheForeachFunc callback,
                             void *user_data)
{
339
        PolKitList *i;
340
341
        PolKitPolicyFileEntry *pfe;

342
343
        g_return_val_if_fail (policy_cache != NULL, FALSE);
        g_return_val_if_fail (callback != NULL, FALSE);
344

345
        for (i = policy_cache->priv_entries; i != NULL; i = i->next) {
346
                pfe = i->data;
347
348
                if (callback (policy_cache, pfe, user_data))
                        return TRUE;
349
        }
350
351

        /* the authdb backend may also want to return synthesized pfe's */
352
353
354
        return _polkit_authorization_db_pfe_foreach (policy_cache,
                                                     callback,
                                                     user_data);
355
}
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370

/**
 * polkit_policy_cache_get_entry_by_annotation:
 * @policy_cache: the policy cache
 * @annotation_key: the key to check for
 * @annotation_value: the value to check for
 *
 * Find the first policy file entry where a given annotation matches a
 * given value. Note that there is nothing preventing the existence of
 * multiple policy file entries matching this criteria; it would
 * however be a packaging bug if this situation occured.
 *
 * Returns: The first #PolKitPolicyFileEntry matching the search
 * criteria. The caller shall not unref this object. Returns #NULL if
 * there are no policy file entries matching the search criteria.
371
372
 *
 * Since: 0.7
373
374
375
376
377
378
 */
PolKitPolicyFileEntry* 
polkit_policy_cache_get_entry_by_annotation (PolKitPolicyCache *policy_cache, 
                                             const char *annotation_key,
                                             const char *annotation_value)
{
379
        PolKitList *i;
380
381
382
383
384

        g_return_val_if_fail (policy_cache != NULL, NULL);
        g_return_val_if_fail (annotation_key != NULL, NULL);
        g_return_val_if_fail (annotation_value != NULL, NULL);

385
        for (i = policy_cache->priv_entries; i != NULL; i = i->next) {
386
387
388
389
390
391
392
393
394
395
396
397
398
399
                const char *value;
                PolKitPolicyFileEntry *pfe = i->data;

                value = polkit_policy_file_entry_get_annotation (pfe, annotation_key);
                if (value == NULL)
                        continue;

                if (strcmp (annotation_value, value) == 0) {
                        return pfe;
                }
        }

        return NULL;
}
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516

#ifdef POLKIT_BUILD_TESTS

static polkit_bool_t
_test_count (PolKitPolicyCache *pc, PolKitPolicyFileEntry *pfe, void *user_data)
{
        int *counter = (int *) user_data;
        const char *action_id;

        action_id = polkit_policy_file_entry_get_id (pfe);
        if (action_id != NULL && (strcmp (action_id, "org.example.valid1") == 0 ||
                                  strcmp (action_id, "org.example.valid2") == 0 ||
                                  strcmp (action_id, "org.example.valid2b") == 0 ||
                                  strcmp (action_id, "org.example.valid3") == 0 ||
                                  strcmp (action_id, "org.example.valid3b") == 0 ||
                                  strcmp (action_id, "org.example.valid4") == 0)) {
                *counter += 1;
        }
                    
        return FALSE;
}

static polkit_bool_t
_test_short_circuit (PolKitPolicyCache *pc, PolKitPolicyFileEntry *pfe, void *user_data)
{
        int *counter = (int *) user_data;
        *counter += 1;
        return TRUE;
}

static polkit_bool_t
_run_test (void)
{
        PolKitError *error;
        PolKitPolicyCache *pc;
        PolKitPolicyFileEntry *pfe;
        PolKitAction *a;
        int counter;

        error = NULL;
        g_assert (_polkit_policy_cache_new (TEST_DATA_DIR "/non-existant", TRUE, &error) == NULL);
        g_assert (polkit_error_is_set (error) && 
                  (polkit_error_get_error_code (error) == POLKIT_ERROR_POLICY_FILE_INVALID ||
                   polkit_error_get_error_code (error) == POLKIT_ERROR_OUT_OF_MEMORY));
        polkit_error_free (error);

        error = NULL;
        if ((pc = _polkit_policy_cache_new (TEST_DATA_DIR "/invalid", TRUE, &error)) == NULL) {
                g_assert (polkit_error_is_set (error) && 
                          polkit_error_get_error_code (error) == POLKIT_ERROR_OUT_OF_MEMORY);
                polkit_error_free (error);
        } else {
                polkit_policy_cache_unref (pc);
        }

        error = NULL;
        if ((pc = _polkit_policy_cache_new (TEST_DATA_DIR "/valid", TRUE, &error)) == NULL) {
                g_assert (polkit_error_is_set (error) && 
                          polkit_error_get_error_code (error) == POLKIT_ERROR_OUT_OF_MEMORY);
                polkit_error_free (error);
                goto out;
        }

        g_assert (polkit_policy_cache_get_entry_by_id (pc, "org.example.valid1") != NULL);
        g_assert (polkit_policy_cache_get_entry_by_id (pc, "org.example.non-existant") == NULL);

        pfe = polkit_policy_cache_get_entry_by_annotation (pc, "the.key1", "Some Value 1");
        g_assert (pfe != NULL && strcmp (polkit_policy_file_entry_get_id (pfe), "org.example.valid2") == 0);
        pfe = polkit_policy_cache_get_entry_by_annotation (pc, "the.key2", "Some Value 2");
        g_assert (pfe != NULL && strcmp (polkit_policy_file_entry_get_id (pfe), "org.example.valid2") == 0);
        pfe = polkit_policy_cache_get_entry_by_annotation (pc, "the.key1", "Some Value 1b");
        g_assert (pfe != NULL && strcmp (polkit_policy_file_entry_get_id (pfe), "org.example.valid2b") == 0);
        pfe = polkit_policy_cache_get_entry_by_annotation (pc, "the.key1", "NON-EXISTANT VALUE");
        g_assert (pfe == NULL);
        pfe = polkit_policy_cache_get_entry_by_annotation (pc, "NON_EXISTANT KEY", "NON-EXISTANT VALUE");
        g_assert (pfe == NULL);

        if ((a = polkit_action_new ()) != NULL) {
                pfe = polkit_policy_cache_get_entry (pc, a);
                g_assert (pfe == NULL);
                if (polkit_action_set_action_id (a, "org.example.valid1")) {
                        pfe = polkit_policy_cache_get_entry (pc, a);
                        g_assert (pfe != NULL && strcmp (polkit_policy_file_entry_get_id (pfe), "org.example.valid1") == 0);
                }
                if (polkit_action_set_action_id (a, "org.example.non-existant")) {
                        pfe = polkit_policy_cache_get_entry (pc, a);
                        g_assert (pfe == NULL);
                }

                polkit_action_unref (a);
        }

        counter = 0;
        polkit_policy_cache_foreach (pc, _test_count, &counter);
        g_assert (counter == 6);

        counter = 0;
        polkit_policy_cache_foreach (pc, _test_short_circuit, &counter);
        g_assert (counter == 1);

        polkit_policy_cache_debug (pc);
        polkit_policy_cache_ref (pc);
        polkit_policy_cache_unref (pc);
        polkit_policy_cache_unref (pc);

out:
        return TRUE;
}

PolKitTest _test_policy_cache = {
        "polkit_policy_cache",
        NULL,
        NULL,
        _run_test
};

#endif /* POLKIT_BUILD_TESTS */