From ffc99a261f23a2baecf5f777d89f9a14d57d7c0f Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Sat, 12 Sep 2009 15:49:53 -0400 Subject: [PATCH] Implement lockdown for the Local Authority implementation --- actions/org.freedesktop.policykit.policy.in | 11 + data/org.freedesktop.PolicyKit1.Authority.xml | 2 +- docs/man/Makefile.am | 2 + docs/man/pklalockdown.xml | 136 ++++++++++++ docs/man/pklocalauthority.xml | 21 +- docs/polkit/polkit-1-docs.xml | 3 +- docs/polkit/polkit-1-sections.txt | 1 + src/polkit/polkitauthorizationresult.c | 25 +++ src/polkit/polkitauthorizationresult.h | 1 + .../polkitbackendinteractiveauthority.c | 14 +- .../polkitbackendinteractiveauthority.h | 6 +- src/polkitbackend/polkitbackendlocalauthority.c | 12 +- .../polkitbackendlocalauthorizationstore.c | 61 +++++- .../polkitbackendlocalauthorizationstore.h | 3 +- src/programs/Makefile.am | 15 +- src/programs/pklalockdown.c | 243 +++++++++++++++++++++ 16 files changed, 537 insertions(+), 19 deletions(-) create mode 100644 docs/man/pklalockdown.xml create mode 100644 src/programs/pklalockdown.c diff --git a/actions/org.freedesktop.policykit.policy.in b/actions/org.freedesktop.policykit.policy.in index d44a0c1..3961001 100644 --- a/actions/org.freedesktop.policykit.policy.in +++ b/actions/org.freedesktop.policykit.policy.in @@ -58,4 +58,15 @@ auth_admin + + + <_description>Configure lockdown on the Local Authority + <_message>Authentication is required to configure lock down policy + + no + no + auth_admin + + /usr/bin/pklalockdown + diff --git a/data/org.freedesktop.PolicyKit1.Authority.xml b/data/org.freedesktop.PolicyKit1.Authority.xml index 7854a96..e71dc20 100644 --- a/data/org.freedesktop.PolicyKit1.Authority.xml +++ b/data/org.freedesktop.PolicyKit1.Authority.xml @@ -129,7 +129,7 @@ - + diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 076608b..6f164c9 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -10,6 +10,7 @@ man_MANS = \ pkexec.1 \ pkcheck.1 \ pkaction.1 \ + pklalockdown.1 \ $(NULL) %.8 %.1 : %.xml @@ -24,6 +25,7 @@ EXTRA_DIST = \ pkexec.xml \ pkcheck.xml \ pkaction.xml \ + pklalockdown.xml \ $(NULL) clean-local: diff --git a/docs/man/pklalockdown.xml b/docs/man/pklalockdown.xml new file mode 100644 index 0000000..74e4f5d --- /dev/null +++ b/docs/man/pklalockdown.xml @@ -0,0 +1,136 @@ + + +]> + + + pklalockdown + May 2009 + polkit + + + + pklalockdown + 1 + + + + + pklalockdown + Configure lockdown for the Local Authority + + + + + pklalockdown + + + + + + pklalockdown + + + action + + + + + pklalockdown + + + action + + + + + + + DESCRIPTION + + pklalockdown is used to configure lockdown + for the Local Authority. + + + The effect of locking down an action is that administrator + authentication is always needed in order for subjects to acquire + the authorization for the action in question (and the subject + has to be in an active session on a local console). The obtained + authorization is temporary and as such typically expires five + minutes after being obtained. + + + To lock down action use the option. + To remove a lockdown for action use the option. + + + + + REQUIRED AUTHORIZATIONS + + The org.freedesktop.policykit.localauthority.lockdown + authorization is needed to add or remove lockdown. By default, + this authorization requires administrator authentication and + cannot be retained. + + + + + IMPLEMENTATION DETAILS + + Lockdown is implemented through .pkla + files. Locked down actions supersede other most other Local + Authority configuration as the .pkla files + are placed + in /var/lib/polkit-1/localauthority90-mandatory.d. + + + Programs checking authorizations can check whether an action is + locked down via by checking + the polkit.localauthority.lockdown key/value pair in + the details of the authorization response. + + + + + RETURN VALUE + + On success pklalockdown returns 0. Otherwise a + non-zero value is returned and a diagnostic message is printed + on standard error. + + + + AUTHOR + + Written by David Zeuthen davidz@redhat.com with + a lot of help from many others. + + + + + BUGS + + Please send bug reports to either the distribution or the + polkit-devel mailing list, + see the link + on how to subscribe. + + + + + SEE ALSO + + + polkit8 + , + + pkcheck1 + , + + pklocalauthority8 + + + + diff --git a/docs/man/pklocalauthority.xml b/docs/man/pklocalauthority.xml index 52dded2..495aa41 100644 --- a/docs/man/pklocalauthority.xml +++ b/docs/man/pklocalauthority.xml @@ -155,7 +155,7 @@ Each group in a .pkla must have a name that is unique within the file it belongs to. The following keys are - required in each group + are processed. @@ -214,12 +214,23 @@ + + ReturnValue + + + A semi-colon separated list of key/value pairs (of the + form key=value) that are add to the details of + authorization result on positive matches. + + + All keys specified above are required except that only at least one of RequireAny, RequireInactive - and RequireActive is present. + and RequireActive is + present. The ReturnValue key is optional. @@ -240,8 +251,10 @@ authorization check matches the data from the authorization check, then the authorization result from RequireAny, RequireInactive - or RequireActive is used. Finally, the - authorization entries are consulted using the user identity. + or RequireActive is used + and ReturnValue is added to the + authorization result. Finally, the authorization entries are + consulted using the user identity in the same manner. Note that processing continues even after a match. This allows diff --git a/docs/polkit/polkit-1-docs.xml b/docs/polkit/polkit-1-docs.xml index f09954b..357efdf 100644 --- a/docs/polkit/polkit-1-docs.xml +++ b/docs/polkit/polkit-1-docs.xml @@ -110,10 +110,11 @@ Manual Pages - + + diff --git a/docs/polkit/polkit-1-sections.txt b/docs/polkit/polkit-1-sections.txt index 333e2c8..f5bc3a8 100644 --- a/docs/polkit/polkit-1-sections.txt +++ b/docs/polkit/polkit-1-sections.txt @@ -64,6 +64,7 @@ polkit_authorization_result_get_is_authorized polkit_authorization_result_get_is_challenge polkit_authorization_result_get_retains_authorization polkit_authorization_result_get_temporary_authorization_id +polkit_authorization_result_get_local_authority_lock_down polkit_authorization_result_get_details PolkitAuthorizationResultClass diff --git a/src/polkit/polkitauthorizationresult.c b/src/polkit/polkitauthorizationresult.c index 40abed1..e55ae88 100644 --- a/src/polkit/polkitauthorizationresult.c +++ b/src/polkit/polkitauthorizationresult.c @@ -276,3 +276,28 @@ polkit_authorization_result_get_temporary_authorization_id (PolkitAuthorizationR return ret; } + +/** + * polkit_authorization_result_get_local_authority_lock_down: + * @result: A #PolkitAuthorizationResult. + * + * Gets whether the action is locked down in the Local Authority via pklalockdown(1). + * + * This method simply reads the value of the key/value pair in @details with the + * key polkit.localauthority.lockdown. + * + * Returns: %TRUE if the authorization is or will be temporary. + */ +gboolean +polkit_authorization_result_get_local_authority_lock_down (PolkitAuthorizationResult *result) +{ + gboolean ret; + PolkitDetails *details; + + ret = FALSE; + details = polkit_authorization_result_get_details (result); + if (details != NULL && polkit_details_lookup (details, "polkit.localauthority.lockdown") != NULL) + ret = TRUE; + + return ret; +} diff --git a/src/polkit/polkitauthorizationresult.h b/src/polkit/polkitauthorizationresult.h index ea479fe..ae00f83 100644 --- a/src/polkit/polkitauthorizationresult.h +++ b/src/polkit/polkitauthorizationresult.h @@ -52,6 +52,7 @@ gboolean polkit_authorization_result_get_is_authorized (P gboolean polkit_authorization_result_get_is_challenge (PolkitAuthorizationResult *result); gboolean polkit_authorization_result_get_retains_authorization (PolkitAuthorizationResult *result); const gchar *polkit_authorization_result_get_temporary_authorization_id (PolkitAuthorizationResult *result); +gboolean polkit_authorization_result_get_local_authority_lock_down (PolkitAuthorizationResult *result); /* ---------------------------------------------------------------------------------------------------- */ diff --git a/src/polkitbackend/polkitbackendinteractiveauthority.c b/src/polkitbackend/polkitbackendinteractiveauthority.c index bf88c2b..add7a60 100644 --- a/src/polkitbackend/polkitbackendinteractiveauthority.c +++ b/src/polkitbackend/polkitbackendinteractiveauthority.c @@ -715,7 +715,8 @@ check_authorization_sync (PolkitBackendAuthority *authority, session_is_active, action_id, details, - implicit_authorization); + implicit_authorization, + result_details); /* first see if there's an implicit authorization for subject available */ if (implicit_authorization == POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED) @@ -844,9 +845,12 @@ polkit_backend_interactive_authority_get_admin_identities (PolkitBackendInteract * @action_id: The action we are checking an authorization for. * @details: Details about the action. * @implicit: A #PolkitImplicitAuthorization value computed from the policy file and @subject. + * @out_details: A #PolkitDetails object that will be return to @caller. * * Checks whether @subject is authorized to perform the action - * specified by @action_id and @details. + * specified by @action_id and @details. The implementation may + * append key/value pairs to @out_details to return extra information + * to @caller. * * The default implementation of this method simply returns @implicit. * @@ -862,7 +866,8 @@ polkit_backend_interactive_authority_check_authorization_sync (PolkitBackendInte gboolean subject_is_active, const gchar *action_id, PolkitDetails *details, - PolkitImplicitAuthorization implicit) + PolkitImplicitAuthorization implicit, + PolkitDetails *out_details) { PolkitBackendInteractiveAuthorityClass *klass; PolkitImplicitAuthorization ret; @@ -883,7 +888,8 @@ polkit_backend_interactive_authority_check_authorization_sync (PolkitBackendInte subject_is_active, action_id, details, - implicit); + implicit, + out_details); } return ret; diff --git a/src/polkitbackend/polkitbackendinteractiveauthority.h b/src/polkitbackend/polkitbackendinteractiveauthority.h index 9820dac..408c3e4 100644 --- a/src/polkitbackend/polkitbackendinteractiveauthority.h +++ b/src/polkitbackend/polkitbackendinteractiveauthority.h @@ -83,7 +83,8 @@ struct _PolkitBackendInteractiveAuthorityClass gboolean subject_is_active, const gchar *action_id, PolkitDetails *details, - PolkitImplicitAuthorization implicit); + PolkitImplicitAuthorization implicit, + PolkitDetails *out_details); /*< private >*/ /* Padding for future expansion */ @@ -138,7 +139,8 @@ PolkitImplicitAuthorization polkit_backend_interactive_authority_check_authoriza gboolean subject_is_active, const gchar *action_id, PolkitDetails *details, - PolkitImplicitAuthorization implicit); + PolkitImplicitAuthorization implicit, + PolkitDetails *out_details); G_END_DECLS diff --git a/src/polkitbackend/polkitbackendlocalauthority.c b/src/polkitbackend/polkitbackendlocalauthority.c index a3fb7f2..932706e 100644 --- a/src/polkitbackend/polkitbackendlocalauthority.c +++ b/src/polkitbackend/polkitbackendlocalauthority.c @@ -79,7 +79,8 @@ static PolkitImplicitAuthorization polkit_backend_local_authority_check_authoriz gboolean subject_is_active, const gchar *action_id, PolkitDetails *details, - PolkitImplicitAuthorization implicit); + PolkitImplicitAuthorization implicit, + PolkitDetails *out_details); G_DEFINE_TYPE_WITH_CODE (PolkitBackendLocalAuthority, @@ -257,7 +258,8 @@ polkit_backend_local_authority_check_authorization_sync (PolkitBackendInteractiv gboolean subject_is_active, const gchar *action_id, PolkitDetails *details, - PolkitImplicitAuthorization implicit) + PolkitImplicitAuthorization implicit, + PolkitDetails *out_details) { PolkitBackendLocalAuthority *local_authority; PolkitBackendLocalAuthorityPrivate *priv; @@ -296,7 +298,8 @@ polkit_backend_local_authority_check_authorization_sync (PolkitBackendInteractiv details, &ret_any, &ret_inactive, - &ret_active)) + &ret_active, + out_details)) { if (subject_is_local && subject_is_active) { @@ -330,7 +333,8 @@ polkit_backend_local_authority_check_authorization_sync (PolkitBackendInteractiv details, &ret_any, &ret_inactive, - &ret_active)) + &ret_active, + out_details)) { if (subject_is_local && subject_is_active) { diff --git a/src/polkitbackend/polkitbackendlocalauthorizationstore.c b/src/polkitbackend/polkitbackendlocalauthorizationstore.c index 1c898f7..bdd0103 100644 --- a/src/polkitbackend/polkitbackendlocalauthorizationstore.c +++ b/src/polkitbackend/polkitbackendlocalauthorizationstore.c @@ -21,6 +21,7 @@ #include "config.h" +#include #include #include "polkitbackendlocalauthorizationstore.h" @@ -79,6 +80,8 @@ typedef struct PolkitImplicitAuthorization result_any; PolkitImplicitAuthorization result_inactive; PolkitImplicitAuthorization result_active; + + GHashTable *return_value; } LocalAuthorization; static void @@ -89,6 +92,8 @@ local_authorization_free (LocalAuthorization *authorization) g_list_free (authorization->identity_specs); g_list_foreach (authorization->action_specs, (GFunc) g_pattern_spec_free, NULL); g_list_free (authorization->action_specs); + if (authorization->return_value != NULL) + g_hash_table_unref (authorization->return_value); g_free (authorization); } @@ -105,6 +110,7 @@ local_authorization_new (GKeyFile *key_file, gchar *result_any_string; gchar *result_inactive_string; gchar *result_active_string; + gchar **return_value_strings; guint n; identity_strings = NULL; @@ -112,6 +118,7 @@ local_authorization_new (GKeyFile *key_file, result_any_string = NULL; result_inactive_string = NULL; result_active_string = NULL; + return_value_strings = NULL; authorization = g_new0 (LocalAuthorization, 1); @@ -221,6 +228,42 @@ local_authorization_new (GKeyFile *key_file, goto out; } + return_value_strings = g_key_file_get_string_list (key_file, + group, + "ReturnValue", + NULL, + error); + if (return_value_strings != NULL) + { + for (n = 0; return_value_strings[n] != NULL; n++) + { + gchar *p; + const gchar *key; + const gchar *value; + + p = strchr (return_value_strings[n], '='); + if (p == NULL) + { + g_warning ("Item `%s' in ReturnValue is malformed. Ignoring.", + return_value_strings[n]); + continue; + } + + *p = '\0'; + key = return_value_strings[n]; + value = p + 1; + + if (authorization->return_value == NULL) + { + authorization->return_value = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_free); + } + g_hash_table_insert (authorization->return_value, g_strdup (key), g_strdup (value)); + } + } + authorization->id = g_strdup_printf ("%s::%s", filename, group); out: @@ -229,6 +272,7 @@ local_authorization_new (GKeyFile *key_file, g_free (result_any_string); g_free (result_inactive_string); g_free (result_active_string); + g_strfreev (return_value_strings); return authorization; } @@ -605,6 +649,7 @@ polkit_backend_local_authorization_store_ensure (PolkitBackendLocalAuthorization * @out_result_any: Return location for the result for any subjects if the look up matched. * @out_result_inactive: Return location for the result for subjects in local inactive sessions if the look up matched. * @out_result_active: Return location for the result for subjects in local active sessions if the look up matched. + * @out_details: %NULL or a #PolkitDetails object to append key/value pairs to on a positive match. * * Checks if an authorization entry from @store matches @identity, @action_id and @details. * @@ -618,7 +663,8 @@ polkit_backend_local_authorization_store_lookup (PolkitBackendLocalAuthorization PolkitDetails *details, PolkitImplicitAuthorization *out_result_any, PolkitImplicitAuthorization *out_result_inactive, - PolkitImplicitAuthorization *out_result_active) + PolkitImplicitAuthorization *out_result_active, + PolkitDetails *out_details) { GList *l, *ll; gboolean ret; @@ -667,6 +713,19 @@ polkit_backend_local_authorization_store_lookup (PolkitBackendLocalAuthorization *out_result_active = authorization->result_active; ret = TRUE; + if (out_details != NULL && authorization->return_value != NULL) + { + GHashTableIter iter; + const gchar *key; + const gchar *value; + + g_hash_table_iter_init (&iter, authorization->return_value); + while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) + { + polkit_details_insert (out_details, key, value); + } + } + #if 0 g_debug ("authorization with id `%s' matched action_id `%s' for identity `%s'", authorization->id, diff --git a/src/polkitbackend/polkitbackendlocalauthorizationstore.h b/src/polkitbackend/polkitbackendlocalauthorizationstore.h index 2f2b452..865bf22 100644 --- a/src/polkitbackend/polkitbackendlocalauthorizationstore.h +++ b/src/polkitbackend/polkitbackendlocalauthorizationstore.h @@ -77,7 +77,8 @@ gboolean polkit_backend_local_authorization_store_lookup (PolkitBackendLocalA PolkitDetails *details, PolkitImplicitAuthorization *out_result_any, PolkitImplicitAuthorization *out_result_inactive, - PolkitImplicitAuthorization *out_result_active); + PolkitImplicitAuthorization *out_result_active, + PolkitDetails *out_details); G_END_DECLS diff --git a/src/programs/Makefile.am b/src/programs/Makefile.am index 074fb39..226c9f3 100644 --- a/src/programs/Makefile.am +++ b/src/programs/Makefile.am @@ -17,7 +17,7 @@ INCLUDES = \ # ---------------------------------------------------------------------------------------------------- -bin_PROGRAMS = pkexec pkcheck pkaction +bin_PROGRAMS = pkexec pkcheck pkaction pklalockdown # ---------------------------------------------------------------------------------------------------- @@ -82,6 +82,19 @@ pkaction_LDADD = \ # ---------------------------------------------------------------------------------------------------- +pklalockdown_SOURCES = pklalockdown.c + +pklalockdown_CFLAGS = \ + $(GLIB_CFLAGS) \ + $(NULL) + +pklalockdown_LDADD = \ + $(GLIB_LDADD) \ + $(top_builddir)/src/polkit/libpolkit-gobject-1.la \ + $(NULL) + +# ---------------------------------------------------------------------------------------------------- + clean-local : rm -f *~ diff --git a/src/programs/pklalockdown.c b/src/programs/pklalockdown.c new file mode 100644 index 0000000..88ce660 --- /dev/null +++ b/src/programs/pklalockdown.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static gchar *get_lockdown_filename (const gchar *action_id); +static gboolean lockdown_exists (const gchar *action_id); + + +static void +usage (int argc, char *argv[]) +{ + GError *error; + + error = NULL; + if (!g_spawn_command_line_sync ("man pklalockdown", + NULL, + NULL, + NULL, + &error)) + { + g_printerr ("Cannot show manual page: %s\n", error->message); + g_error_free (error); + } +} + +int +main (int argc, char *argv[]) +{ + guint n; + guint ret; + gboolean opt_show_help; + gboolean opt_show_version; + gchar *opt_lockdown; + gchar *opt_remove_lockdown; + + ret = 1; + + opt_show_help = FALSE; + opt_show_version = FALSE; + opt_lockdown = NULL; + opt_remove_lockdown = NULL; + + /* if we are not yet uid 0, make us uid 0 through pkexec */ + if (getuid () != 0) + { + gchar **exec_argv; + + exec_argv = g_new0 (gchar *, argc + 2); + exec_argv[0] = PACKAGE_BIN_DIR "/pkexec"; + memcpy (exec_argv + 1, argv, argc * sizeof (gchar *)); + + if (execv (PACKAGE_BIN_DIR "/pkexec", exec_argv) != 0) + { + g_printerr ("Error executing " PACKAGE_BIN_DIR "/pkexec: %s\n", g_strerror (errno)); + goto out; + } + + g_assert_not_reached (); + } + + /* We are now uid 0 (by default, the user had to authenticate to get + * here) - be careful to check all incoming args + */ + for (n = 1; n < (guint) argc; n++) + { + if (strcmp (argv[n], "--help") == 0) + { + opt_show_help = TRUE; + } + else if (strcmp (argv[n], "--version") == 0) + { + opt_show_version = TRUE; + } + else if (strcmp (argv[n], "--lockdown") == 0 || strcmp (argv[n], "-l") == 0) + { + n++; + if (n >= (guint) argc) + { + usage (argc, argv); + goto out; + } + + opt_lockdown = g_strdup (argv[n]); + } + else if (strcmp (argv[n], "--remove-lockdown") == 0 || strcmp (argv[n], "-r") == 0) + { + n++; + if (n >= (guint) argc) + { + usage (argc, argv); + goto out; + } + + opt_remove_lockdown = g_strdup (argv[n]); + } + else + { + break; + } + } + + if (opt_show_help) + { + usage (argc, argv); + ret = 0; + goto out; + } + else if (opt_show_version) + { + g_print ("pkexec version %s\n", PACKAGE_VERSION); + ret = 0; + goto out; + } + else if (opt_lockdown != NULL) + { + gchar *filename; + gchar *contents; + GError *error; + + if (lockdown_exists (opt_lockdown)) + { + g_printerr ("Error: action %s is already locked down\n", opt_lockdown); + goto out; + } + + filename = get_lockdown_filename (opt_lockdown); + contents = g_strdup_printf ("# Added by pklalockdown(1)\n" + "#\n" + "[Lockdown]\n" + "Identity=unix-user:*\n" + "Action=%s\n" + "ResultAny=no\n" + "ResultInactive=no\n" + "ResultActive=auth_admin_keep\n" + "ReturnValue=polkit.localauthority.lockdown=1", + opt_lockdown); + error = NULL; + if (!g_file_set_contents (filename, + contents, + -1, + &error)) + { + g_printerr ("Error: Cannot write to file %s: %s\n", filename, error->message); + g_error_free (error); + g_free (filename); + g_free (contents); + goto out; + } + g_free (filename); + g_free (contents); + ret = 0; + goto out; + } + else if (opt_remove_lockdown != NULL) + { + gchar *filename; + + if (!lockdown_exists (opt_remove_lockdown)) + { + g_printerr ("Error: action %s is not locked down\n", opt_remove_lockdown); + goto out; + } + + filename = get_lockdown_filename (opt_remove_lockdown); + if (g_unlink (filename) != 0) + { + g_printerr ("Error: Cannot unlink file %s: %s\n", filename, g_strerror (errno)); + g_free (filename); + goto out; + } + g_free (filename); + + ret = 0; + goto out; + } + + usage (argc, argv); + + out: + g_free (opt_lockdown); + g_free (opt_remove_lockdown); + return ret; +} + +static gchar * +get_lockdown_filename (const gchar *action_id) +{ + return g_strdup_printf (PACKAGE_LOCALSTATE_DIR + "/lib/polkit-1/localauthority/90-mandatory.d/" + "org.freedesktop.policykit.localauthority.lockdown.action-%s.pkla", + action_id); +} + +static gboolean +lockdown_exists (const gchar *action_id) +{ + gchar *filename; + gboolean ret; + + ret = FALSE; + + filename = get_lockdown_filename (action_id); + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS)) + ret = TRUE; + g_free (filename); + + return ret; +} + -- 2.7.4