int main (int argc, char** argv) { GdaQuarkList *ql, *ql2; guint nfailed = 0; guint ntests = 0; guint tmp; ql = gda_quark_list_new_from_string ("PARAM=value;PASSWORD=*mypass*;USERNAME=dirch"); ql2 = gda_quark_list_copy (ql); nfailed = test_quark (ql, &ntests); nfailed += test_quark (ql2, &tmp); ntests += tmp; /* out */ gda_quark_list_free (ql); gda_quark_list_free (ql2); if (nfailed == 0) { g_print ("Ok (%d tests passed)\n", ntests); return EXIT_SUCCESS; } else { g_print ("Failed (%d tests failed out of %d)\n", nfailed, ntests); return EXIT_FAILURE; } }
static void fav_selection_changed_cb (G_GNUC_UNUSED GtkWidget *widget, gint fav_id, TFavoritesType fav_type, const gchar *selection, SchemaBrowserPerspective *bpers) { if (fav_type == T_FAVORITES_TABLES) { GdaQuarkList *ql; const gchar *type; const gchar *schema = NULL, *table = NULL, *short_name = NULL; ql = gda_quark_list_new_from_string (selection); if (ql) { type = gda_quark_list_find (ql, "OBJ_TYPE"); schema = gda_quark_list_find (ql, "OBJ_SCHEMA"); table = gda_quark_list_find (ql, "OBJ_NAME"); short_name = gda_quark_list_find (ql, "OBJ_SHORT_NAME"); } if (!type || !schema || !table) { if (ql) gda_quark_list_free (ql); return; } if (!strcmp (type, "table")) { schema_browser_perspective_display_table_info (bpers, schema, table, short_name); } else { gint ntabs, i; ntabs = gtk_notebook_get_n_pages (GTK_NOTEBOOK (bpers->priv->notebook)); for (i = 0; i < ntabs; i++) { GtkWidget *child; child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (bpers->priv->notebook), i); if (IS_TABLE_INFO (child)) { if (!strcmp (schema, table_info_get_table_schema (TABLE_INFO (child))) && !strcmp (table, table_info_get_table_name (TABLE_INFO (child)))) { gtk_notebook_set_current_page (GTK_NOTEBOOK (bpers->priv->notebook), i); return; } } } g_warning ("Non handled favorite type: %s", type); TO_IMPLEMENT; } if (ql) gda_quark_list_free (ql); } else if (fav_type == T_FAVORITES_DIAGRAMS) { #ifdef HAVE_GOOCANVAS schema_browser_perspective_display_diagram (bpers, fav_id); #else g_warning ("Can't display diagram because canvas not compiled."); #endif } #ifdef GDA_DEBUG_NO g_print ("Reacted to selection fav_id=>%d type=>%u, contents=>%s\n", fav_id, fav_type, selection); #endif }
/* * Set up a connection. * * Optionnally the database can be created if the <upper_case_provider_name>_DBCREATE_PARAMS * environment variable exists. Examples are: * MYSQL_DBCREATE_PARAMS "HOST=localhost" * POSTGRESQL_DBCREATE_PARAMS "HOST=localhost;PORT=5432" * SQLITE_DBCREATE_PARAMS "DB_DIR=." * BERKELEY_DB_CNC_PARAMS "DB_NAME=gda_check_bdb.db" * * The connection is opened if the <upper_case_provider_name>_CNC_PARAMS environment variable exists. * For example: * MSACCESS_CNC_PARAMS "DB_DIR=/home/me/libgda/tests/providers;DB_NAME=gda_check_db" * ORACLE_CNC_PARAMS TNSNAME=//127.0.0.1 * * * If the <upper_case_provider_name>_DBCREATE_PARAMS is supplied, then its contents can be used * to complement the <upper_case_provider_name>_CNC_PARAMS. * * Returns: a GdaConnection if no error occurred */ GdaConnection * test_cnc_setup_connection (const gchar *provider, const gchar *dbname, GError **error) { GdaConnection *cnc = NULL; gchar *str, *upname; const gchar *db_params, *cnc_params; GdaProviderInfo *prov_info; GdaQuarkList *db_quark_list = NULL, *cnc_quark_list = NULL; gboolean db_created = FALSE; g_return_val_if_fail (dbname && *dbname, NULL); prov_info = gda_config_get_provider_info (provider); if (!prov_info) { g_set_error (error, TEST_ERROR, TEST_ERROR_GENERIC, "Provider '%s' not found", provider); return NULL; } /* create database if requested */ upname = prov_name_upcase (prov_info->id); str = g_strdup_printf ("%s_DBCREATE_PARAMS", upname); db_params = getenv (str); g_free (str); if (db_params) { GdaServerOperation *op; db_quark_list = gda_quark_list_new_from_string (db_params); op = gda_server_operation_prepare_drop_database (prov_info->id, dbname, NULL); gda_quark_list_foreach (db_quark_list, (GHFunc) db_create_quark_foreach_func, op); gda_server_operation_perform_drop_database (op, NULL, NULL); g_object_unref (op); op = gda_server_operation_prepare_create_database (prov_info->id, dbname, NULL); gda_quark_list_foreach (db_quark_list, (GHFunc) db_create_quark_foreach_func, op); if (!gda_server_operation_perform_create_database (op, NULL, error)) goto out; db_created = TRUE; } /* open connection to database */ cnc = test_cnc_open_connection (provider, dbname, error); out: if (cnc) { g_object_set_data_full (G_OBJECT (cnc), "dbname", g_strdup (dbname), g_free); g_object_set_data (G_OBJECT (cnc), "db_created", GINT_TO_POINTER (db_created)); g_print ("Connection now set up (%s)\n", db_created ? "database created" : "reusing database"); } return cnc; }
/* * Options handling */ static void set_entry_options (GdauiEntryString *mgstr, const gchar *options) { GdauiEntryStringPrivate *priv = gdaui_entry_string_get_instance_private (mgstr); if (options && *options) { GdaQuarkList *params; const gchar *str; params = gda_quark_list_new_from_string (options); str = gda_quark_list_find (params, "MAX_SIZE"); if (str) priv->maxsize = atoi (str); str = gda_quark_list_find (params, "MULTILINE"); if (str) { if ((*str == 't') || (*str == 'T')) priv->multiline = TRUE; else priv->multiline = FALSE; } str = gda_quark_list_find (params, "HIDDEN"); if (str) { if ((*str == 't') || (*str == 'T')) priv->hidden = TRUE; else priv->hidden = FALSE; } if (priv->entry) { if (priv->multiline) { gtk_widget_hide (priv->entry); gtk_widget_show (priv->sw); } else { gtk_widget_show (priv->entry); gtk_widget_hide (priv->sw); gtk_entry_set_visibility (GTK_ENTRY (priv->entry), !priv->hidden); } } gda_quark_list_free (params); sync_entry_options (mgstr); } }
static void plugins_combo_changed_cb (GtkComboBox *combo, TablePreferences *tpref) { GtkTreeIter iter; GtkWidget *old_options = NULL; if (tpref->priv->options_wid) { old_options = tpref->priv->options_wid; tpref->priv->options_wid = NULL; } if (gtk_combo_box_get_active_iter (combo, &iter)) { GdauiPlugin *plugin; GtkTreeModel *model; GError *error = NULL; model = gtk_combo_box_get_model (combo); gtk_tree_model_get (model, &iter, PL_COLUMN_PLUGIN, &plugin, -1); if (plugin && plugin->options_xml_spec) { GdaSet *plist; plist = gda_set_new_from_spec_string (plugin->options_xml_spec, &error); if (!plist) { g_warning ("Cannot parse XML spec for plugin options: %s", error && error->message ? error->message : "No detail"); g_clear_error (&error); } else { if (!old_options || (g_object_get_data (G_OBJECT (old_options), "plugin") != plugin)) { tpref->priv->options_wid = gdaui_basic_form_new (plist); g_object_set_data (G_OBJECT (tpref->priv->options_wid), "plugin", plugin); g_signal_connect (G_OBJECT (tpref->priv->options_wid), "holder-changed", G_CALLBACK (options_form_param_changed_cb), tpref); gtk_box_pack_start (GTK_BOX (tpref->priv->options_vbox), tpref->priv->options_wid, TRUE, TRUE, 0); } else { tpref->priv->options_wid = old_options; old_options = NULL; } g_object_unref (plist); } if (tpref->priv->options_wid) { plist = gdaui_basic_form_get_data_set (GDAUI_BASIC_FORM (tpref->priv->options_wid)); gtk_widget_hide (tpref->priv->options_none); gtk_widget_show (tpref->priv->options_wid); if (plist && !tpref->priv->save_plugin_changes) { /* load plugin options */ GtkTreeSelection *select; GtkTreeIter citer; select = gtk_tree_view_get_selection (tpref->priv->columns_treeview); if (gtk_tree_selection_get_selected (select, NULL, &citer)) { gchar *plugin_str; gtk_tree_model_get (GTK_TREE_MODEL (tpref->priv->columns_store), &citer, COLUMN_PLUGIN, &plugin_str, -1); /*g_print ("%p PLUGIN_STR:[%s]\n", tpref, plugin_str);*/ if (plugin_str) { GdaQuarkList *ql; GSList *list; gchar *tmp; for (tmp = plugin_str; *tmp && (*tmp != ':'); tmp++); if (*tmp == ':') { ql = gda_quark_list_new_from_string (tmp+1); for (list = plist->holders; list; list = list->next) { GdaHolder *holder = GDA_HOLDER (list->data); const gchar *cstr; cstr = gda_quark_list_find (ql, gda_holder_get_id (holder)); if (cstr) gda_holder_set_value_str (holder, NULL, cstr, NULL); else gda_holder_set_value (holder, NULL, NULL); } gda_quark_list_free (ql); } g_free (plugin_str); } } } } } if (tpref->priv->save_plugin_changes && tpref->priv->current_table && tpref->priv->current_column && ! browser_connection_set_table_column_attribute (tpref->priv->bcnc, tpref->priv->current_table, tpref->priv->current_column, BROWSER_CONNECTION_COLUMN_PLUGIN, plugin ? plugin->plugin_name : NULL, &error)) { TO_IMPLEMENT; /* FIXME: add a notice somewhere in the UI */ g_warning ("Error: %s\n", error && error->message ? error->message : _("No detail")); g_clear_error (&error); } set_preview_widget (tpref); } if (old_options) gtk_widget_destroy (old_options); if (! tpref->priv->options_wid) gtk_widget_show (tpref->priv->options_none); }
/* * Prepare connection request * * In this function, the following _must_ be done: * - check for the presence and validify of the parameters required to actually open a connection, * using @params * - open the real connection to the database using the parameters previously checked, create one or * more GdaDataModel objects and declare them to the virtual connection with table names * - create a LdapConnectionData structure and associate it to @cnc * * Returns: TRUE if no error occurred, or FALSE otherwise (and an ERROR connection event must be added to @cnc) */ static gboolean gda_ldap_provider_prepare_connection (GdaServerProvider *provider, GdaConnection *cnc, GdaQuarkList *params, GdaQuarkList *auth) { g_return_val_if_fail (GDA_IS_LDAP_PROVIDER (provider), FALSE); g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE); /* Check for connection parameters */ const gchar *base_dn; const gchar *host; const gchar *tmp; const gchar *port; const gchar *user = NULL; gchar *dnuser = NULL; const gchar *pwd = NULL; const gchar *time_limit = NULL; const gchar *size_limit = NULL; const gchar *tls_method = NULL; const gchar *tls_cacert = NULL; int rtls_method = -1; gint rport; gboolean use_ssl, use_cache; /* calling the parent's function first */ GdaServerProviderBase *parent_functions; parent_functions = gda_server_provider_get_impl_functions_for_class (parent_class, GDA_SERVER_PROVIDER_FUNCTIONS_BASE); if (parent_functions->prepare_connection) { if (! parent_functions->prepare_connection (GDA_SERVER_PROVIDER (provider), cnc, params, auth)) return FALSE; } base_dn = gda_quark_list_find (params, "DB_NAME"); if (!base_dn) { gda_connection_add_event_string (cnc, "%s", _("The connection string must contain the DB_NAME value")); return FALSE; } host = gda_quark_list_find (params, "HOST"); if (!host) host = "127.0.0.1"; port = gda_quark_list_find (params, "PORT"); tmp = gda_quark_list_find (params, "USE_SSL"); use_ssl = (tmp && ((*tmp == 't') || (*tmp == 'T'))) ? TRUE : FALSE; tmp = gda_quark_list_find (params, "USE_CACHE"); use_cache = (!tmp || ((*tmp == 't') || (*tmp == 'T'))) ? TRUE : FALSE; if (port && *port) rport = atoi (port); else { if (use_ssl) rport = LDAPS_PORT; else rport = LDAP_PORT; } user = gda_quark_list_find (auth, "USERNAME"); if (!user) user = gda_quark_list_find (params, "USERNAME"); pwd = gda_quark_list_find (auth, "PASSWORD"); if (!pwd) pwd = gda_quark_list_find (params, "PASSWORD"); tls_cacert = gda_quark_list_find (params, "TLS_CACERT"); tls_method = gda_quark_list_find (params, "TLS_REQCERT"); if (tls_method && *tls_method) { if (! g_ascii_strcasecmp (tls_method, "never")) rtls_method = LDAP_OPT_X_TLS_NEVER; else if (! g_ascii_strcasecmp (tls_method, "hard")) rtls_method = LDAP_OPT_X_TLS_HARD; else if (! g_ascii_strcasecmp (tls_method, "demand")) rtls_method = LDAP_OPT_X_TLS_DEMAND; else if (! g_ascii_strcasecmp (tls_method, "allow")) rtls_method = LDAP_OPT_X_TLS_ALLOW; else if (! g_ascii_strcasecmp (tls_method, "try")) rtls_method = LDAP_OPT_X_TLS_TRY; else { gda_connection_add_event_string (cnc, "%s", _("Invalid value for 'TLS_REQCERT'")); return FALSE; } } time_limit = gda_quark_list_find (params, "TIME_LIMIT"); size_limit = gda_quark_list_find (params, "SIZE_LIMIT"); /* open LDAP connection */ LdapConnectionData *cdata; LDAP *ld; int res; gchar *url; if (use_ssl) { /* Configuring SSL/TLS options: * this is for texting purpose only, and should actually be done through LDAP's conf. * files, see: man 5 ldap.conf * * For example ~/.ldaprc can contain: * TLS_REQCERT demand * TLS_CACERT /usr/share/ca-certificates/mozilla/Thawte_Premium_Server_CA.crt * * Note: if server certificate verification fails, * the error message is: "Can't contact LDAP server" */ if (rtls_method >= 0) { res = ldap_set_option (NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &rtls_method); if (res != LDAP_SUCCESS) { gda_connection_add_event_string (cnc, ldap_err2string (res)); return FALSE; } } if (tls_cacert && *tls_cacert) { res = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTFILE, tls_cacert); if (res != LDAP_SUCCESS) { gda_connection_add_event_string (cnc, ldap_err2string (res)); return FALSE; } } url = g_strdup_printf ("ldaps://%s:%d", host, rport); } else url = g_strdup_printf ("ldap://%s:%d", host, rport); if (user && *user && ! gda_ldap_parse_dn (user, NULL)) { /* analysing the @user parameter */ /* the user name is not a DN => we need to fetch the DN of the entry * using filters defined in the "mappings" array @user */ guint i; const gchar *ptr; GString *rname; rname = g_string_new (""); for (ptr = user; *ptr; ptr++) { if ((*ptr == ',') || (*ptr == '\\') || (*ptr == '#') || (*ptr == '+') || (*ptr == '<') || (*ptr == '>') || (*ptr == ';') || (*ptr == '"') || (*ptr == '=') || (*ptr == '*')) g_string_append_c (rname, '\\'); g_string_append_c (rname, *ptr); } for (i = 0; i < sizeof (mappings) / sizeof (LdapAuthMapping); i++) { gchar *tmp; tmp = fetch_user_dn (url, base_dn, rname->str, &(mappings[i])); if (tmp) { dnuser = tmp; break; } } g_string_free (rname, TRUE); /* if no DN user has been found, then still use the provided name AS IS * => dnuser can be %NULL here */ } res = ldap_initialize (&ld, url); if (res != LDAP_SUCCESS) { gda_connection_add_event_string (cnc, ldap_err2string (res)); g_free (url); g_free (dnuser); return FALSE; } cdata = g_new0 (LdapConnectionData, 1); cdata->keep_bound_count = 0; cdata->handle = ld; cdata->url = url; cdata->time_limit = 0; cdata->size_limit = 0; cdata->base_dn = g_strdup (base_dn); if (use_cache) cdata->attributes_cache_file = compute_data_file_name (params, TRUE, "attrs"); /* set protocol version to 3 by default */ int version = LDAP_VERSION3; res = ldap_set_option (cdata->handle, LDAP_OPT_PROTOCOL_VERSION, &version); if (res != LDAP_SUCCESS) { if (res == LDAP_PROTOCOL_ERROR) { version = LDAP_VERSION2; res = ldap_set_option (cdata->handle, LDAP_OPT_PROTOCOL_VERSION, &version); } if (res != LDAP_SUCCESS) { gda_connection_add_event_string (cnc, ldap_err2string (res)); gda_ldap_free_cnc_data (cdata); g_free (dnuser); return FALSE; } } /* time limit */ if (time_limit && *time_limit) { int limit = atoi (time_limit); res = ldap_set_option (cdata->handle, LDAP_OPT_TIMELIMIT, &limit); if (res != LDAP_SUCCESS) { gda_connection_add_event_string (cnc, ldap_err2string (res)); gda_ldap_free_cnc_data (cdata); g_free (dnuser); return FALSE; } cdata->time_limit = limit; } /* size limit */ if (size_limit && *size_limit) { int limit = atoi (size_limit); res = ldap_set_option (cdata->handle, LDAP_OPT_SIZELIMIT, &limit); if (res != LDAP_SUCCESS) { gda_connection_add_event_string (cnc, ldap_err2string (res)); gda_ldap_free_cnc_data (cdata); g_free (dnuser); return FALSE; } cdata->size_limit = limit; } /* authentication */ struct berval cred; memset (&cred, 0, sizeof (cred)); cred.bv_len = pwd && *pwd ? strlen (pwd) : 0; cred.bv_val = pwd && *pwd ? (char *) pwd : NULL; res = ldap_sasl_bind_s (ld, dnuser ? dnuser : user, NULL, &cred, NULL, NULL, NULL); if (res != LDAP_SUCCESS) { gda_connection_add_event_string (cnc, ldap_err2string (res)); gda_ldap_free_cnc_data (cdata); g_free (dnuser); return FALSE; } if (pwd) { gchar *tmp; tmp = g_strdup_printf ("PASSWORD=%s", pwd); cdata->auth = gda_quark_list_new_from_string (tmp); g_free (tmp); } if (dnuser) { gchar *tmp; tmp = g_strdup_printf ("USERNAME=%s", dnuser); if (cdata->auth) gda_quark_list_add_from_string (cdata->auth, tmp, FALSE); else cdata->auth = gda_quark_list_new_from_string (tmp); g_free (tmp); dnuser = NULL; } else if (user) { gchar *tmp; tmp = g_strdup_printf ("USERNAME=%s", user); if (cdata->auth) gda_quark_list_add_from_string (cdata->auth, tmp, FALSE); else cdata->auth = gda_quark_list_new_from_string (tmp); g_free (tmp); } /* set startup file name */ gchar *fname; fname = compute_data_file_name (params, FALSE, "start"); g_object_set ((GObject*) cnc, "startup-file", fname, NULL); g_free (fname); /* open virtual connection */ gda_virtual_connection_internal_set_provider_data (GDA_VIRTUAL_CONNECTION (cnc), cdata, (GDestroyNotify) gda_ldap_free_cnc_data); gda_ldap_may_unbind (GDA_LDAP_CONNECTION (cnc)); return TRUE; }
GdaConnection * test_cnc_open_connection (const gchar *provider, const gchar *dbname, GError **error) { GdaConnection *cnc = NULL; gchar *str, *upname; const gchar *cnc_params; GdaProviderInfo *prov_info; GdaQuarkList *db_quark_list = NULL, *cnc_quark_list = NULL; gboolean db_created = FALSE; g_return_val_if_fail (dbname && *dbname, NULL); prov_info = gda_config_get_provider_info (provider); if (!prov_info) { g_set_error (error, TEST_ERROR, TEST_ERROR_GENERIC, "Provider '%s' not found", provider); return NULL; } /* open connection to database */ upname = prov_name_upcase (prov_info->id); str = g_strdup_printf ("%s_CNC_PARAMS", upname); cnc_params = getenv (str); g_free (str); if (cnc_params) cnc_quark_list = gda_quark_list_new_from_string (cnc_params); if (db_quark_list || cnc_quark_list) { Data1 data; data.string = g_string_new (""); data.ql = NULL; data.requested_db_name = NULL; if (db_quark_list) gda_quark_list_foreach (db_quark_list, (GHFunc) cnc_quark_foreach_func, &data); data.ql = db_quark_list; if (cnc_quark_list) gda_quark_list_foreach (cnc_quark_list, (GHFunc) cnc_quark_foreach_func, &data); if (*(data.string->str) != 0) g_string_append_c (data.string, ';'); g_string_append_printf (data.string, "DB_NAME=%s", data.requested_db_name ? data.requested_db_name : dbname); g_print ("Open connection string: %s\n", data.string->str); gchar *auth_string = NULL; GSList *current = prov_info->auth_params->holders; while (current) { GdaHolder *holder = (GdaHolder *) current->data; const gchar *id = gda_holder_get_id (holder); const gchar *env = NULL; if (g_strrstr (id, "USER") != NULL) { str = g_strdup_printf ("%s_USER", upname); env = getenv (str); g_free (str); } else if (g_strrstr (id, "PASS") != NULL) { str = g_strdup_printf ("%s_PASS", upname); env = getenv (str); g_free (str); } if (env) { str = g_strdup_printf ("%s=%s;", id, env); gchar *tmp = auth_string; auth_string = g_strconcat (auth_string, str, NULL); g_free (str); g_free (tmp); } current = g_slist_next (current); } cnc = gda_connection_open_from_string (prov_info->id, data.string->str, auth_string, GDA_CONNECTION_OPTIONS_NONE, error); g_free (auth_string); g_string_free (data.string, TRUE); } if (db_quark_list) gda_quark_list_free (db_quark_list); if (cnc_quark_list) gda_quark_list_free (cnc_quark_list); if (!cnc_params) g_set_error (error, TEST_ERROR, TEST_ERROR_GENERIC, "Connection parameters not specified, test not executed (define %s_CNC_PARAMS or %s_DBCREATE_PARAMS to create a test DB)\n", upname, upname); g_free (upname); return cnc; }
/* * Cleans up a connection. * * If @destroy_db is TRUE, then the database is destroyed, except if <upper_case_provider_name>_DONT_REMOVE_DB * is set. * * WARNING: the @cnc connection destroyed closed by this function */ gboolean test_cnc_clean_connection (GdaConnection *cnc, GError **error) { gchar *prov_id; gboolean retval = TRUE; gchar *str, *upname; gboolean destroy_db; prov_id = g_strdup (gda_connection_get_provider_name (cnc)); upname = prov_name_upcase (prov_id); str = g_strdup_printf ("%s_DONT_REMOVE_DB", upname); if (getenv (str)) destroy_db = FALSE; else destroy_db = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cnc), "db_created")); g_free (str); if (destroy_db) { GdaServerOperation *op; gchar *dbname; const gchar *db_params; GdaQuarkList *db_quark_list = NULL; dbname = (gchar *) g_object_get_data (G_OBJECT (cnc), "dbname"); g_assert (dbname); dbname = g_strdup (dbname); g_assert (gda_connection_close (cnc, NULL)); g_object_unref (cnc); #ifdef CHECK_EXTRA_INFO g_print ("Waiting a bit for the server to register the disconnection...\n"); #endif sleep (1); str = g_strdup_printf ("%s_DBCREATE_PARAMS", upname); db_params = getenv (str); g_free (str); g_assert (db_params); op = gda_server_operation_prepare_drop_database (prov_id, dbname, NULL); g_free (dbname); db_quark_list = gda_quark_list_new_from_string (db_params); gda_quark_list_foreach (db_quark_list, (GHFunc) db_drop_quark_foreach_func, op); gda_quark_list_free (db_quark_list); if (!gda_server_operation_perform_drop_database (op, NULL, error)) retval = FALSE; g_object_unref (op); } else { TO_IMPLEMENT; g_assert (gda_connection_close (cnc, NULL)); g_object_unref (cnc); } g_free (upname); g_free (prov_id); return retval; }