Blob Blame History Raw
Index: daemon/gvfsbackendnetwork.c
===================================================================
--- daemon/gvfsbackendnetwork.c	(revision 2118)
+++ daemon/gvfsbackendnetwork.c	(working copy)
@@ -76,6 +76,8 @@
   gboolean have_smb;
   char *current_workgroup;
   GFileMonitor *smb_monitor;
+  GMutex *smb_mount_lock;
+  GVfsJobMount *mount_job;
 
   /* DNS-SD Stuff */
   gboolean have_dnssd;
@@ -270,7 +272,7 @@
       files = g_list_prepend (files, file);
 
       if (backend->current_workgroup == NULL ||
-         backend->current_workgroup[0] == 0)
+          backend->current_workgroup[0] == 0)
         workgroup = g_strconcat ("smb://", DEFAULT_WORKGROUP_NAME, "/", NULL);  
       else 
         workgroup = g_strconcat ("smb://", backend->current_workgroup, "/", NULL);    
@@ -292,10 +294,9 @@
             {
 	      char *uri = g_file_get_uri (server_file);
               g_warning ("Couldn't create directory monitor on %s. Error: %s", 
-			 uri, error->message);
+	     		 uri, error->message);
 	      g_free (uri);
-              g_error_free (error);
-              error = NULL;
+              g_clear_error (&error);
             }
         }
 
@@ -303,7 +304,8 @@
       enumer = g_file_enumerate_children (server_file, 
                                           NETWORK_FILE_ATTRIBUTES, 
                                           G_FILE_QUERY_INFO_NONE, 
-                                          NULL, NULL);
+                                          NULL, &error);
+                                          
       if (enumer != NULL)
         {
           info = g_file_enumerator_next_file (enumer, NULL, NULL);
@@ -322,10 +324,13 @@
               g_object_unref (info);
               info = g_file_enumerator_next_file (enumer, NULL, NULL);
             }
+          g_file_enumerator_close (enumer, NULL, NULL);
+          g_object_unref (enumer);
         }
+        
+      if (error)
+        g_error_free (error);
     
-      g_file_enumerator_close (enumer, NULL, NULL);
-      g_object_unref (enumer);
       g_object_unref (server_file);
 
       g_free (workgroup);
@@ -434,10 +439,61 @@
 }
 
 static void
+mount_smb_done_cb (GObject *object,
+                   GAsyncResult *res,
+                   gpointer user_data)
+{
+  GVfsBackendNetwork *backend = G_VFS_BACKEND_NETWORK(user_data);
+  GError *error = NULL;
+
+  g_file_mount_enclosing_volume_finish (G_FILE (object), res, &error);
+  
+  if (error)
+    g_error_free (error);
+
+  recompute_files (backend);
+
+  /*  We've been spawned from try_mount  */
+  if (backend->mount_job)
+    {
+      g_vfs_job_succeeded (G_VFS_JOB (backend->mount_job));
+      g_object_unref (backend->mount_job);
+    }  
+  g_mutex_unlock (backend->smb_mount_lock);
+}
+
+static void
+remount_smb (GVfsBackendNetwork *backend, GVfsJobMount *job)
+{
+  GFile *file;
+  char *workgroup;
+
+  if (! g_mutex_trylock (backend->smb_mount_lock))
+    /*  Do nothing when the mount operation is already active  */
+    return;
+  
+  backend->mount_job = job ? g_object_ref (job) : NULL;
+
+  if (backend->current_workgroup == NULL ||
+      backend->current_workgroup[0] == 0)
+    workgroup = g_strconcat ("smb://", DEFAULT_WORKGROUP_NAME, "/", NULL);  
+  else 
+    workgroup = g_strconcat ("smb://", backend->current_workgroup, "/", NULL);    
+
+  file = g_file_new_for_uri (workgroup);
+
+  g_file_mount_enclosing_volume (file, G_MOUNT_MOUNT_NONE,
+                                 NULL, NULL, mount_smb_done_cb, backend);
+  g_free (workgroup);
+  g_object_unref (file);
+}
+
+static void
 notify_smb_files_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, 
                           GFileMonitorEvent event_type, gpointer user_data)
 {
   GVfsBackendNetwork *backend = G_VFS_BACKEND_NETWORK(user_data);
+  
   switch (event_type)
     {
     case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
@@ -453,9 +509,12 @@
         backend->idle_tag = g_idle_add ((GSourceFunc)idle_add_recompute, backend);
       
       /* stop monitoring as the backend's gone. */
-      g_file_monitor_cancel (backend->smb_monitor);
-      g_object_unref (backend->smb_monitor);
-      backend->smb_monitor = NULL;
+      if (backend->smb_monitor)
+        {
+          g_file_monitor_cancel (backend->smb_monitor);
+          g_object_unref (backend->smb_monitor);
+          backend->smb_monitor = NULL;
+        }  
       break;
     default:
       break;
@@ -545,16 +604,17 @@
   backend->current_workgroup = current_workgroup;
 
   /* cancel the smb monitor */
-  g_signal_handlers_disconnect_by_func (backend->smb_monitor,
-					notify_smb_files_changed,
-					backend->smb_monitor);
-  g_file_monitor_cancel (backend->smb_monitor);
-  g_object_unref (backend->smb_monitor);
-  backend->smb_monitor = NULL;
+  if (backend->smb_monitor)
+    {
+      g_signal_handlers_disconnect_by_func (backend->smb_monitor,
+					    notify_smb_files_changed,
+					    backend->smb_monitor);
+      g_file_monitor_cancel (backend->smb_monitor);
+      g_object_unref (backend->smb_monitor);
+      backend->smb_monitor = NULL;
+    }  
 
-  /* don't re-issue recomputes if we've already queued one. */
-  if (backend->idle_tag == 0)
-    backend->idle_tag = g_idle_add ((GSourceFunc)idle_add_recompute, backend);
+  remount_smb (backend, NULL);
 }
 
 static NetworkFile *
@@ -692,6 +752,7 @@
   return TRUE;
 }
 
+
 static gboolean
 try_mount (GVfsBackend *backend,
            GVfsJobMount *job,
@@ -700,10 +761,19 @@
            gboolean is_automount)
 {
   GVfsBackendNetwork *network_backend = G_VFS_BACKEND_NETWORK (backend);
+
   network_backend->root_monitor = g_vfs_monitor_new (backend);
-  recompute_files (network_backend);
-  g_vfs_job_succeeded (G_VFS_JOB (job));
 
+  if (network_backend->have_smb)
+    {
+      remount_smb (network_backend, job);
+    }
+  else
+    {
+      recompute_files (network_backend);
+      g_vfs_job_succeeded (G_VFS_JOB (job));
+    }  
+    
   return TRUE;
 }
 
@@ -749,6 +819,8 @@
   const char * const* supported_vfs;
   int i;
 
+  network_backend->smb_mount_lock = g_mutex_new ();
+
   supported_vfs = g_vfs_get_supported_uri_schemes (g_vfs_get_default ());
 
   network_backend->have_smb = FALSE;
@@ -830,6 +902,7 @@
   GVfsBackendNetwork *backend;
   backend = G_VFS_BACKEND_NETWORK (object);
 
+  g_mutex_free (backend->smb_mount_lock);
   g_mount_spec_unref (backend->mount_spec);
   g_object_unref (backend->root_monitor);
   g_object_unref (backend->workgroup_icon);
Index: daemon/smb-browse.mount.in
===================================================================
--- daemon/smb-browse.mount.in	(revision 2118)
+++ daemon/smb-browse.mount.in	(working copy)
@@ -2,5 +2,5 @@
 Type=smb-network;smb-server
 Exec=@libexecdir@/gvfsd-smb-browse
 DBusName=org.gtk.vfs.mountpoint.smb_browse
-AutoMount=true
+AutoMount=false
 Scheme=smb
Index: daemon/gvfsbackendsmbbrowse.c
===================================================================
--- daemon/gvfsbackendsmbbrowse.c	(revision 2118)
+++ daemon/gvfsbackendsmbbrowse.c	(working copy)
@@ -28,6 +28,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <string.h>
+#include <stdlib.h>
 
 #include <glib/gstdio.h>
 #include <glib/gi18n.h>
@@ -41,6 +42,7 @@
 #include "gvfsjobqueryinfo.h"
 #include "gvfsjobenumerate.h"
 #include "gvfsdaemonprotocol.h"
+#include "gvfskeyring.h"
 #include "gmounttracker.h"
 
 #include <libsmbclient.h>
@@ -56,6 +58,10 @@
 /* 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;
@@ -74,7 +80,20 @@
   char *mounted_server; /* server or DEFAULT_WORKGROUP_NAME */
   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;
   int entry_errno;
@@ -206,6 +225,7 @@
   g_free (backend->server);
   
   g_mutex_free (backend->entries_lock);
+  g_mutex_free (backend->update_cache_lock);
 
   smbc_free_context (backend->smb_context, TRUE);
   
@@ -220,6 +240,7 @@
 g_vfs_backend_smb_browse_init (GVfsBackendSmbBrowse *backend)
 {
   backend->entries_lock = g_mutex_new ();
+  backend->update_cache_lock = g_mutex_new ();
 
   if (mount_tracker == NULL)
     mount_tracker = g_mount_tracker_new (NULL);
@@ -256,14 +277,121 @@
 	       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);
-  strncpy (password_out, "", pwmaxlen);
+
+  if (backend->mount_cancelled)
+    {
+      /*  Don't prompt for credentials, let smbclient finish the mount loop  */
+      strncpy (username_out, "ABORT", unmaxlen);
+      strncpy (password_out, "", pwmaxlen);
+      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;
+    }
+  else
+    {
+      gboolean in_keyring = FALSE;
+
+      if (!backend->password_in_keyring)
+        {
+	  in_keyring = g_vfs_keyring_lookup_password (backend->user,
+						      backend->server,
+						      backend->domain,
+						      "smb",
+						      NULL,
+						      NULL,
+						      0,
+						      &ask_user,
+						      &ask_domain,
+						      &ask_password);
+	  backend->password_in_keyring = in_keyring;
+	}
+
+      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;
+
+	  /* translators: %s is a server name */
+	  message = g_strdup_printf (_("Password required 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);
 }
 
 /* Add a server to the cache system
@@ -419,8 +547,8 @@
     }
 }
 
-static void
-update_cache (GVfsBackendSmbBrowse *backend)
+static gboolean
+update_cache (GVfsBackendSmbBrowse *backend, SMBCFILE *supplied_dir)
 {
   GString *uri;
   char dirents[1024*4];
@@ -436,6 +564,9 @@
 
   entries = NULL;
   entry_errno = 0;
+  res = -1;
+
+  g_mutex_lock (backend->update_cache_lock);
   
   /* Update Cache */
   uri = g_string_new ("smb://");
@@ -450,7 +581,7 @@
   smbc_getdents = smbc_getFunctionGetdents (backend->smb_context);
   smbc_closedir = smbc_getFunctionClosedir (backend->smb_context);
 
-  dir = smbc_opendir (backend->smb_context, uri->str);
+  dir = supplied_dir ? supplied_dir : smbc_opendir (backend->smb_context, uri->str);
   g_string_free (uri, TRUE);
   if (dir == NULL)
     {
@@ -494,10 +625,11 @@
 
       entries = g_list_reverse (entries);
     }
-      
-  smbc_closedir (backend->smb_context, dir);
 
+  if (! supplied_dir)
+    smbc_closedir (backend->smb_context, dir);
 
+
  out:
 
   g_mutex_lock (backend->entries_lock);
@@ -510,7 +642,9 @@
   backend->last_entry_update = time (NULL);
 
   g_mutex_unlock (backend->entries_lock);
-  
+  g_mutex_unlock (backend->update_cache_lock);
+
+  return (res >= 0);
 }
 
 static BrowseEntry *
@@ -620,10 +754,17 @@
 static gboolean
 cache_needs_updating (GVfsBackendSmbBrowse *backend)
 {
-  time_t now = time (NULL);
+  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 now < backend->last_entry_update ||
-    (now - backend->last_entry_update) > 10;
+  return res; 
 }
 
 static void
@@ -635,10 +776,17 @@
 {
   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;
+  GString *uri;
+  gboolean res;
   GMountSpec *browse_mount_spec;
-
+  smbc_opendir_fn smbc_opendir;
+  smbc_closedir_fn smbc_closedir;
+  
   smb_context = smbc_new_context ();
   if (smb_context == NULL)
     {
@@ -650,7 +798,13 @@
 
   smbc_setOptionUserData (smb_context, backend);
 
-  smbc_setDebug (smb_context, 0);
+  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);
@@ -666,10 +820,13 @@
   smb_context->flags = 0;
 #endif
 
+  /* Initial settings: 
+   *   - use Kerberos (always) 
+   *   - in case of no username specified, try anonymous login 
+   */
   smbc_setOptionUseKerberos (smb_context, 1);
-  smbc_setOptionFallbackAfterKerberos (smb_context, 1);  
-  //smbc_setOptionNoAutoAnonymousLogin (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);
@@ -723,6 +880,88 @@
   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 = g_string_new ("smb://");
+
+  if (op_backend->server)
+    {
+      g_string_append_encoded (uri, op_backend->server, NULL, NULL);
+      g_string_append_c (uri, '/');
+    }
+
+  do
+    {
+      op_backend->mount_try_again = FALSE;
+      op_backend->mount_cancelled = FALSE;
+
+      dir = smbc_opendir (smb_context, uri->str);
+
+      if (dir == NULL && 
+          (op_backend->mount_cancelled || (errno != EPERM && errno != EACCES)))
+	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);
+          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)
+        {
+          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_string_free (uri, TRUE);
+
+  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_PERMISSION_DENIED,
+                         _("Password dialog cancelled"));
+      else
+        /* TODO: Error from errno? */
+        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"));
+
+      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));
 }
 
@@ -822,7 +1061,7 @@
 {
   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
 
-  update_cache (op_backend);
+  update_cache (op_backend, NULL);
 
   run_mount_mountable (op_backend,
 		       job,
@@ -878,7 +1117,7 @@
 {
   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
 
-  update_cache (op_backend);
+  update_cache (op_backend, NULL);
 
   run_open_for_read (op_backend, job, filename);
 }
@@ -1052,7 +1291,7 @@
 {
   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
 
-  update_cache (op_backend);
+  update_cache (op_backend, NULL);
 
   run_query_info (op_backend, job, filename, info, matcher);
 }
@@ -1145,9 +1384,9 @@
 	      GFileQueryInfoFlags flags)
 {
   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
-  
-  update_cache (op_backend);
 
+  update_cache (op_backend, NULL);
+
   run_enumerate (op_backend, job, filename, matcher);
 }