/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2006-2007 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * Author: Alexander Larsson */ #include #include #include #include #include #include #include #include #include #include #include #include "gvfsbackendsmbbrowse.h" #include "gvfsjobmountmountable.h" #include "gvfsjobopenforread.h" #include "gvfsjobread.h" #include "gvfsjobseekread.h" #include "gvfsjobqueryinfo.h" #include "gvfsjobenumerate.h" #include "gvfsdaemonprotocol.h" #include "gvfskeyring.h" #include "gmounttracker.h" #include "gvfsbackendsmbprivate.h" #include "gvfsutils.h" #include /* The magic "default workgroup" hostname */ #define DEFAULT_WORKGROUP_NAME "X-GNOME-DEFAULT-WORKGROUP" /* Time in seconds before we mark dirents cache outdated */ #define DEFAULT_CACHE_EXPIRATION_TIME 10 typedef struct { unsigned int smbc_type; char *name; char *name_normalized; char *name_utf8; char *comment; } BrowseEntry; struct _GVfsBackendSmbBrowse { GVfsBackend parent_instance; char *user; char *domain; char *server; char *mounted_server; /* server or DEFAULT_WORKGROUP_NAME */ char *default_workgroup; int port; SMBCCTX *smb_context; char *last_user; char *last_domain; char *last_password; GMountSource *mount_source; int mount_try; gboolean mount_try_again; gboolean mount_cancelled; gboolean password_in_keyring; GPasswordSave password_save; GMutex entries_lock; GMutex update_cache_lock; time_t last_entry_update; GList *entries; }; static GHashTable *server_cache = NULL; static GMountTracker *mount_tracker = NULL; typedef struct { char *server_name; char *share_name; char *domain; char *username; } CachedServer; G_DEFINE_TYPE (GVfsBackendSmbBrowse, g_vfs_backend_smb_browse, G_VFS_TYPE_BACKEND) static char * normalize_smb_name_helper (const char *name, gssize len, gboolean valid_utf8) { if (valid_utf8) return g_utf8_casefold (name, len); else return g_ascii_strdown (name, len); } static char * normalize_smb_name (const char *name, gssize len) { gboolean valid_utf8; valid_utf8 = g_utf8_validate (name, len, NULL); return normalize_smb_name_helper (name, len, valid_utf8); } static char * smb_name_to_utf8 (const char *name, gboolean *valid_utf8_out) { GString *string; const gchar *remainder, *invalid; gint remaining_bytes, valid_bytes; gboolean valid_utf8; remainder = name; remaining_bytes = strlen (name); valid_utf8 = TRUE; string = g_string_sized_new (remaining_bytes); while (remaining_bytes != 0) { if (g_utf8_validate (remainder, remaining_bytes, &invalid)) break; valid_utf8 = FALSE; valid_bytes = invalid - remainder; g_string_append_len (string, remainder, valid_bytes); /* append U+FFFD REPLACEMENT CHARACTER */ g_string_append (string, "\357\277\275"); remaining_bytes -= valid_bytes + 1; remainder = invalid + 1; } g_string_append (string, remainder); if (valid_utf8_out) *valid_utf8_out = valid_utf8; return g_string_free (string, FALSE); } static void browse_entry_free (BrowseEntry *entry) { g_free (entry->name); g_free (entry->comment); g_free (entry); } static gboolean cached_server_equal (gconstpointer _a, gconstpointer _b) { const CachedServer *a = _a; const CachedServer *b = _b; return strcmp (a->server_name, b->server_name) == 0 && strcmp (a->share_name, b->share_name) == 0 && strcmp (a->domain, b->domain) == 0 && strcmp (a->username, b->username) == 0; } static guint cached_server_hash (gconstpointer key) { const CachedServer *server = key; return g_str_hash (server->server_name) ^ g_str_hash (server->share_name) ^ g_str_hash (server->domain) ^ g_str_hash (server->username); } static void cached_server_free (CachedServer *server) { g_free (server->server_name); g_free (server->share_name); g_free (server->domain); g_free (server->username); g_free (server); } static void g_vfs_backend_smb_browse_finalize (GObject *object) { GVfsBackendSmbBrowse *backend; backend = G_VFS_BACKEND_SMB_BROWSE (object); g_free (backend->user); g_free (backend->domain); g_free (backend->mounted_server); g_free (backend->server); g_free (backend->default_workgroup); g_mutex_clear (&backend->entries_lock); g_mutex_clear (&backend->update_cache_lock); smbc_free_context (backend->smb_context, TRUE); g_list_free_full (backend->entries, (GDestroyNotify)browse_entry_free); if (G_OBJECT_CLASS (g_vfs_backend_smb_browse_parent_class)->finalize) (*G_OBJECT_CLASS (g_vfs_backend_smb_browse_parent_class)->finalize) (object); } static void g_vfs_backend_smb_browse_init (GVfsBackendSmbBrowse *backend) { char *workgroup; GSettings *settings; g_mutex_init (&backend->entries_lock); g_mutex_init (&backend->update_cache_lock); if (mount_tracker == NULL) mount_tracker = g_mount_tracker_new (NULL, FALSE); /* Get default workgroup name */ settings = g_settings_new ("org.gnome.system.smb"); workgroup = g_settings_get_string (settings, "workgroup"); if (workgroup && workgroup[0]) backend->default_workgroup = workgroup; else g_free (workgroup); g_object_unref (settings); g_debug ("g_vfs_backend_smb_browse_init: default workgroup = '%s'\n", backend->default_workgroup ? backend->default_workgroup : "NULL"); } /** * Authentication callback function type (method that includes context) * * Type for the the authentication function called by the library to * obtain authentication credentals * * @param context Pointer to the smb context * @param srv Server being authenticated to * @param shr Share being authenticated to * @param wg Pointer to buffer containing a "hint" for the * workgroup to be authenticated. Should be filled in * with the correct workgroup if the hint is wrong. * @param wglen The size of the workgroup buffer in bytes * @param un Pointer to buffer containing a "hint" for the * user name to be use for authentication. Should be * filled in with the correct workgroup if the hint is * wrong. * @param unlen The size of the username buffer in bytes * @param pw Pointer to buffer containing to which password * copied * @param pwlen The size of the password buffer in bytes * */ static void auth_callback (SMBCCTX *context, const char *server_name, const char *share_name, char *domain_out, int domainmaxlen, char *username_out, int unmaxlen, char *password_out, int pwmaxlen) { GVfsBackendSmbBrowse *backend; char *ask_password, *ask_user, *ask_domain; gboolean handled, abort; backend = smbc_getOptionUserData (context); strncpy (password_out, "", pwmaxlen); if (backend->domain) strncpy (domain_out, backend->domain, domainmaxlen); if (backend->user) strncpy (username_out, backend->user, unmaxlen); if (backend->mount_cancelled) { /* Don't prompt for credentials, let smbclient finish the mount loop */ strncpy (username_out, "ABORT", unmaxlen); strncpy (password_out, "", pwmaxlen); g_debug ("auth_callback - mount_cancelled\n"); return; } if (backend->mount_source == NULL) { /* Not during mount, use last password */ if (backend->last_user) strncpy (username_out, backend->last_user, unmaxlen); if (backend->last_domain) strncpy (domain_out, backend->last_domain, domainmaxlen); if (backend->last_password) strncpy (password_out, backend->last_password, pwmaxlen); return; } if (backend->mount_try == 0 && backend->user == NULL && backend->domain == NULL) { /* Try again if kerberos login + anonymous fallback fails */ backend->mount_try_again = TRUE; g_debug ("auth_callback - anonymous pass\n"); } else { gboolean in_keyring = FALSE; g_debug ("auth_callback - normal pass\n"); if (!backend->password_in_keyring) { in_keyring = g_vfs_keyring_lookup_password (backend->user, backend->server, backend->domain, "smb", NULL, NULL, backend->port != -1 ? backend->port : 0, &ask_user, &ask_domain, &ask_password); backend->password_in_keyring = in_keyring; if (in_keyring) g_debug ("auth_callback - reusing keyring credentials: user = '%s', domain = '%s'\n", ask_user ? ask_user : "NULL", ask_domain ? ask_domain : "NULL"); } if (!in_keyring) { GAskPasswordFlags flags = G_ASK_PASSWORD_NEED_PASSWORD; char *message; if (g_vfs_keyring_is_available ()) flags |= G_ASK_PASSWORD_SAVING_SUPPORTED; if (backend->domain == NULL) flags |= G_ASK_PASSWORD_NEED_DOMAIN; if (backend->user == NULL) flags |= G_ASK_PASSWORD_NEED_USERNAME; g_debug ("auth_callback - asking for password...\n"); if (backend->user) { /* Translators: %s is a server name */ message = g_strdup_printf (_("Authentication Required\nEnter password for “%s”:"), server_name); } else { /* Translators: %s is a server name */ message = g_strdup_printf (_("Authentication Required\nEnter user and password for “%s”:"), server_name); } handled = g_mount_source_ask_password (backend->mount_source, message, username_out, domain_out, flags, &abort, &ask_password, &ask_user, &ask_domain, NULL, &(backend->password_save)); g_free (message); if (!handled) goto out; if (abort) { strncpy (username_out, "ABORT", unmaxlen); strncpy (password_out, "", pwmaxlen); backend->mount_cancelled = TRUE; goto out; } } /* Try again if this fails */ backend->mount_try_again = TRUE; strncpy (password_out, ask_password, pwmaxlen); if (ask_user && *ask_user) strncpy (username_out, ask_user, unmaxlen); if (ask_domain && *ask_domain) strncpy (domain_out, ask_domain, domainmaxlen); out: g_free (ask_password); g_free (ask_user); g_free (ask_domain); } backend->last_user = g_strdup (username_out); backend->last_domain = g_strdup (domain_out); backend->last_password = g_strdup (password_out); g_debug ("auth_callback - out: last_user = '%s', last_domain = '%s'\n", backend->last_user, backend->last_domain); } /* Add a server to the cache system * * @param c pointer to smb context * @param srv pointer to server to add * @param server server name * @param share share name * @param workgroup workgroup used to connect * @param username username used to connect * @return 0 on success. 1 on failure. * */ static int add_cached_server (SMBCCTX *context, SMBCSRV *new, const char *server_name, const char *share_name, const char *domain, const char *username) { CachedServer *cached_server; cached_server = g_new (CachedServer, 1); cached_server->server_name = g_strdup (server_name); cached_server->share_name = g_strdup (share_name); cached_server->domain = g_strdup (domain); cached_server->username = g_strdup (username); g_debug ("adding cached server '%s'\\'%s', user '%s';'%s' with data %p\n", server_name ? server_name : "NULL", share_name ? share_name : "(no share)", domain ? domain : "(no domain)", username ? username : "NULL", new); if (server_cache == NULL) server_cache = g_hash_table_new_full (cached_server_hash, cached_server_equal, (GDestroyNotify)cached_server_free, NULL); g_hash_table_insert (server_cache, cached_server, new); return 0; } static gboolean remove_cb (gpointer key, gpointer value, gpointer user_data) { return value == user_data; } /* Remove cached server * * @param c pointer to smb context * @param srv pointer to server to remove * @return 0 when found and removed. 1 on failure. * */ static int remove_cached_server (SMBCCTX * context, SMBCSRV * server) { guint num; if (server_cache) { g_debug ("removing cached servers with data %p\n", server); num = g_hash_table_foreach_remove (server_cache, remove_cb, server); if (num != 0) return 0; } return 1; } /* Look up a server in the cache system * * @param c pointer to smb context * @param server server name to match * @param share share name to match * @param workgroup workgroup to match * @param username username to match * @return pointer to SMBCSRV on success. NULL on failure. * */ static SMBCSRV * get_cached_server (SMBCCTX * context, const char *server_name, const char *share_name, const char *domain, const char *username) { const CachedServer key = { (char *)server_name, (char *)share_name, (char *)domain, (char *)username }; SMBCSRV *ret = NULL; g_debug ("looking up cached server '%s'\\'%s', user '%s';'%s'\n", server_name ? server_name : "NULL", share_name ? share_name : "(no share)", domain ? domain : "(no domain)", username ? username : "NULL"); if (server_cache) ret = g_hash_table_lookup (server_cache, &key); g_debug (" returning %p\n", ret); return ret; } /* Try to remove all servers from the cache system and disconnect * * @param c pointer to smb context * * @return 0 when found and removed. 1 on failure. * */ static int purge_cached (SMBCCTX * context) { g_debug ("purging server cache\n"); if (server_cache) g_hash_table_remove_all (server_cache); return 0; } static gboolean update_cache (GVfsBackendSmbBrowse *backend, SMBCFILE *supplied_dir) { char *uri; char dirents[1024*4]; struct smbc_dirent *dirp; GList *entries; SMBCFILE *dir; int res; smbc_opendir_fn smbc_opendir; smbc_getdents_fn smbc_getdents; smbc_closedir_fn smbc_closedir; entries = NULL; res = -1; g_mutex_lock (&backend->update_cache_lock); g_debug ("update_cache - updating...\n"); /* Update Cache */ smbc_opendir = smbc_getFunctionOpendir (backend->smb_context); smbc_getdents = smbc_getFunctionGetdents (backend->smb_context); smbc_closedir = smbc_getFunctionClosedir (backend->smb_context); uri = create_smb_uri (backend->server, backend->port, NULL, NULL); dir = supplied_dir ? supplied_dir : smbc_opendir (backend->smb_context, uri); g_free (uri); if (dir == NULL) { goto out; } while (TRUE) { res = smbc_getdents (backend->smb_context, dir, (struct smbc_dirent *)dirents, sizeof (dirents)); if (res <= 0) { if (res < 0) g_debug ("update_cache - smbc_getdents returned %d, errno = [%d] %s\n", res, errno, g_strerror (errno)); break; } dirp = (struct smbc_dirent *)dirents; while (res > 0) { unsigned int dirlen; if (dirp->smbc_type != SMBC_IPC_SHARE && dirp->smbc_type != SMBC_COMMS_SHARE && dirp->smbc_type != SMBC_PRINTER_SHARE && strcmp (dirp->name, ".") != 0 && strcmp (dirp->name, "..") != 0) { BrowseEntry *entry = g_new (BrowseEntry, 1); gboolean valid_utf8; entry->smbc_type = dirp->smbc_type; entry->name = g_strdup (dirp->name); entry->name_utf8 = smb_name_to_utf8 (dirp->name, &valid_utf8); entry->name_normalized = normalize_smb_name_helper (dirp->name, -1, valid_utf8); entry->comment = smb_name_to_utf8 (dirp->comment, NULL); entries = g_list_prepend (entries, entry); } dirlen = dirp->dirlen; dirp = (struct smbc_dirent *) (((char *)dirp) + dirlen); res -= dirlen; } entries = g_list_reverse (entries); } if (! supplied_dir) smbc_closedir (backend->smb_context, dir); out: g_mutex_lock (&backend->entries_lock); /* Clear old cache */ g_list_free_full (backend->entries, (GDestroyNotify)browse_entry_free); backend->entries = entries; backend->last_entry_update = time (NULL); g_debug ("update_cache - done.\n"); g_mutex_unlock (&backend->entries_lock); g_mutex_unlock (&backend->update_cache_lock); return (res >= 0); } static BrowseEntry * find_entry_unlocked (GVfsBackendSmbBrowse *backend, const char *filename) { BrowseEntry *entry, *found; GList *l; char *end; int len; char *normalized; while (*filename == '/') filename++; end = strchr (filename, '/'); if (end) { len = end - filename; while (*end == '/') end++; if (*end != 0) return NULL; } else len = strlen (filename); /* First look for an exact filename match */ found = NULL; for (l = backend->entries; l != NULL; l = l->next) { entry = l->data; if (strncmp (filename, entry->name, len) == 0 && strlen (entry->name) == len) { found = entry; break; } } if (found == NULL) { /* That failed, try normalizing the filename */ normalized = normalize_smb_name (filename, len); for (l = backend->entries; l != NULL; l = l->next) { entry = l->data; if (strcmp (normalized, entry->name_normalized) == 0) { found = entry; break; } } g_free (normalized); } return found; } static GMountSpec * get_mount_spec_for_share (const char *server, int port, const char *share) { GMountSpec *mount_spec; char *normalized; char *port_str; mount_spec = g_mount_spec_new ("smb-share"); normalized = normalize_smb_name (server, -1); g_mount_spec_set (mount_spec, "server", normalized); g_free (normalized); normalized = normalize_smb_name (share, -1); g_mount_spec_set (mount_spec, "share", normalized); g_free (normalized); if (port != -1) { port_str = g_strdup_printf ("%d", port); g_mount_spec_set (mount_spec, "port", port_str); g_free (port_str); } return mount_spec; } static gboolean is_root (const char *filename) { const char *p; p = filename; while (*p == '/') p++; return *p == 0; } static gboolean has_name (GVfsBackendSmbBrowse *backend, const char *filename) { gboolean res; g_mutex_lock (&backend->entries_lock); res = (find_entry_unlocked (backend, filename) != NULL); g_mutex_unlock (&backend->entries_lock); return res; } static gboolean cache_needs_updating (GVfsBackendSmbBrowse *backend) { time_t now; gboolean res; /* If there's already cache update in progress, lock and wait until update is finished, then recheck */ g_mutex_lock (&backend->update_cache_lock); now = time (NULL); res = now < backend->last_entry_update || (now - backend->last_entry_update) > DEFAULT_CACHE_EXPIRATION_TIME; g_mutex_unlock (&backend->update_cache_lock); return res; } static void do_mount (GVfsBackend *backend, GVfsJobMount *job, GMountSpec *mount_spec, GMountSource *mount_source, gboolean is_automount) { GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend); SMBCCTX *smb_context; SMBCFILE *dir; char *display_name; const char *debug; int debug_val; char *icon; char *symbolic_icon; gchar *port_str; char *uri; gboolean res; GMountSpec *browse_mount_spec; smbc_opendir_fn smbc_opendir; smbc_closedir_fn smbc_closedir; int errsv; smb_context = smbc_new_context (); if (smb_context == NULL) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Internal Error (%s)"), "Failed to allocate smb context"); return; } smbc_setOptionUserData (smb_context, backend); debug = g_getenv ("GVFS_SMB_DEBUG"); if (debug) debug_val = atoi (debug); else debug_val = 0; smbc_setDebug (smb_context, debug_val); smbc_setFunctionAuthDataWithContext (smb_context, auth_callback); smbc_setFunctionAddCachedServer (smb_context, add_cached_server); smbc_setFunctionGetCachedServer (smb_context, get_cached_server); smbc_setFunctionRemoveCachedServer (smb_context, remove_cached_server); smbc_setFunctionPurgeCachedServers (smb_context, purge_cached); if (op_backend->default_workgroup != NULL) smbc_setWorkgroup (smb_context, op_backend->default_workgroup); /* Initial settings: * - use Kerberos (always) * - in case of no username specified, try anonymous login */ smbc_setOptionUseKerberos (smb_context, 1); smbc_setOptionFallbackAfterKerberos (smb_context, op_backend->user != NULL); smbc_setOptionNoAutoAnonymousLogin (smb_context, op_backend->user != NULL); #if 0 smbc_setOptionDebugToStderr (smb_context, 1); #endif if (!smbc_init_context (smb_context)) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Internal Error (%s)"), "Failed to initialize smb context"); smbc_free_context (smb_context, FALSE); return; } op_backend->smb_context = smb_context; /* Convert DEFAULT_WORKGROUP_NAME to real domain */ if (op_backend->mounted_server != NULL && g_ascii_strcasecmp (op_backend->mounted_server, DEFAULT_WORKGROUP_NAME) == 0) op_backend->server = g_strdup (smbc_getWorkgroup (smb_context)); else op_backend->server = g_strdup (op_backend->mounted_server); #ifdef HAVE_SMBC_SETOPTIONPROTOCOLS /* Force NT1 protocol version if server can't be resolved (i.e. is not * hostname, nor IP address). This is needed for workgroup support, because * "client max protocol" has been changed from NT1 to SMB3 in recent samba * versions. */ if (op_backend->server != NULL) { GResolver *resolver; GList *addresses; GError *error = NULL; gchar *server; resolver = g_resolver_get_default (); /* IPv6 server includes brackets in GMountSpec, GResolver doesn't */ if (gvfs_is_ipv6 (op_backend->server)) server = g_strndup (op_backend->server + 1, strlen (op_backend->server) - 2); else server = g_strdup (op_backend->server); addresses = g_resolver_lookup_by_name (resolver, server, NULL, &error); if (addresses == NULL) { if (error != NULL) { g_debug ("%s\n", error->message); g_error_free (error); } g_debug ("Forcing NT1 protocol version\n"); smbc_setOptionProtocols (smb_context, "NT1", "NT1"); } g_resolver_free_addresses (addresses); g_object_unref (resolver); g_free (server); } #endif icon = NULL; symbolic_icon = NULL; if (op_backend->server == NULL) { display_name = g_strdup (_("Windows Network")); browse_mount_spec = g_mount_spec_new ("smb-network"); icon = "network-workgroup"; symbolic_icon = "network-workgroup-symbolic"; } else { /* translators: Name for the location that lists the smb shares availible on a server (%s is the name of the server) */ display_name = g_strdup_printf (_("Windows shares on %s"), op_backend->server); browse_mount_spec = g_mount_spec_new ("smb-server"); g_mount_spec_set (browse_mount_spec, "server", op_backend->mounted_server); if (op_backend->port != -1) { port_str = g_strdup_printf ("%d", op_backend->port); g_mount_spec_set (browse_mount_spec, "port", port_str); g_free (port_str); } icon = "network-server"; symbolic_icon = "network-server-symbolic"; } if (op_backend->user) g_mount_spec_set (browse_mount_spec, "user", op_backend->user); if (op_backend->domain) g_mount_spec_set (browse_mount_spec, "domain", op_backend->domain); g_vfs_backend_set_display_name (backend, display_name); g_free (display_name); if (icon) g_vfs_backend_set_icon_name (backend, icon); if (symbolic_icon) g_vfs_backend_set_symbolic_icon_name (backend, symbolic_icon); g_vfs_backend_set_user_visible (backend, FALSE); g_vfs_backend_set_mount_spec (backend, browse_mount_spec); g_mount_spec_unref (browse_mount_spec); op_backend->mount_source = mount_source; op_backend->mount_try = 0; op_backend->password_save = G_PASSWORD_SAVE_NEVER; smbc_opendir = smbc_getFunctionOpendir (smb_context); smbc_closedir = smbc_getFunctionClosedir (smb_context); uri = create_smb_uri (op_backend->server, op_backend->port, NULL, NULL); g_debug ("do_mount - URI = %s\n", uri); errsv = 0; do { op_backend->mount_try_again = FALSE; op_backend->mount_cancelled = FALSE; g_debug ("do_mount - try #%d \n", op_backend->mount_try); dir = smbc_opendir (smb_context, uri); errsv = errno; g_debug ("do_mount - [%s; %d] dir = %p, cancelled = %d, errno = [%d] '%s' \n", uri, op_backend->mount_try, dir, op_backend->mount_cancelled, errsv, g_strerror (errsv)); if (errsv == EINVAL && op_backend->mount_try == 0 && op_backend->user == NULL) { /* EINVAL is "expected" when kerberos is misconfigured, see: * https://gitlab.gnome.org/GNOME/gvfs/-/issues/611 */ } else if (dir == NULL && (op_backend->mount_cancelled || (errsv != EPERM && errsv != EACCES))) { g_debug ("do_mount - (errno != EPERM && errno != EACCES), cancelled = %d, breaking\n", op_backend->mount_cancelled); break; } if (dir != NULL) { /* Let update_cache() do enumeration, check for the smbc_getdents() result */ res = update_cache (op_backend, dir); smbc_closedir (smb_context, dir); g_debug ("do_mount - login successful, res = %d\n", res); if (res) break; } else { /* Purge the cache, we need to have clean playground for next auth try */ purge_cached (smb_context); } /* The first round is Kerberos-only. Only if this fails do we enable * NTLMSSP fallback (turning off anonymous fallback, which we've * already tried and failed with). */ if (op_backend->mount_try == 0) { g_debug ("do_mount - after anon, enabling NTLMSSP fallback\n"); smbc_setOptionFallbackAfterKerberos (op_backend->smb_context, 1); smbc_setOptionNoAutoAnonymousLogin (op_backend->smb_context, 1); } op_backend->mount_try++; } while (op_backend->mount_try_again); g_free (uri); op_backend->mount_source = NULL; if (dir == NULL) { if (op_backend->mount_cancelled) g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED, _("Password dialog cancelled")); else g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, /* translators: We tried to mount a windows (samba) share, but failed */ _("Failed to retrieve share list from server: %s"), g_strerror (errsv)); return; } g_vfs_keyring_save_password (op_backend->last_user, op_backend->server, op_backend->last_domain, "smb", NULL, NULL, 0, op_backend->last_password, op_backend->password_save); g_vfs_job_succeeded (G_VFS_JOB (job)); } static gboolean try_mount (GVfsBackend *backend, GVfsJobMount *job, GMountSpec *mount_spec, GMountSource *mount_source, gboolean is_automount) { GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend); const char *server; const char *user, *domain; const char *port; int port_num; if (strcmp (g_mount_spec_get_type (mount_spec), "smb-network") == 0) server = NULL; else { server = g_mount_spec_get (mount_spec, "server"); if (server == NULL) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "No server specified for smb-server share"); return TRUE; } } user = g_mount_spec_get (mount_spec, "user"); domain = g_mount_spec_get (mount_spec, "domain"); port = g_mount_spec_get (mount_spec, "port"); if (is_automount && ((user != NULL) || (domain != NULL))) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Can't automount smb browsing with specified user or domain"); return TRUE; } op_backend->user = g_strdup (user); op_backend->domain = g_strdup (domain); op_backend->mounted_server = g_strdup (server); if (port && (port_num = atoi (port))) op_backend->port = port_num; else op_backend->port = -1; return FALSE; } static void run_mount_mountable (GVfsBackendSmbBrowse *backend, GVfsJobMountMountable *job, const char *filename, GMountSource *mount_source) { BrowseEntry *entry; GError *error = NULL; GMountSpec *mount_spec; g_mutex_lock (&backend->entries_lock); entry = find_entry_unlocked (backend, filename); if (entry) { if (backend->server != NULL && entry->smbc_type == SMBC_FILE_SHARE) { mount_spec = get_mount_spec_for_share (backend->server, backend->port, entry->name); g_vfs_job_mount_mountable_set_target (job, mount_spec, "/", TRUE); g_mount_spec_unref (mount_spec); } else g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTABLE_FILE, _("Not a mountable file")); } else g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("File doesn’t exist")); g_mutex_unlock (&backend->entries_lock); if (error) { g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); } else g_vfs_job_succeeded (G_VFS_JOB (job)); } static void do_mount_mountable (GVfsBackend *backend, GVfsJobMountMountable *job, const char *filename, GMountSource *mount_source) { GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend); update_cache (op_backend, NULL); run_mount_mountable (op_backend, job, filename, mount_source); } static gboolean try_mount_mountable (GVfsBackend *backend, GVfsJobMountMountable *job, const char *filename, GMountSource *mount_source) { GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend); if (is_root (filename)) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_MOUNTABLE_FILE, _("Not a mountable file")); return TRUE; } if (cache_needs_updating (op_backend)) return FALSE; run_mount_mountable (op_backend, job, filename, mount_source); return TRUE; } static void run_open_for_read (GVfsBackendSmbBrowse *backend, GVfsJobOpenForRead *job, const char *filename) { if (has_name (backend, filename)) g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, _("Not a regular file")); else g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("File doesn’t exist")); } static void do_open_for_read (GVfsBackend *backend, GVfsJobOpenForRead *job, const char *filename) { GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend); update_cache (op_backend, NULL); run_open_for_read (op_backend, job, filename); } static gboolean try_open_for_read (GVfsBackend *backend, GVfsJobOpenForRead *job, const char *filename) { GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend); if (cache_needs_updating (op_backend)) return FALSE; run_open_for_read (op_backend, job, filename); return TRUE; } static gboolean try_read (GVfsBackend *backend, GVfsJobRead *job, GVfsBackendHandle handle, char *buffer, gsize bytes_requested) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid argument"); return TRUE; } static gboolean try_seek_on_read (GVfsBackend *backend, GVfsJobSeekRead *job, GVfsBackendHandle handle, goffset offset, GSeekType type) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid argument"); return TRUE; } static gboolean try_close_read (GVfsBackend *backend, GVfsJobCloseRead *job, GVfsBackendHandle handle) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid argument"); return TRUE; } static void get_file_info_from_entry (GVfsBackendSmbBrowse *backend, BrowseEntry *entry, GFileInfo *info) { GMountSpec *mount_spec; GString *uri; GIcon *icon; GIcon *symbolic_icon; g_file_info_set_name (info, entry->name); g_file_info_set_display_name (info, entry->name_utf8); g_file_info_set_edit_name (info, entry->name_utf8); g_file_info_set_attribute_string (info, "smb::comment", entry->comment); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL, TRUE); g_file_info_set_content_type (info, "inode/directory"); icon = NULL; if (entry->smbc_type == SMBC_WORKGROUP) { icon = g_themed_icon_new ("network-workgroup"); symbolic_icon = g_themed_icon_new ("network-workgroup-symbolic"); } else if (entry->smbc_type == SMBC_SERVER) { icon = g_themed_icon_new ("network-server"); symbolic_icon = g_themed_icon_new ("network-server-symbolic"); } else { icon = g_themed_icon_new ("folder-remote"); symbolic_icon = g_themed_icon_new ("folder-remote-symbolic"); } if (icon) { g_file_info_set_icon (info, icon); g_object_unref (icon); } if (symbolic_icon) { g_file_info_set_symbolic_icon (info, symbolic_icon); g_object_unref (symbolic_icon); } mount_spec = NULL; if (backend->server) { /* browsing server/workgroup */ if (entry->smbc_type == SMBC_WORKGROUP || entry->smbc_type == SMBC_SERVER) { uri = g_string_new ("smb://"); g_string_append_uri_escaped (uri, entry->name, NULL, FALSE); g_string_append_c (uri, '/'); } else { mount_spec = get_mount_spec_for_share (backend->server, backend->port, entry->name); uri = g_string_new ("smb://"); g_string_append_uri_escaped (uri, backend->server, NULL, FALSE); g_string_append_c (uri, '/'); g_string_append_uri_escaped (uri, entry->name, NULL, FALSE); } } else { /* browsing network */ uri = g_string_new ("smb://"); g_string_append_uri_escaped (uri, entry->name, NULL, FALSE); g_string_append_c (uri, '/'); /* these are auto-mounted, so no CAN_MOUNT/UNMOUNT */ } if (mount_spec) { g_file_info_set_file_type (info, G_FILE_TYPE_MOUNTABLE); if (g_mount_tracker_has_mount_spec (mount_tracker, mount_spec)) g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT, FALSE); else g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT, TRUE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT, FALSE); g_mount_spec_unref (mount_spec); } else g_file_info_set_file_type (info, G_FILE_TYPE_SHORTCUT); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, uri->str); g_string_free (uri, TRUE); } static void run_query_info (GVfsBackendSmbBrowse *backend, GVfsJobQueryInfo *job, const char *filename, GFileInfo *info, GFileAttributeMatcher *matcher) { BrowseEntry *entry; g_mutex_lock (&backend->entries_lock); entry = find_entry_unlocked (backend, filename); if (entry) get_file_info_from_entry (backend, entry, info); g_mutex_unlock (&backend->entries_lock); if (entry) g_vfs_job_succeeded (G_VFS_JOB (job)); else g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("File doesn’t exist")); } static void do_query_info (GVfsBackend *backend, GVfsJobQueryInfo *job, const char *filename, GFileQueryInfoFlags flags, GFileInfo *info, GFileAttributeMatcher *matcher) { GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend); update_cache (op_backend, NULL); run_query_info (op_backend, job, filename, info, matcher); } static gboolean try_query_info (GVfsBackend *backend, GVfsJobQueryInfo *job, const char *filename, GFileQueryInfoFlags flags, GFileInfo *info, GFileAttributeMatcher *matcher) { GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend); GIcon *icon; if (is_root (filename)) { g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); g_file_info_set_name (info, "/"); g_file_info_set_display_name (info, g_vfs_backend_get_display_name (backend)); g_file_info_set_content_type (info, "inode/directory"); icon = g_vfs_backend_get_icon (backend); if (icon != NULL) g_file_info_set_icon (info, icon); icon = g_vfs_backend_get_symbolic_icon (backend); if (icon != NULL) g_file_info_set_symbolic_icon (info, icon); g_vfs_job_succeeded (G_VFS_JOB (job)); return TRUE; } if (cache_needs_updating (op_backend)) return FALSE; run_query_info (op_backend, job, filename, info, matcher); return TRUE; } static void run_enumerate (GVfsBackendSmbBrowse *backend, GVfsJobEnumerate *job, const char *filename, GFileAttributeMatcher *matcher) { GList *files, *l; GFileInfo *info; if (!is_root (filename)) { if (has_name (backend, filename)) g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, _("Not a directory")); else g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("File doesn’t exist")); return; } /* TODO: limit requested to what we support */ g_vfs_job_succeeded (G_VFS_JOB (job)); files = NULL; g_mutex_lock (&backend->entries_lock); for (l = backend->entries; l != NULL; l = l->next) { BrowseEntry *entry = l->data; info = g_file_info_new (); get_file_info_from_entry (backend, entry, info); files = g_list_prepend (files, info); } g_mutex_unlock (&backend->entries_lock); files = g_list_reverse (files); g_vfs_job_enumerate_add_infos (job, files); g_list_free_full (files, g_object_unref); g_vfs_job_enumerate_done (job); } static void do_enumerate (GVfsBackend *backend, GVfsJobEnumerate *job, const char *filename, GFileAttributeMatcher *matcher, GFileQueryInfoFlags flags) { GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend); update_cache (op_backend, NULL); run_enumerate (op_backend, job, filename, matcher); } static gboolean try_enumerate (GVfsBackend *backend, GVfsJobEnumerate *job, const char *filename, GFileAttributeMatcher *matcher, GFileQueryInfoFlags flags) { GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend); if (cache_needs_updating (op_backend)) return FALSE; run_enumerate (op_backend, job, filename, matcher); return TRUE; } static gboolean try_query_fs_info (GVfsBackend *backend, GVfsJobQueryFsInfo *job, const char *filename, GFileInfo *info, GFileAttributeMatcher *matcher) { g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "cifs"); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, TRUE); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, G_FILESYSTEM_PREVIEW_TYPE_NEVER); g_vfs_job_succeeded (G_VFS_JOB (job)); return TRUE; } static void g_vfs_backend_smb_browse_class_init (GVfsBackendSmbBrowseClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass); gobject_class->finalize = g_vfs_backend_smb_browse_finalize; backend_class->mount = do_mount; backend_class->try_mount = try_mount; backend_class->mount_mountable = do_mount_mountable; backend_class->try_mount_mountable = try_mount_mountable; backend_class->open_for_read = do_open_for_read; backend_class->try_open_for_read = try_open_for_read; backend_class->try_read = try_read; backend_class->try_seek_on_read = try_seek_on_read; backend_class->try_close_read = try_close_read; backend_class->query_info = do_query_info; backend_class->try_query_info = try_query_info; backend_class->try_query_fs_info = try_query_fs_info; backend_class->enumerate = do_enumerate; backend_class->try_enumerate = try_enumerate; } void g_vfs_smb_browse_daemon_init (void) { g_set_application_name (_("Windows Network File System Service")); }